Ordenar tablas
Clase 20 de 24 • Curso de Introducción a Selenium con Python
Contenido del curso
Clase 20 de 24 • Curso de Introducción a Selenium con Python
Contenido del curso
Steven Moreno
Héctor Daniel Vega Quiñones
Sebastian Castro Torres
Víctor H. Torres
John Steven González
Ismael Danilo Herrera Sánchez
Victor Martin Ortiz Palacio
Carlos Andres Ocampo Pabon
Sebastián Andrade
Daniel Hernández Sánchez
fidel angel ochoa
fidel angel ochoa
Juan Antonio Pavon Carmona
Henry Daniel
Gonzalo García
Cristian David Restrepo Marin
Andrés Xavier Vargas Vera
Andrés Xavier Vargas Vera
Santiago Herrera Velásquez
Israel Yance
Gabriel Cayoja
Ismael Danilo Herrera Sánchez
Beatriz Lumbreras Morales
Ismael Danilo Herrera Sánchez
Jeyson David Vargas Crespo
Cristian David Restrepo Marin
Juan Esteban Sierra Aguilar
Randhal Smith Ramirez Orozco
Fabricio Quintas
Jorge Heli Rueda Uribe
Cristian David Restrepo Marin
Jorge Heli Rueda Uribe
Miguel Angel Lopez Monroy
Gabriel Ichcanziho Pérez Landa
Zacil Dzul
Roger Christian Cansaya Olazabal
En la línea 24 hice el siguiente cambio en parametro de la función para xpath
//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]
Cambié j por i. Solo eso.
Totalmente válido ;) Lo importante es llegar a la solución y existen muchos caminos.
El mejor comentario de todos. jajaja
El objetivo de la clase era aprender a obtener la información de una tabla y se cumplió. Sin embargo, la información almacenada en la colección, quedó mal estructurada a mi parecer.
Este es el resultado del ejercicio del docente:
>>> [['Last Name', 'Smith', 'Frank', 'jdoe@hotmail.com', '$50.00'], ['First Name', 'Smith', 'Frank', 'jdoe@hotmail.com', '$50.00'], ['Email', 'Smith', 'Frank', 'jdoe@hotmail.com', '$50.00'], ['Due', 'Smith', 'Frank', 'jdoe@hotmail.com', '$50.00'], ['Web Site', 'Smith', 'Frank', 'jdoe@hotmail.com', '$50.00']]
Por cada columna, está guardando la información de toda la fila.
Esta debería ser el resultado correcto, a mi parecer:
>>> [['Last Name', 'Smith', 'Bach', 'Doe', 'Conway'], ['First Name', 'John', 'Frank', 'Jason', 'Tim'], ['Email', 'jsmith@gmail.com', 'fbach@yahoo.com', 'jdoe@hotmail.com', 'tconway@earthlink.net'], ['Due', '$50.00', '$51.00', '$100.00', '$50.00']]
NOTA: Modifiqué las url del código anterior, porque no me dejaba publica este comentario con dichas urls.
Ahora, el inicio de cada sublista tiene el nombre de la columna y el resto de posiciones está la información de solo esa columna.
El problema es como obtiene el valor de row_data. Dejo el código corregido para que puedan ver las diferencias:
import unittest from selenium import webdriver class Typos(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome(executable_path=r'./chromedriver.exe') driver = self.driver driver.get('https://the-internet.herokuapp.com/') driver.find_element_by_link_text('Sortable Data Tables').click() def test_sort_tables(self): driver = self.driver table_data = [[] for i in range(5)] print(table_data) for i in range(5): header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i + 1}]/span') table_data[i].append(header.text) for j in range(4): row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]') table_data[i].append(row_data.text) print(table_data) def tearDown(self): self.driver.quit() if __name__ == '__main__': unittest.main(verbosity=2)
También noté que no funcionaba bien el método al imprimir la información.
Gracias por compartir la solución, para los nuevos: Es iterar también con la variable {i + 1} en la línea de código 24.
👏👏👏 Definitivamente de acuerdo.
Lo realize de la siguiente manera:
import unittest from selenium import webdriver from selenium.common.exceptions import NoSuchElementException class Tables(unittest.TestCase): def setUp(self): """Start web driver""" options = webdriver.ChromeOptions() options.add_argument('--no-sandbox') options.add_argument('--headless') options.add_argument('--disable-gpu') self.driver = webdriver.Chrome(options=options) def tearDow(self): """Stop web driver""" self.driver.quit() def test_sort_tables(self): driver = self.driver table_data = [] try: """Find and click link with text Sortable Data Tables""" driver.get('https://the-internet.herokuapp.com/') driver.find_element_by_link_text('Sortable Data Tables').click() get_rows_table = driver.find_element_by_id('table1').get_property('rows') get_head_table = get_rows_table[0].get_property('cells') for i in range(1, len(get_rows_table)): data = {} for j in range(len(get_rows_table)): get_head_cells = get_head_table[j].text get_body_cells = get_rows_table[i].get_property('cells')[j].text data.update({get_head_cells:get_body_cells}) table_data.append(data) print(table_data) except NoSuchElementException as ex: self.fail(ex.msg) if __name__ == "__main__": unittest.main(verbosity = 2)```
Obteniendo el siguiente resultado:
[{'Last Name': 'Smith', 'First Name': 'John', 'Email': 'jsmith@gmail.com', 'Due': '$50.00', 'Web Site': 'http://www.jsmith.com'}, {'Last Name': 'Bach', 'First Name': 'Frank', 'Email': 'fbach@yahoo.com', 'Due': '$51.00', 'Web Site': 'http://www.frank.com'}, {'Last Name': 'Doe', 'First Name': 'Jason', 'Email': 'jdoe@hotmail.com', 'Due': '$100.00', 'Web Site': 'http://www.jdoe.com'}, {'Last Name': 'Conway', 'First Name': 'Tim', 'Email': 'tconway@earthlink.net', 'Due': '$50.00', 'Web Site': 'http://www.timconway.com'}]
¿Qué es lo que escribes en la función de setUo, ese tema de las opciones, para qué son?
Hola ¿Para que sirven las opciones que tienes arriba?
¿Qué les parece esta presentación?
Aquí les dejo como lo hice con PrettyTable:
import unittest from selenium import webdriver from selenium.webdriver.common.by import By from prettytable import PrettyTable class Tables(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome(r'chromedriver') driver = self.driver #Aquí pongan el enlace, como no es https no me permite añadir mi comentario. driver.get() def test_table(self): driver = self.driver rows = [] ptable = PrettyTable() for i in range(5): header = driver.find_element( By.XPATH, f'//*[@id="table1"]/thead/tr/th[{i+1}]/span') for j in range(4): row = driver.find_element( By.XPATH, f'//*[@id="table1"]/tbody/tr[{j+1}]/td[{i+1}]') rows.append(row.text) ptable.add_column(header.text, rows) rows.clear() print(ptable) def tearDown(self): self.driver.quit() if __name__ == '__main__': unittest.main(verbosity=2)
En este enlace encontrarán la info para usar PrettyTable.
excelente aporte, me ayudo mucho y luce mejor la tabla de esta manera.
¿Como supiste como usar "prettyTable" de esta manera? lo que vi de la documentacion no usa muchos ejemplos
El resultado del video no es correcto. min 6.10. No se vuelcan correctamente los datos de la lista
Reto resuelto:
def test_sort_tables(self): driver = self.driver # Total elementos del header a capturar header_data_size = len(driver.find_elements_by_css_selector('#table1 > thead > tr > th')) -1 # total elementos del body a capturar. body_data_size = len(driver.find_elements_by_css_selector('#table1 > tbody > tr')) # Se crea una lista de sublistas table_data = [[] for i in range(body_data_size)] # Se capturan los datos for i in range(body_data_size): for j in range(header_data_size): header_data = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{j+1}]/span').text cell_data = driver.find_element_by_xpath(f'//*[@id="table2"]/tbody/tr[{i+1}]/td[{j+1}]').text table_data[i].append({header_data : cell_data}) print(table_data)
Resultado:
[[{'Last Name': 'Smith'}, {'First Name': 'John'}, {'Email': 'jsmith@gmail.com'}, {'Due': '$50.00'}, {'Web Site': 'https://www.jsmith.com'}], [{'Last Name': 'Bach'}, {'First Name': 'Frank'}, {'Email': 'fbach@yahoo.com'}, {'Due': '$51.00'}, {'Web Site': 'https://www.frank.com'}], [{'Last Name': 'Doe'}, {'First Name': 'Jason'}, {'Email': 'jdoe@hotmail.com'}, {'Due': '$100.00'}, {'Web Site': 'https://www.jdoe.com'}], [{'Last Name': 'Conway'}, {'First Name': 'Tim'}, {'Email': 'tconway@earthlink.net'}, {'Due': '$50.00'}, {'Web Site': 'https://www.timconway.com'}]]```
solo has separado, o sea has creado lista por persona no por columna, esa no era la idea en todo caso
Lo más lindo de Python, además de ser Open Source, son sus librerías. Con esta van a poder visualizar de manera mucho más estética los valores obtenidos de la tabla: https://pypi.org/project/prettytable/ Además de jugar un poco más con el código :) Espero que les guste!
Gonzalo muchas gracias por el aporte
Deberían regrabar esta clase, ese ciclo for está otro nivel de mal :(
Propongo esta solución: Agregar la dirección del sitio
from unittest import TestCase, main from pyunitreport import HTMLTestRunner from selenium import webdriver class Tables(TestCase): @classmethod def setUpClass(cls): cls.driver = webdriver.Chrome(executable_path='./chromedriver.exe') driver = cls.driver driver.implicitly_wait(10) driver.maximize_window() driver.get("<direccion del sitio>") driver.find_element_by_link_text('Sortable Data Tables').click() def test_name_elements(self): driver = self.driver number_of_columns = len(driver.find_elements_by_xpath('//table[@id="table1"]/thead/tr/th')) number_of_rows = len(driver.find_elements_by_xpath('//table[@id="table1"]//tr')) print(number_of_rows) print(number_of_columns) table_data = [] print(table_data) for i in range(number_of_rows): row=[] for j in range(number_of_columns): if i==0: # Header data =driver.find_element_by_xpath(f'//table[@id="table1"]/thead/tr/th[{j+1}]') else: # Body data = driver.find_element_by_xpath(f'//table[@id="table1"]/tbody/tr[{i}]/td[{j+1}]') row.append(data.text) table_data.append(row) print(table_data) @classmethod def tearDownClass(cls): cls.driver.implicitly_wait(5) cls.driver.quit() if __name__ == "__main__": main(verbosity=2, testRunner=HTMLTestRunner( output="tables", report_name="test_tables"))
Si se fijan el codigo de la clase retorna la misma fila de la tabla varias veces, dejo mi codigo con las pequeñas correcciones
import unittest from selenium import webdriver from time import sleep ROWS = 5 class Tables(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome('../chromedriver') driver = self.driver driver.get('pagina de prueba') #no la pongo porque la IA de comentarios de platzi me la borra driver.find_element_by_link_text('Sortable Data Tables').click() def test_sort_tables(self): driver = self.driver table_data = [[] for _ in range(ROWS)] for i in range(ROWS): header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i + 1}]/span') table_data[i].append(header.text) for j in range(ROWS - 1): row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]') table_data[i].append(row_data.text) print(table_data) def tearDown(self): self.driver.close() if __name__ == "__main__": unittest.main(verbosity = 2)
RESULTADO:
test_sort_tables (__main__.Tables) ... DevTools listening on ws://127.0.0.1:50420/devtools/browser/1ba7a561-8b24-40af-9638-e8594d558ec8 [['Last Name', 'Smith', 'Bach', 'Doe', 'Conway'], ['First Name', 'John', 'Frank', 'Jason', 'Tim'], ['Email', 'jsmith@gmail.com', 'fbach@yahoo.com', 'jdoe@hotmail.com', 'tconway@earthlink.net'], ['Due', '$50.00', '$51.00', '$100.00', '$50.00'], ['Web Site', 'https://www.jsmith.com', 'https://www.frank.com', 'https://www.jdoe.com', 'https://www.timconway.com']]
Acá lo hice con diccionario, listo para convertirlo en DataFrame si se desea:
def test_sort_tables(self): driver = self.driver table_data = {} print(table_data) for i in range(5): header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i + 1}]/span') table_data[header.text] = [] for j in range(4): row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]') table_data[header.text].append(row_data.text) print(table_data)
esta clase esta bastante improvisada, las iteraciones le salen mal y el lo sabe, y no hace nada por corregirlas
Compañeros, alguien de casualidad sabe cual es la extensión que el profesor utiliza, para que el área de las funciones o condicionales este de colores?
Los colores tiene que ver más con Themes, los puedes encontrar en el marketplace de Visual Studio Code
Estos:
Hola, me pueden ayudar, me está sacando este error y no he podido:
Este es el código:
def test_sort_tables(self): driver = self.driver #se crea una lista vacia donde se almacenan los datos, #esta lista se constituye de otras sublistas, con un comprehension list table_data = [[] for i in range(5)] #5 columnas print(table_data) #iterar por cada uno de los headers y los datos for i in range(5): header = driver.find_element_by_xpath(f'/html/body/div[2]/div/div/table[1]/thead/tr/th[{i + 1}]/span') #en el [había un 1] este se cambia para poder iterar y por eso se escogió el xpath table_data[i].append(header.text) #text para tomar solo el texto for j in range(4): #son 4 las filas que se están iterando row_data = driver.find_element_by_xpath(f'/html/body/div[2]/div/div/table[1]/tbody/tr[{j + 1}]/td[{i + 1}]') #agregar ese dato a la tabla table_data[1].append(row_data.text) #si queremos estar seguros que estamos tomando los datos print(table_data ) ``` Muchas gracias.
Hola jeydvc, veo que el error puede estar en el xpath, prueba copiándolo de nuevo.
Hola, acá mi aporte, tiene el extra de que determina el número de filas y columnas de la tabla, esto lo hace através de los selectores css.
def test_sort_tables(self): driver = self.driver # Compute rows and columns rows = len(driver.find_elements_by_css_selector('#table1 > tbody > tr')) cols = len(driver.find_elements_by_css_selector('#table1 > thead > tr > th')) table = [[] for i in range(rows+1)] # Add headers for i in range(rows): header = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{i + 1}]/span') table[0].append(header.text) # Add table data for i in range(rows): for j in range(cols): row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{i + 1}]/td[{j + 1}]') table[i].append(row_data.text) # Let's give it a basic format formated_rows = [', '.join(i) for i in table] print('\n' + '\n'.join(formated_rows))
En mi humilde opinion con el selector CSS se puede hacer mas rapido, siplemente pones la tabla y ya, nada de for loop.
En pantalla mi respuesta:
import unittest from selenium import webdriver from pyunitreport import HTMLTestRunner from selenium.webdriver.common.by import By class HomePageTests(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver = webdriver.Chrome(executable_path = 'chromedriver.exe') cls.driver.get('https://the-internet.herokuapp.com/tables') cls.driver.maximize_window() cls.driver.implicitly_wait(15) def test_xfil(self): """Test of table by its xpath""" data_table = self.driver.find_element(By.CSS_SELECTOR, '#table1') print(data_table.text) @classmethod def tearDownClass(cls): cls.driver.quit() if __name__ == '__main__': unittest.main(verbosity = 2, testRunner = HTMLTestRunner(output = 'reportes', report_name = 'table-data-report'))
Aquí está mi resolución del reto, espero a alguien le sea de ayuda.
Con este código logré que devuelva, en cada lista creada (creadas en base a la cantidad de filas que tiene la tabla) en forma de diccionarios, cada dato del header junto al que contiene el body.
El código está todo comentado para que se entienda mejor.
import unittest from selenium import webdriver from time import sleep class Tables(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome(executable_path=r'C:\chromedriver.exe') driver = self.driver driver.get('https://the-internet.herokuapp.com/') driver.find_element_by_link_text('Sortable Data Tables').click() def test_sort_tables(self): driver = self.driver # Get the len of the head data ( Table 1 ), getting a list of elements from his css selector ( -1 for the edit/delete panel ) columns_size = len(driver.find_elements_by_css_selector('#table1 > thead > tr > th')) - 1 # Get the len of data size ( in rows ) from the table 1 ( Without header ) row_size = len(driver.find_elements_by_css_selector('#table1 > tbody > tr')) table_data = [[] for i in range(row_size)] # Create a list with empty lists inside (the quantity of rows of the body) print(table_data) # Lets save all data for i in range(row_size): # Iter through each row of the data # Now iter through each column of the data for j in range(columns_size): # Take each head data text of the table header_data = driver.find_element_by_xpath(f'//*[@id="table1"]/thead/tr/th[{j + 1}]/span').text # j will iter column by column # Take each body data text row_data = driver.find_element_by_xpath(f'//*[@id="table1"]/tbody/tr[{i + 1}]/td[{j + 1}]').text # i iter row by row # Append to the table data, as a dict table_data[i].append({header_data : row_data}) print(table_data) def tearDown(self): self.driver.close() if __name__ == '__main__': unittest.main(verbosity = 2)
Esto es lo que devuelve el código.
No comprendo el [ ] que está antes del for en la línea 16.
Hola jorueda, en Python existen las list comprehensions que proporcionan una forma concisa de crear listas, esta imagen resumen un poco lo que sucede en la linea 16, donde el profe crea una lista con listas, es decir una lista padre que contiene varias listas hijas.
Te recomiendo ver esta clase del curso de Python intermedio donde el profesor Facundo explica esta forma de crear listas https://platzi.com/clases/2255-python-intermedio/36463-list-comprehensions/
Te dejo el enlace de la documentación oficial donde puedes profundizar un poco. https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions
¡Que espectacular Christian! ¡Super poderoso lo de las list compehensions! Gracias. Por otro lado, viendo lo que me comentas, haré el curso de Python intermedio, se ve súper interesante. ¡Muchas gracias!
El reto queda así:
import unittest from selenium import webdriver from selenium.webdriver.common.by import By COLUMN_NUMBER = 5 ROW_NUMBER = 4 class Tables(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome(executable_path="./usr/bin/chromedriver") driver = self.driver driver.get("https://the-internet.herokuapp.com/") driver.find_element(By.LINK_TEXT, "Sortable Data Tables").click() def test_sort_tables(self): driver = self.driver table_data = [[] for i in range(COLUMN_NUMBER)] print(table_data) for i in range(COLUMN_NUMBER): header = driver.find_element( By.XPATH, f'//*[@id="table1"]/thead/tr/th[{i + 1}]/span' ) table_data[i].append(header.text) for j in range(ROW_NUMBER): row_data = driver.find_element( By.XPATH, f'//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]' ) table_data[i].append(row_data.text) print(table_data) def tearDown(self): self.driver.close() if __name__ == "__main__": unittest.main(verbosity=2)
A quién le sirva, el siguiente código extrae los datos de header y body de forma aislada
y genera un dataframe de pandas :)
def test_sort_tables(self): driver = self.driver header = driver.find_elements(By.XPATH, '//*[@id="table1"]/thead/tr/th') header_text = [cell.text for cell in header[:-1]] body_table = driver.find_elements(By.XPATH, '//*[@id="table1"]/tbody/tr') table = [] for row in body_table: values = row.find_elements(By.XPATH, "td") values = [cell.text for cell in values[:-1]] table.append(values) table_df = pd.DataFrame(data=table, columns=header_text) print(table_df)
el código para instalar pandas es
pip install pandas
Con el ordenamiento de la clase los datos quedan escalonados, se cambio la linea row_data = driver.find_element(By.XPATH,f'//*[@id="table1"]/tbody/tr[{j + 1}]/td[{i + 1}]') para que conserve los datos de la columna y se itere solo las filas y queda asi, ya sale la columna web site con los datos de sus filas: [['Last Name', 'Smith', 'Bach', 'Doe', 'Conway'], ['First Name', 'John', 'Frank', 'Jason', 'Tim'], ['Email', 'jsmith@gmail.com', 'fbach@yahoo.com', 'jdoe@hotmail.com', 'tconway@earthlink.net'], ['Due', '$50.00', '$51.00', '$100.00', '$50.00'], ['Web Site', aqui sales las urls que no deja aportar en el comentario']]
Considerando la organizacion correcta de los datos, actualizando el codigo actualizado. (deprecated) El codigo final quedaria asi.
import unittest from selenium import webdriver from prettytable import PrettyTable from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager class Tables(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) driver = self.driver driver.get('https://the-internet.herokuapp.com/') driver.maximize_window() driver.implicitly_wait(3) driver.find_element(by=By.LINK_TEXT, value='Sortable Data Tables').click() def test_sort_table(self): driver = self.driver table_data = [[] for i in range(5)] print(table_data) ptable = PrettyTable() for i in range(5): header = driver.find_element(by=By.XPATH, value=f'//*[@id="table1"]/thead/tr/th[{i+1}]/span') table_data[i].append(header.text) for j in range(4): row_data = driver.find_element(by=By.XPATH, value=f'//*[@id="table1"]/tbody/tr[{j+1}]/td[{i+1}]') table_data[i].append(row_data.text) ptable.add_column(header.text, table_data[i]) #print(table_data) print(ptable) def tearDown(self): self.driver.implicitly_wait(3) self.driver.close() if __name__ == "__main__": unittest.main(verbosity = 2)