Web Scraping moderno con Python y Selenium
Publicado: 2022-03-11El raspado web se ha utilizado para extraer datos de sitios web casi desde el momento en que nació la World Wide Web. En los primeros días, el raspado se realizaba principalmente en páginas estáticas, aquellas con elementos, etiquetas y datos conocidos.
Sin embargo, más recientemente, las tecnologías avanzadas en el desarrollo web han hecho que la tarea sea un poco más difícil. En este artículo, exploraremos cómo podríamos realizar el raspado de datos en el caso de que la nueva tecnología y otros factores impidan el raspado estándar.
Raspado de datos tradicional
Como la mayoría de los sitios web producen páginas destinadas a la legibilidad humana en lugar de la lectura automática, el web scraping consistía principalmente en digerir mediante programación los datos de marcado de una página web (piense en hacer clic con el botón derecho, Ver código fuente) y luego detectar patrones estáticos en esos datos que permitirían que el programa para “leer” varias piezas de información y guardarlas en un archivo o una base de datos.
Si se encontraran los datos del informe, a menudo se podría acceder a los datos pasando variables de formulario o parámetros con la URL. Por ejemplo:
https://www.myreportdata.com?month=12&year=2004&clientid=24823
Python se ha convertido en uno de los lenguajes de web scraping más populares debido en parte a las diversas bibliotecas web que se han creado para él. Una biblioteca popular, Beautiful Soup, está diseñada para extraer datos de archivos HTML y XML al permitir buscar, navegar y modificar etiquetas (es decir, el árbol de análisis).
Raspado basado en navegador
Recientemente, tuve un proyecto de raspado que parecía bastante sencillo y estaba completamente preparado para usar el raspado tradicional para manejarlo. Pero a medida que avanzaba, encontré obstáculos que no podían superarse con los métodos tradicionales.
Tres problemas principales me impidieron mis métodos estándar de raspado:
- Certificado. Se requería instalar un certificado para acceder a la parte del sitio web donde se encontraban los datos. Al acceder a la página inicial, apareció un mensaje que me pedía que seleccionara el certificado adecuado de los instalados en mi computadora y que hiciera clic en Aceptar.
- Iframes. El sitio usó iframes, lo que arruinó mi raspado normal. Sí, podría intentar encontrar todas las URL de iframe y luego crear un mapa del sitio, pero parecía que podría volverse difícil de manejar.
- JavaScript. Se accedió a los datos después de completar un formulario con parámetros (por ejemplo, ID de cliente, rango de fechas, etc.). Normalmente, omitiría el formulario y simplemente pasaría las variables del formulario (a través de URL o como variables de formulario ocultas) a la página de resultados y vería los resultados. Pero en este caso, el formulario contenía JavaScript, lo que no me permitía acceder a las variables del formulario de manera normal.
Entonces, decidí abandonar mis métodos tradicionales y buscar una posible herramienta para el raspado basado en navegador. Esto funcionaría de manera diferente a lo normal: en lugar de ir directamente a una página, descargar el árbol de análisis y extraer elementos de datos, "actuaría como un humano" y usaría un navegador para llegar a la página que necesitaba, luego rasparía el datos, evitando así la necesidad de lidiar con las barreras mencionadas.
Selenio
En general, Selenium es bien conocido como un marco de prueba de código abierto para aplicaciones web, lo que permite a los especialistas en control de calidad realizar pruebas automatizadas, ejecutar reproducciones e implementar la funcionalidad de control remoto (permitiendo muchas instancias de navegador para pruebas de carga y múltiples tipos de navegador). En mi caso, esto parecía que podría ser útil.
Mi lenguaje de referencia para el raspado web es Python, ya que tiene bibliotecas bien integradas que generalmente pueden manejar toda la funcionalidad requerida. Y, por supuesto, existe una biblioteca de Selenium para Python. Esto me permitiría crear una instancia de un "navegador" (Chrome, Firefox, IE, etc.) y luego fingir que estaba usando el navegador para obtener acceso a los datos que estaba buscando. Y si no quisiera que el navegador apareciera realmente, podría crear el navegador en modo "sin cabeza", haciéndolo invisible para cualquier usuario.
Configuración del proyecto
Para comenzar a experimentar, necesitaba configurar mi proyecto y obtener todo lo que necesitaba. Usé una máquina con Windows 10 y me aseguré de tener una versión de Python relativamente actualizada (era v. 3.7.3). Creé un script de Python en blanco, luego cargué las bibliotecas que pensé que podrían ser necesarias, usando PIP (instalador de paquetes para Python) si aún no tenía la biblioteca cargada. Estas son las principales bibliotecas con las que comencé:
- Solicitudes (para realizar solicitudes HTTP)
- URLLib3 (manejo de URL)
- Beautiful Soup (en caso de que Selenium no pueda con todo)
- Selenium (para navegación basada en navegador)
También agregué algunos parámetros de llamada al script (usando la biblioteca argparse) para poder jugar con varios conjuntos de datos, llamando al script desde la línea de comando con diferentes opciones. Estos incluían ID de cliente, mes/año y mes/año.
Problema 1 – El Certificado
La primera elección que tenía que hacer era qué navegador le iba a decir a Selenium que usara. Como generalmente uso Chrome, y se basa en el proyecto Chromium de código abierto (también utilizado por los navegadores Edge, Opera y Amazon Silk), pensé que probaría eso primero.
Pude iniciar Chrome en el script agregando los componentes de la biblioteca que necesitaba y luego emitiendo un par de comandos simples:
# Load selenium components from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait, Select from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException # Establish chrome driver and go to report site URL url = "https://reportdata.mytestsite.com/transactionSearch.jsp" driver = webdriver.Chrome() driver.get(url)
Como no inicié el navegador en modo sin cabeza, el navegador apareció y pude ver lo que estaba haciendo. Inmediatamente me pidió que seleccionara un certificado (que había instalado anteriormente).
El primer problema a abordar fue el certificado. ¿Cómo seleccionar el adecuado y aceptarlo para ingresar al sitio web? En mi primera prueba del script, recibí este mensaje:
Esto no fue bueno. No quería hacer clic manualmente en el botón Aceptar cada vez que ejecutaba mi script.
Resultó que pude encontrar una solución para esto, sin programación. Si bien esperaba que Chrome tuviera la capacidad de pasar un nombre de certificado al inicio, esa función no existía. Sin embargo, Chrome tiene la capacidad de seleccionar automáticamente un certificado si existe una determinada entrada en su registro de Windows. Puede configurarlo para seleccionar el primer certificado que ve, o ser más específico. Como solo tenía un certificado cargado, utilicé el formato genérico.
Por lo tanto, con ese conjunto, cuando le dije a Selenium que iniciara Chrome y apareció un aviso de certificado, Chrome "Seleccionaría automáticamente" el certificado y continuaría.
Problema 2 – Iframes
Bien, ahora estaba en el sitio y apareció un formulario que me pedía que ingresara la identificación del cliente y el rango de fechas del informe.

