Python Web Scraping: sitios web dinámicos

En este capítulo, aprendamos cómo realizar web scraping en sitios web dinámicos y los conceptos involucrados en detalle.

Introducción

El web scraping es una tarea compleja y la complejidad se multiplica si el sitio web es dinámico. Según la Auditoría Global de Accesibilidad Web de las Naciones Unidas, más del 70% de los sitios web son de naturaleza dinámica y dependen de JavaScript para sus funcionalidades.

Ejemplo de sitio web dinámico

Veamos un ejemplo de un sitio web dinámico y sepamos por qué es difícil de raspar. Aquí vamos a tomar un ejemplo de búsqueda desde un sitio web llamadohttp://example.webscraping.com/places/default/search.Pero, ¿cómo podemos decir que este sitio web es de naturaleza dinámica? Se puede juzgar a partir del resultado del siguiente script de Python que intentará extraer datos de la página web mencionada anteriormente:

import re
import urllib.request
response = urllib.request.urlopen('http://example.webscraping.com/places/default/search')
html = response.read()
text = html.decode()
re.findall('(.*?)',text)

Salida

[ ]

La salida anterior muestra que el raspador de ejemplo no pudo extraer información porque el elemento <div> que estamos tratando de encontrar está vacío.

Enfoques para extraer datos de sitios web dinámicos

Hemos visto que el raspador no puede raspar la información de un sitio web dinámico porque los datos se cargan dinámicamente con JavaScript. En tales casos, podemos utilizar las dos técnicas siguientes para extraer datos de sitios web dinámicos dependientes de JavaScript:

  • JavaScript de ingeniería inversa
  • Representación de JavaScript

JavaScript de ingeniería inversa

El proceso llamado ingeniería inversa sería útil y nos permite comprender cómo las páginas web cargan los datos de forma dinámica.

Para hacer esto, necesitamos hacer clic en el inspect elementpestaña para una URL especificada. A continuación, haremos clic enNETWORK pestaña para encontrar todas las solicitudes realizadas para esa página web, incluido search.json con una ruta de /ajax. En lugar de acceder a los datos AJAX desde el navegador o mediante la pestaña RED, también podemos hacerlo con la ayuda del siguiente script de Python:

import requests
url=requests.get('http://example.webscraping.com/ajax/search.json?page=0&page_size=10&search_term=a')
url.json()

Ejemplo

El script anterior nos permite acceder a la respuesta JSON mediante el método Python json. De manera similar, podemos descargar la respuesta de cadena sin procesar y, al usar el método json.loads de python, también podemos cargarla. Estamos haciendo esto con la ayuda del siguiente script de Python. Básicamente, raspará todos los países buscando la letra del alfabeto 'a' y luego iterando las páginas resultantes de las respuestas JSON.

import requests
import string
PAGE_SIZE = 15
url = 'http://example.webscraping.com/ajax/' + 'search.json?page={}&page_size={}&search_term=a'
countries = set()
for letter in string.ascii_lowercase:
   print('Searching with %s' % letter)
   page = 0
   while True:
   response = requests.get(url.format(page, PAGE_SIZE, letter))
   data = response.json()
   print('adding %d records from the page %d' %(len(data.get('records')),page))
   for record in data.get('records'):countries.add(record['country'])
   page += 1
   if page >= data['num_pages']:
      break
   with open('countries.txt', 'w') as countries_file:
   countries_file.write('n'.join(sorted(countries)))

Después de ejecutar el script anterior, obtendremos el siguiente resultado y los registros se guardarán en el archivo llamado countries.txt.

Salida

Searching with a
adding 15 records from the page 0
adding 15 records from the page 1
...

Representación de JavaScript

En la sección anterior, hicimos ingeniería inversa en la página web sobre cómo funcionaba la API y cómo podemos usarla para recuperar los resultados en una sola solicitud. Sin embargo, podemos enfrentar las siguientes dificultades al hacer ingeniería inversa:

  • A veces, los sitios web pueden ser muy difíciles. Por ejemplo, si el sitio web está creado con una herramienta de navegador avanzada como Google Web Toolkit (GWT), entonces el código JS resultante sería generado por una máquina y sería difícil de entender y realizar ingeniería inversa.

  • Algunos marcos de nivel superior como React.js puede dificultar la ingeniería inversa al abstraer la ya compleja lógica de JavaScript.

La solución a las dificultades anteriores es utilizar un motor de renderizado de navegador que analiza HTML, aplica el formato CSS y ejecuta JavaScript para mostrar una página web.

Ejemplo

En este ejemplo, para renderizar Java Script, usaremos un módulo de Python conocido, Selenium. El siguiente código de Python representará una página web con la ayuda de Selenium:

Primero, necesitamos importar webdriver desde selenium de la siguiente manera:

from selenium import webdriver

Ahora, proporcione la ruta del controlador web que hemos descargado según nuestro requisito:

path = r'C:\\Users\\gaurav\\Desktop\\Chromedriver'
driver = webdriver.Chrome(executable_path = path)

Ahora, proporcione la URL que queremos abrir en ese navegador web ahora controlado por nuestro script de Python.

driver.get('http://example.webscraping.com/search')

Ahora, podemos usar el ID de la caja de herramientas de búsqueda para configurar el elemento a seleccionar.

driver.find_element_by_id('search_term').send_keys('.')

A continuación, podemos usar el script java para configurar el contenido del cuadro de selección de la siguiente manera:

js = "document.getElementById('page_size').options[1].text = '100';"
driver.execute_script(js)

La siguiente línea de código muestra que la búsqueda está lista para hacer clic en la página web:

driver.find_element_by_id('search').click()

La siguiente línea de código muestra que esperará 45 segundos para completar la solicitud AJAX.

driver.implicitly_wait(45)

Ahora, para seleccionar enlaces de países, podemos usar el selector CSS de la siguiente manera:

links = driver.find_elements_by_css_selector('#results a')

Ahora se puede extraer el texto de cada enlace para crear la lista de países:

countries = [link.text for link in links]
print(countries)
driver.close()