Al examinar el formulario en las herramientas para desarrolladores (F12), noté que el formulario se presentaba dentro de un iframe. Entonces, antes de que pudiera comenzar a completar el formulario, necesitaba "cambiar" al iframe adecuado donde existía el formulario. Para hacer esto, invoqué la función de cambio a de Selenium, así:
# Switch to iframe where form is frame_ref = driver.find_elements_by_tag_name("iframe")[0] iframe = driver.switch_to.frame(frame_ref)
Bien, ahora en el cuadro derecho, pude determinar los componentes, completar el campo de identificación del cliente y seleccionar los menús desplegables de fecha:
# Find the Customer ID field and populate it element = driver.find_element_by_name("custId") element.send_keys(custId) # send a test id # Find and select the date drop-downs select = Select(driver.find_element_by_name("fromMonth")) select.select_by_visible_text(from_month) select = Select(driver.find_element_by_name("fromYear")) select.select_by_visible_text(from_year) select = Select(driver.find_element_by_name("toMonth")) select.select_by_visible_text(to_month) select = Select(driver.find_element_by_name("toYear")) select.select_by_visible_text(to_year)
Problema 3 – JavaScript
Lo único que quedaba en el formulario era hacer "clic" en el botón Buscar, para que comenzara la búsqueda. Esto fue un poco complicado ya que el botón Buscar parecía estar controlado por JavaScript y no era un botón normal del tipo "Enviar". Al inspeccionarlo en las herramientas de desarrollo, encontré la imagen del botón y pude obtener el XPath haciendo clic con el botón derecho.
Luego, armado con esta información, encontré el elemento en la página y luego hice clic en él.
# Find the 'Find' button, then click it driver.find_element_by_xpath("/html/body/table/tbody/tr[2]/td[1]/table[3]/tbody/tr[2]/td[2]/input").click()
¡Y listo, se envió el formulario y aparecieron los datos! Ahora, podría raspar todos los datos en la página de resultados y guardarlos según sea necesario. ¿O podría?
Obtener los datos
Primero, tuve que manejar el caso donde la búsqueda no encontró nada. Eso fue bastante sencillo. Mostraría un mensaje en el formulario de búsqueda sin dejarlo, algo así como "No se encontraron registros". Simplemente busqué esa cadena y me detuve allí mismo si la encontraba.
Pero si llegaban los resultados, los datos se presentaban en divs con un signo más (+) para abrir una transacción y mostrar todos sus detalles. Una transacción abierta mostraba un signo menos (-) que, al hacer clic, cerraría el div. Al hacer clic en un signo más, se llamaría a una URL para abrir su div y cerrar cualquier abierto.
Por lo tanto, era necesario encontrar cualquier signo más en la página, recopilar la URL junto a cada uno y luego recorrer cada uno para obtener todos los datos de cada transacción.
# Loop through transactions and count links = driver.find_elements_by_tag_name('a') link_urls = [link.get_attribute('href') for link in links] thisCount = 0 isFirst = 1 for url in link_urls: if (url.find("GetXas.do?processId") >= 0): # URL to link to transactions if isFirst == 1: # already expanded + isFirst = 0 else: driver.get(url) # collapsed +, so expand # Find closest element to URL element with correct class to get tran type tran_type=driver.find_element_by_xpath("//*[contains(@href,'/retail/transaction/results/GetXas.do?processId=-1')]/following::td[@class='txt_75b_lmnw_T1R10B1']").text # Get transaction status status = driver.find_element_by_class_name('txt_70b_lmnw_t1r10b1').text # Add to count if transaction found if (tran_type in ['Move In','Move Out','Switch']) and (status == "Complete"): thisCount += 1
En el código anterior, los campos que obtuve fueron el tipo de transacción y el estado, luego se agregaron a un conteo para determinar cuántas transacciones se ajustan a las reglas especificadas. Sin embargo, podría haber recuperado otros campos dentro del detalle de la transacción, como fecha y hora, subtipo, etc.
Para este proyecto, el recuento se devolvió a una aplicación de llamada. Sin embargo, este y otros datos raspados también podrían haberse almacenado en un archivo plano o en una base de datos.
Posibles obstáculos y soluciones adicionales
Es posible que se presenten muchos otros obstáculos al rastrear sitios web modernos con su propia instancia de navegador, pero la mayoría se puede resolver. Aquí hay algunos:
Tratando de encontrar algo antes de que aparezca
Mientras navega, ¿con qué frecuencia encuentra que está esperando que aparezca una página, a veces durante muchos segundos? Bueno, lo mismo puede ocurrir mientras se navega mediante programación. Buscas una clase u otro elemento, ¡y no está allí!
Afortunadamente, Selenium tiene la capacidad de esperar hasta que ve un determinado elemento, y puede agotarse el tiempo de espera si el elemento no aparece, así:
element = WebDriverWait(driver, 10). until(EC.presence_of_element_located((By.ID, "theFirstLabel")))
Pasar por un captcha
Algunos sitios emplean Captcha o similar para evitar robots no deseados (que podrían considerarlo). Esto puede poner un freno al web scraping y ralentizarlo.
Para indicaciones simples (como "¿cuánto es 2 + 3?"), generalmente se pueden leer y resolver fácilmente. Sin embargo, para barreras más avanzadas, existen bibliotecas que pueden ayudar a intentar descifrarlas. Algunos ejemplos son 2Captcha, Death by Captcha y Bypass Captcha.
Cambios estructurales del sitio web
Los sitios web están destinados a cambiar, y a menudo lo hacen. Es por eso que al escribir un script de scraping, es mejor tener esto en cuenta. Querrá pensar en qué métodos usará para encontrar los datos y cuáles no usar. Considere técnicas de coincidencia parcial, en lugar de tratar de hacer coincidir una frase completa. Por ejemplo, un sitio web puede cambiar un mensaje de "No se encontraron registros" a "No se encontraron registros", pero si su coincidencia está en "No hay registros", debería estar bien. Además, considere si desea hacer coincidir XPATH, ID, nombre, texto de enlace, etiqueta o nombre de clase, o selector de CSS, y cuál es menos probable que cambie.
Resumen: Python y Selenium
Esta fue una breve demostración para mostrar que casi cualquier sitio web se puede raspar, sin importar qué tecnologías se utilicen y qué complejidades estén involucradas. Básicamente, si puede navegar por el sitio usted mismo, generalmente se puede raspar.
Ahora, como advertencia, no significa que todos los sitios web deban rasparse. Algunos tienen restricciones legítimas y ha habido numerosos casos judiciales que deciden la legalidad de raspar ciertos sitios. Por otro lado, algunos sitios aceptan y fomentan la recuperación de datos de su sitio web y, en algunos casos, proporcionan una API para facilitar las cosas.
De cualquier manera, es mejor consultar los términos y condiciones antes de comenzar cualquier proyecto. Pero si sigue adelante, tenga la seguridad de que puede hacer el trabajo.
Recursos recomendados para Web Scraping complejo:
- Web Scraping avanzado de Python: mejores prácticas y soluciones alternativas
- Scraping escalable hágalo usted mismo: cómo construir y ejecutar scrapers a gran escala