En este artículo, verás:
- Las principales causas de la lentitud del proceso de Scraping web
- Varias técnicas para acelerar el Scraping web
- Cómo optimizar un script de scraping de Python de muestra para una recuperación de datos más rápida
¡Empecemos!
Razones por las que tu proceso de scraping es lento
Explora las razones clave por las que tu proceso de Scraping web puede ser lento.
Razón n.º 1: respuestas lentas del servidor
Uno de los factores más notables que afectan a la velocidad del Scraping web es el tiempo de respuesta del servidor. Cuando envías una solicitud a un sitio web, el servidor la procesa y responde a tu solicitud. Si el servidor es lento, tus solicitudes tardarán más en completarse. Las razones por las que un servidor puede ser lento son el tráfico intenso, los recursos limitados o las ralentizaciones de la red.
Desafortunadamente, hay poco que pueda hacer para acelerar un servidor de destino. Eso está fuera de su control, a menos que la ralentización se deba a un número abrumador de solicitudes por su parte. Si este es el caso, distribuya sus solicitudes a lo largo de un periodo de tiempo más largo añadiendo retrasos aleatorios entre ellas.
Razón n.º 2: procesamiento lento de la CPU
La velocidad de procesamiento de la CPU desempeña un papel crucial en la rapidez con la que pueden funcionar sus scripts de scraping. Cuando ejecuta sus scripts de forma secuencial, su CPU se encarga de procesar cada operación de una en una, lo que puede llevar mucho tiempo. Esto se nota especialmente cuando sus scripts implican cálculos complejos o transformaciones de datos.
Además, el parseo de HTML lleva cierto tiempo y puede ralentizar significativamente el proceso de scraping. Obtenga más información en nuestro artículo sobre Scraping web HTML.
Razón n.º 3: Operaciones de E/S limitadas
Las operaciones de entrada/salida (E/S) pueden convertirse fácilmente en el cuello de botella de su operación de scraping. Esto es especialmente cierto cuando el sitio de destino consta de varias páginas. Si su script está diseñado para esperar las respuestas de recursos externos antes de continuar, esto puede provocar retrasos considerables.
Enviar una solicitud, esperar a que el servidor responda, procesarla y luego pasar a la siguiente solicitud no es una forma eficiente de realizar el Scraping web.
Otras razones
Otras razones que ralentizan su script de Scraping web son:
- Código ineficiente: una lógica de scraping mal optimizada puede ralentizar todo el proceso de scraping. Evite estructuras de datos ineficientes, bucles innecesarios o registros excesivos.
- Limitación de velocidad: si el sitio de destino restringe el número de solicitudes que un usuario puede realizar en un periodo de tiempo determinado, tu Scraper automatizado será lento como resultado. ¿La solución? ¡Servicios de Proxy!
- CAPTCHAs y otras soluciones antirraspado: Los CAPTCHAs y las medidas antibots pueden interrumpir su proceso de rastreo al requerir la interacción del usuario. Descubra otras técnicas antirraspado.
Técnicas para acelerar el Scraping web
En esta sección, descubrirá los métodos más populares para acelerar el Scraping web. Comenzaremos con un script básico de scraping en Python y demostraremos el impacto de varias optimizaciones en él.
Nota: Las técnicas que se exploran aquí funcionan con cualquier lenguaje de programación o tecnología. Se utiliza Python solo por simplicidad y porque es uno de los mejores lenguajes de programación para el Scraping web.
Este es el script inicial de scraping en Python:
import requests
from bs4 import BeautifulSoup
import csv
import time
def scrape_quotes_to_scrape():
# matriz con las URL de la página que se va a rastrear
urls = [
"http://quotes.toscrape.com/",
"https://quotes.toscrape.com/page/2/",
"https://quotes.toscrape.com/page/3/",
"https://quotes.toscrape.com/page/4/",
"https://quotes.toscrape.com/page/5/",
"https://quotes.toscrape.com/page/6/",
"https://quotes.toscrape.com/page/7/",
"https://quotes.toscrape.com/page/8/",
"https://quotes.toscrape.com/page/9/",
"https://quotes.toscrape.com/page/10/"
]
# dónde almacenar los datos recopilados
quotes = []
# extraer las páginas secuencialmente
for url in urls:
print(f"Extraer página: '{url}'")
# enviar una solicitud GET para obtener el HTML de la página
response = requests.get(url)
# analizar el HTML de la página utilizando BeautifulSoup
soup = BeautifulSoup(response.content, "html.parser")
# seleccionar todos los elementos de cita de la página
quote_html_elements = soup.select(".quote")
# iterar sobre los elementos de cita y extraer su contenido
for quote_html_element in quote_html_elements:
# extraer el texto de la cita
text = quote_html_element.select_one(".text").get_text()
# extraer el autor de la cita
author = quote_html_element.select_one(".author").get_text()
# extraer las etiquetas asociadas a la cita
tags = [tag.get_text() for tag in quote_html_element.select(".tag")]
# rellenar un nuevo objeto de cita y añadirlo a la lista
quote = {
"text": text,
"author": author,
"tags": ", ".join(tags)
}
quotes.append(quote)
print(f"Página '{url}' rastreada con éxiton")
print("Exportando datos rastreados a CSV")
# exportar las citas extraídas a un archivo CSV
with open("quotes.csv", "w", newline="", encoding="utf-8") as csvfile:
fieldnames = ["text", "author", "tags"]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(quotes)
print("Citas exportadas a CSVn")
# medir el tiempo de ejecución
start_time = time.time()
scrape_quotes_to_scrape()
end_time = time.time()
execution_time = end_time - start_time
print(f"Tiempo de ejecución: {execution_time:.2f} segundos")
El Scraper anterior tiene como objetivo 10 URL paginadas del sitio web Quotes to Scrape. Para cada URL, el script realiza las siguientes operaciones:
- Envía una solicitud GET utilizando
requestspara obtener el HTML de la página. - Parsea el contenido HTML con
BeautifulSoup. - Extrae el texto de la cita, el autor y las etiquetas de cada elemento de cita de la página.
- Almacena los datos extraídos en una lista de diccionarios.
Por último, exporta los datos extraídos a un archivo CSV llamado quotes.csv.
Para ejecutar el script, instala las bibliotecas necesarias con:
pip install requests beautifulsoup4
La llamada a la función scrape_quotes_to_scrape() se envuelve con llamadas a time.time() para medir cuánto tiempo tarda el proceso de recopilación. En nuestra máquina, el script inicial tarda aproximadamente 4,51 segundos en completarse.
Al ejecutar el script, se genera un archivo quotes.csv en la carpeta del proyecto. Además, verá registros similares a los siguientes:
Rastreando la página: 'http://quotes.toscrape.com/'
Página 'http://quotes.toscrape.com/' rastreada con éxito
Rastreando la página: 'https://quotes.toscrape.com/page/2/'
Página 'https://quotes.toscrape.com/page/2/' rastreada con éxito
Raspando la página: 'https://quotes.toscrape.com/page/3/'
Página 'https://quotes.toscrape.com/page/3/' raspada con éxito
Raspando la página: 'https://quotes.toscrape.com/page/4/'
Página «https://quotes.toscrape.com/page/4/» extraída correctamente
Extraer página: «https://quotes.toscrape.com/page/5/»
Página «https://quotes.toscrape.com/page/5/» extraída correctamente
Raspando página: 'https://quotes.toscrape.com/page/6/'
Página 'https://quotes.toscrape.com/page/6/' raspada con éxito
Raspando página: 'https://quotes.toscrape.com/page/7/'
Página «https://quotes.toscrape.com/page/7/» extraída correctamente
Extraer página: «https://quotes.toscrape.com/page/8/»
Página «https://quotes.toscrape.com/page/8/» extraída correctamente
Raspando página: 'https://quotes.toscrape.com/page/9/'
Página 'https://quotes.toscrape.com/page/9/' raspada con éxito
Raspando página: 'https://quotes.toscrape.com/page/10/'
Página «https://quotes.toscrape.com/page/10/» extraída correctamente
Exportando datos extraídos a CSV
Cotizaciones exportadas a CSV
Tiempo de ejecución: 4,63 segundos
Este resultado muestra claramente que el script extrae secuencialmente cada página web paginada de Quotes to Scrape. Como verás a continuación, algunas optimizaciones cambiarán significativamente el flujo y la velocidad de este proceso.
Ahora, ¡veamos cómo acelerar el Scraping web!
1. Utilice una biblioteca de parseo HTML más rápida
El parseo de datos consume tiempo y recursos, y los diferentes analizadores HTML utilizan diversos enfoques para realizar esta tarea. Algunos se centran en proporcionar un amplio conjunto de funciones con una API autodescriptiva, mientras que otros dan prioridad al rendimiento. Para obtener más detalles, consulte nuestra guía sobre los mejores analizadores HTML.
En Python, Beautiful Soup es el analizador HTML más popular, pero no necesariamente el más rápido. Consulte algunas pruebas de rendimiento para obtener más contexto.
En realidad, Beautiful Soup actúa como un envoltorio alrededor de diferentes analizadores subyacentes. Puede especificar el analizador que desea utilizar al inicializarlo, a través del segundo argumento:
soup = BeautifulSoup(response.content, "html.parser")
Por lo general, Beautiful Soup se utiliza en combinación con html.parser, el analizador integrado de la biblioteca estándar de Python. Sin embargo, si lo que busca es velocidad, debería considerar lxml. Se trata de uno de los analizadores HTML más rápidos disponibles en Python, ya que se basa en una implementación en C.
Para instalar lxml, ejecuta el siguiente comando:
pip install lxml
Una vez instalado, puedes utilizarlo con Beautiful Soup de la siguiente manera:
soup = BeautifulSoup(response.content, "lxml")
Ahora, vuelve a ejecutar tu script de scraping en Python. Esta vez, deberías ver el siguiente resultado:
# omitido por brevedad...
Tiempo de ejecución: 4,35 segundos
El tiempo de ejecución se redujo de 4,61 segundos a 4,35 segundos. Aunque este cambio pueda parecer pequeño, el impacto de esta optimización depende en gran medida del tamaño y la complejidad de las páginas HTML que se realizan y del número de elementos que se seleccionan.
En este ejemplo, el sitio de destino tiene páginas con una estructura DOM simple, corta y poco profunda. Aun así, lograr una mejora de velocidad de alrededor del 6 % con solo un pequeño cambio en el código es una ganancia que vale la pena.
👍 Ventajas:
- Fácil de implementar en Beautiful Soup
👎 Desventajas:
- Ventaja pequeña
- Solo funciona en páginas con estructuras DOM complejas
- Los analizadores HTML más rápidos pueden tener una API más compleja
2. Implementar el scraping multiprocesamiento
El multiprocesamiento es un enfoque de ejecución paralela en el que un programa genera múltiples procesos. Cada uno de estos procesos opera en paralelo y de forma independiente en un núcleo de CPU para realizar tareas simultáneamente en lugar de secuencialmente.
Este método es especialmente beneficioso para operaciones vinculadas a E/S, como el Scraping web. El motivo es que el principal cuello de botella suele ser el tiempo que se pasa esperando las respuestas de los servidores web. Al utilizar múltiples procesos, se pueden enviar solicitudes a varias páginas al mismo tiempo, lo que reduce el tiempo total de scraping.
Para adaptar su script de scraping al multiprocesamiento, es necesario realizar algunas modificaciones importantes en la lógica de ejecución. Siga los pasos que se indican a continuación para transformar su Scraper de Python de un enfoque secuencial a uno de multiprocesamiento.
Para empezar con el multiprocesamiento en Python, el primer paso es importar Pool y cpu_count desde el módulo multiprocessing:
from multiprocessing import Pool, cpu_count
Pool proporciona lo que necesitas para gestionar un conjunto de procesos de trabajo. Por su parte, cpu_count te ayuda a determinar el número de núcleos de CPU disponibles para el procesamiento paralelo.
A continuación, aísle la lógica para extraer una sola URL dentro de una función:
def scrape_page(url):
print(f"Scraping page: '{url}'")
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")
quote_html_elements = soup.select(".quote")
quotes = []
for quote_html_element in quote_html_elements:
text = quote_html_element.select_one(".text").get_text()
author = quote_html_element.select_one(".author").get_text()
tags = [tag.get_text() for tag in quote_html_element.select(".tag")]
quotes.append({
"text": text,
"author": author,
"tags": ", ".join(tags)
})
print(f"Página '{url}' extraída correctamenten")
return quotes
La función anterior será llamada por cada proceso de trabajo y ejecutada en un núcleo de CPU a la vez.
A continuación, sustituya el flujo de rastreo secuencial por una lógica de multiprocesamiento:
def scrape_quotes():
urls = [
"http://quotes.toscrape.com/",
"https://quotes.toscrape.com/page/2/",
"https://quotes.toscrape.com/page/3/",
"https://quotes.toscrape.com/page/4/",
"https://quotes.toscrape.com/page/5/",
"https://quotes.toscrape.com/page/6/",
"https://quotes.toscrape.com/page/7/",
«https://quotes.toscrape.com/page/8/»,
«https://quotes.toscrape.com/page/9/»,
«https://quotes.toscrape.com/page/10/»
]
# crear un grupo de procesos
with Pool(processes=cpu_count()) as pool:
results = pool.map(scrape_page, urls)
# aplanar la lista de resultados
quotes = [quote for sublist in results for quote in sublist]
print("Exporting scraped data to CSV")
with open("quotes_multiprocessing.csv", "w", newline="", encoding="utf-8") as csvfile:
fieldnames = ["text", "author", "tags"]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(quotes)
print("Citas exportadas a CSVn")
Por último, ejecuta la función scrape_quotes() mientras mides el tiempo de ejecución:
if __name__ == "__main__":
start_time = time.time()
scrape_quotes()
end_time = time.time()
execution_time = end_time - start_time
print(f"Tiempo de ejecución: {execution_time:.2f} segundos")
Ten en cuenta que la construcción if __name__ == "__main__": es necesaria para evitar que ciertas partes de tu código se ejecuten cuando se importa el módulo. Sin esta comprobación, el módulo multiprocessing puede intentar generar nuevos procesos que pueden provocar un comportamiento inesperado, especialmente en Windows.
Si lo juntamos todo, obtendremos:
from multiprocessing import Pool, cpu_count
import requests
from bs4 import BeautifulSoup
import csv
import time
def scrape_page(url):
print(f"Scraping page: '{url}'")
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")
quote_html_elements = soup.select(".quote")
quotes = []
for quote_html_element in quote_html_elements:
text = quote_html_element.select_one(".text").get_text()
author = quote_html_element.select_one(".author").get_text()
tags = [tag.get_text() for tag in quote_html_element.select(".tag")]
quotes.append({
"text": text,
"author": author,
"tags": ", ".join(tags)
})
print(f"Página '{url}' extraída correctamenten")
return quotes
def scrape_quotes():
urls = [
"http://quotes.toscrape.com/",
"https://quotes.toscrape.com/page/2/",
"https://quotes.toscrape.com/page/3/",
"https://quotes.toscrape.com/page/4/",
"https://quotes.toscrape.com/page/5/",
"https://quotes.toscrape.com/page/6/",
"https://quotes.toscrape.com/page/7/",
«https://quotes.toscrape.com/page/8/»,
«https://quotes.toscrape.com/page/9/»,
«https://quotes.toscrape.com/page/10/»
]
# crear un grupo de procesos
with Pool(processes=cpu_count()) as pool:
results = pool.map(scrape_page, urls)
# aplanar la lista de resultados
quotes = [quote for sublist in results for quote in sublist]
print("Exporting scraped data to CSV")
with open("quotes_multiprocessing.csv", "w", newline="", encoding="utf-8") as csvfile:
fieldnames = ["text", "author", "tags"]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(quotes)
print("Citas exportadas a CSVn")
if __name__ == "__main__":
start_time = time.time()
scrape_quotes()
end_time = time.time()
execution_time = end_time - start_time
print(f"Tiempo de ejecución: {execution_time:.2f} segundos")
Vuelve a ejecutar el script. Esta vez, generará algunos registros como los siguientes:
Raspando página: 'http://quotes.toscrape.com/'
Raspando página: 'https://quotes.toscrape.com/page/2/'
Raspando página: 'https://quotes.toscrape.com/page/3/'
Raspando página: 'https://quotes.toscrape.com/page/4/'
Página rastreada: «https://quotes.toscrape.com/page/5/»
Página rastreada: «https://quotes.toscrape.com/page/6/»
Página de rastreo: «https://quotes.toscrape.com/page/7/»
Página de rastreo: «https://quotes.toscrape.com/page/8/»
Página «http://quotes.toscrape.com/» rastreada correctamente
Página de rastreo: «https://quotes.toscrape.com/page/9/»
Página «https://quotes.toscrape.com/page/3/» extraída correctamente
Extraer página: «https://quotes.toscrape.com/page/10/»
Página «https://quotes.toscrape.com/page/4/» extraída correctamente.
Página «https://quotes.toscrape.com/page/5/» extraída correctamente.
Página «https://quotes.toscrape.com/page/6/» extraída correctamente.
Página «https://quotes.toscrape.com/page/7/» extraída correctamente.
Página «https://quotes.toscrape.com/page/2/» extraída correctamente.
Página «https://quotes.toscrape.com/page/8/» extraída correctamente.
Página «https://quotes.toscrape.com/page/9/» extraída correctamente
Página «https://quotes.toscrape.com/page/10/» extraída correctamente
Exportación de datos extraídos a CSV
Cotizaciones exportadas a CSV
Tiempo de ejecución: 1,87 segundos
Como puede ver, el orden de ejecución ya no es secuencial. Ahora su script puede extraer varias páginas simultáneamente. Concretamente, puede extraer hasta el número de núcleos disponibles en su CPU (8, en nuestro caso).
El procesamiento paralelo da como resultado una mejora del tiempo de alrededor del 145 %, lo que reduce el tiempo de ejecución de 4,61 segundos a 1,87 segundos. ¡Es impresionante!
👍 Ventajas:
- Gran mejora en el tiempo de ejecución
- Compatible de forma nativa con la mayoría de los lenguajes de programación
👎 Desventajas:
- Limitado por el número de núcleos disponibles en su máquina
- No respeta el orden de las URL en la lista
- Requiere muchos cambios en el código
3. Implementar el scraping multihilo
El multithreading es una técnica de programación que permite ejecutar varios subprocesos simultáneamente dentro de un mismo proceso. Esto permite que tu script realice varias tareas a la vez, cada una de ellas gestionada por un subproceso específico.
Aunque es similar al multiprocesamiento, el multithreading no requiere necesariamente múltiples núcleos de CPU. Esto se debe a que un solo núcleo de CPU puede ejecutar numerosos subprocesos simultáneamente, compartiendo el mismo espacio de memoria. Profundiza en este concepto en nuestra guía sobre concurrencia frente a paralelismo.
Ten en cuenta que transformar un script de scraping de un enfoque secuencial a uno multihilo requiere cambios similares a los descritos en el capítulo anterior.
En esta implementación, utilizaremos ThreadPoolExecutor del módulo concurrent.futures de Python. Puede importarlo como se indica a continuación:
from concurrent.futures import ThreadPoolExecutor
ThreadPoolExecutor proporciona una interfaz de alto nivel para gestionar un grupo de subprocesos, ejecutándolos simultáneamente por usted.
Como antes, comience aislando la lógica para extraer una sola URL en una función, tal como lo hicimos en el capítulo anterior. La diferencia clave es que ahora necesita utilizar ThreadPoolExecutor para ejecutar la función en múltiples subprocesos:
quotes = []
# crear un grupo de subprocesos con hasta 10 trabajadores
with ThreadPoolExecutor(max_workers=10) as executor:
# utilizar map para aplicar la función scrape_page a cada URL
results = executor.map(scrape_page, urls)
# combinar los resultados de todos los subprocesos
for result in results:
quotes.extend(result)
De forma predeterminada, si max_workers es None o no se especifica, el valor predeterminado será el número de procesadores de tu máquina, multiplicado por 5. En este caso, solo tenemos 10 páginas, por lo que establecerlo en 10 estará bien. No olvides que abrir demasiados subprocesos puede ralentizar tu sistema y provocar problemas de rendimiento en lugar de mejoras.
El script completo de scraping contendrá el siguiente código:
from concurrent.futures import ThreadPoolExecutor
import requests
from bs4 import BeautifulSoup
import csv
import time
def scrape_page(url):
print(f"Scraping page: '{url}'")
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")
quote_html_elements = soup.select(".quote")
quotes = []
for quote_html_element in quote_html_elements:
text = quote_html_element.select_one(".text").get_text()
author = quote_html_element.select_one(".author").get_text()
tags = [tag.get_text() for tag in quote_html_element.select(".tag")]
quotes.append({
"text": text,
"author": author,
"tags": ", ".join(tags)
})
print(f"Página '{url}' extraída correctamenten")
return quotes
def scrape_quotes():
urls = [
"http://quotes.toscrape.com/",
"https://quotes.toscrape.com/page/2/",
"https://quotes.toscrape.com/page/3/",
"https://quotes.toscrape.com/page/4/",
"https://quotes.toscrape.com/page/5/",
"https://quotes.toscrape.com/page/6/",
"https://quotes.toscrape.com/page/7/",
«https://quotes.toscrape.com/page/8/»,
«https://quotes.toscrape.com/page/9/»,
«https://quotes.toscrape.com/page/10/»
]
# dónde almacenar los datos extraídos
quotes = []
# crear un grupo de subprocesos con hasta 10 trabajadores
with ThreadPoolExecutor(max_workers=10) as executor:
# usar map para aplicar la función scrape_page a cada URL
results = executor.map(scrape_page, urls)
# combinar los resultados de todos los subprocesos
for result in results:
quotes.extend(result)
print("Exportando datos extraídos a CSV")
with open("quotes_multiprocessing.csv", "w", newline="", encoding="utf-8") as csvfile:
fieldnames = ["text", "author", "tags"]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(quotes)
print("Citas exportadas a CSVn")
if __name__ == "__main__":
start_time = time.time()
scrape_quotes()
end_time = time.time()
execution_time = end_time - start_time
print(f"Tiempo de ejecución: {execution_time:.2f} segundos")
Al ejecutarlo, se registrarán los siguientes mensajes:
Raspando la página: 'http://quotes.toscrape.com/'
Raspando la página: 'https://quotes.toscrape.com/page/2/'
Raspando la página: 'https://quotes.toscrape.com/page/3/'
Raspando la página: 'https://quotes.toscrape.com/page/4/'
Raspando la página: 'https://quotes.toscrape.com/page/5/'
Raspando la página: 'https://quotes.toscrape.com/page/6/'
Raspando la página: 'https://quotes.toscrape.com/page/7/'
Página de rastreo: 'https://quotes.toscrape.com/page/8/'
Página de rastreo: 'https://quotes.toscrape.com/page/9/'
Página de rastreo: 'https://quotes.toscrape.com/page/10/'
Página «http://quotes.toscrape.com/» extraída correctamente
Página «https://quotes.toscrape.com/page/6/» extraída correctamente
Página «https://quotes.toscrape.com/page/7/» extraída correctamente
Página «https://quotes.toscrape.com/page/10/» extraída correctamente
Página «https://quotes.toscrape.com/page/8/» extraída correctamente
Página «https://quotes.toscrape.com/page/5/» extraída correctamente
Página «https://quotes.toscrape.com/page/9/» extraída correctamente
Página «https://quotes.toscrape.com/page/4/» extraída correctamente
Página «https://quotes.toscrape.com/page/3/» extraída correctamente
Página «https://quotes.toscrape.com/page/2/» extraída correctamente
Exportación de datos extraídos a CSV
Cotizaciones exportadas a CSV
Tiempo de ejecución: 0,52 segundos
Al igual que en el rastreo multiprocesamiento, el orden de ejecución de las páginas ya no es secuencial. En esta ocasión, la mejora en el rendimiento es aún mayor que con el multiprocesamiento. Esto se debe a que el script ahora puede ejecutar 10 solicitudes simultáneamente, superando el límite anterior de 8 solicitudes (el número de núcleos de la CPU).
La mejora en el tiempo es enorme, de 4,61 segundos a 0,52 segundos, lo que supone una reducción porcentual de alrededor del 885 %.
👍 Ventajas:
- Enorme mejora en el tiempo de ejecución
- Compatible de forma nativa con la mayoría de las tecnologías
👎 Desventajas:
- No es fácil encontrar el número adecuado de subprocesos
- No respeta el orden de las URL en la lista
- Requiere muchos cambios en el código
4. Uso de Async/Await Scraping
La programación asíncrona es un paradigma de programación moderno que permite escribir código sin bloqueos. La idea es dar a los desarrolladores la capacidad de gestionar operaciones concurrentes sin tener que gestionar explícitamente el multithreading o el multiprocesamiento.
En un enfoque síncrono tradicional, cada operación debe terminar antes de que comience la siguiente. Esto puede dar lugar a ineficiencias, especialmente en tareas vinculadas a la E/S, como el Scraping web. Con la programación asíncrona, se pueden iniciar varias operaciones de E/S simultáneamente y luego esperar a que se completen. Esto mantiene el script receptivo y eficiente.
En Python, el scraping asíncrono se implementa generalmente utilizando el módulo asyncio de la biblioteca estándar. Este paquete proporciona la infraestructura para escribir código concurrente de un solo subproceso utilizando corrutinas, a través de las palabras clave async y await.
Sin embargo, las bibliotecas HTTP estándar, como requests, no admiten operaciones asíncronas. Por lo tanto, es necesario utilizar un cliente HTTP asíncrono como AIOHTTP, que está diseñado específicamente para funcionar a la perfección con asyncio. Esta combinación le ayuda a enviar múltiples solicitudes HTTP simultáneamente sin bloquear la ejecución de su script.
Instale AIOHTTP utilizando el siguiente comando:
pip install aiohttp
A continuación, importe asyncio y aiohttp:
import asyncio
import aiohttp
Al igual que en los capítulos anteriores, encapsule la lógica para extraer una sola URL en una función. Sin embargo, esta vez, la función será asíncrona:
async def scrape_url(session, url):
async with session.get(url) as response:
print(f"Scraping page: '{url}'")
html_content = await response.text()
soup = BeautifulSoup(html_content, "html.parser")
# scraping logic...
Observe el uso de la función await para recuperar el HTML de la página web.
Para ejecutar la función en paralelo, cree una sesión AIOHTTP y recopile varias tareas de rastreo:
# ejecutando las tareas de scraping de forma concurrente
async with aiohttp.ClientSession() as session:
tasks = [scrape_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
# aplanar la lista de resultados
quotes = [quote for sublist in results for quote in sublist]
Por último, utiliza asyncio.run() para ejecutar tu función principal de scraping asíncrono:
if __name__ == "__main__":
start_time = time.time()
asyncio.run(scrape_quotes())
end_time = time.time()
execution_time = end_time - start_time
print(f"Tiempo de ejecución: {execution_time:.2f} segundos")
Tu script de scraping asíncrono en Python contendrá estas líneas de código:
import asyncio
import aiohttp
from bs4 import BeautifulSoup
import csv
import time
async def scrape_url(session, url):
async with session.get(url) as response:
print(f"Rastreando página: '{url}'")
html_content = await response.text()
soup = BeautifulSoup(html_content, "html.parser")
quote_html_elements = soup.select(".quote")
quotes = []
for quote_html_element in quote_html_elements:
text = quote_html_element.select_one(".text").get_text()
author = quote_html_element.select_one(".author").get_text()
tags = [tag.get_text() for tag in quote_html_element.select(".tag")]
quotes.append({
"text": text,
"author": author,
"tags": ", ".join(tags)
})
print(f"Página '{url}' rastreada con éxiton")
return quotes
async def scrape_quotes():
urls = [
"http://quotes.toscrape.com/",
"https://quotes.toscrape.com/page/2/",
"https://quotes.toscrape.com/page/3/",
"https://quotes.toscrape.com/page/4/",
"https://quotes.toscrape.com/page/5/",
"https://quotes.toscrape.com/page/6/",
"https://quotes.toscrape.com/page/7/",
«https://quotes.toscrape.com/page/8/»,
«https://quotes.toscrape.com/page/9/»,
«https://quotes.toscrape.com/page/10/»
]
# ejecutando las tareas de scraping simultáneamente
async with aiohttp.ClientSession() as session:
tasks = [scrape_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
# aplanar la lista de resultados
quotes = [quote for sublist in results for quote in sublist]
print("Exportando datos extraídos a CSV")
with open("quotes_multiprocessing.csv", "w", newline="", encoding="utf-8") as csvfile:
fieldnames = ["text", "author", "tags"]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(quotes)
print("Citas exportadas a CSVn")
if __name__ == "__main__":
start_time = time.time()
asyncio.run(scrape_quotes())
end_time = time.time()
execution_time = end_time - start_time
print(f"Tiempo de ejecución: {execution_time:.2f} segundos")
Ejecútalo y obtendrás un resultado como este:
Raspando la página: 'http://quotes.toscrape.com/'
Página 'http://quotes.toscrape.com/' raspada con éxito
Raspando la página: 'https://quotes.toscrape.com/page/3/'
Raspando la página: 'https://quotes.toscrape.com/page/7/'
Raspando la página: 'https://quotes.toscrape.com/page/9/'
Raspando la página: 'https://quotes.toscrape.com/page/6/'
Página rastreada: «https://quotes.toscrape.com/page/8/»
Página rastreada: «https://quotes.toscrape.com/page/10/»
Página «https://quotes.toscrape.com/page/3/» rastreada correctamente
Página de rastreo: 'https://quotes.toscrape.com/page/5/'
Página de rastreo: 'https://quotes.toscrape.com/page/4/'
Página 'https://quotes.toscrape.com/page/7/' rastreada correctamente
Página «https://quotes.toscrape.com/page/9/» extraída correctamente
Página «https://quotes.toscrape.com/page/6/» extraída correctamente
Extrayendo página: «https://quotes.toscrape.com/page/2/»
Página «https://quotes.toscrape.com/page/10/» extraída correctamente
Página «https://quotes.toscrape.com/page/5/» extraída correctamente
Página «https://quotes.toscrape.com/page/4/» extraída correctamente
Página «https://quotes.toscrape.com/page/8/» extraída correctamente.
Página «https://quotes.toscrape.com/page/2/» extraída correctamente.
Exportación de datos extraídos a CSV.
Cotizaciones exportadas a CSV.
Tiempo de ejecución: 0,51 segundos.
Tenga en cuenta que el tiempo de ejecución es similar al del enfoque multihilo, pero con la ventaja añadida de no tener que gestionar manualmente los hilos.
👍 Ventajas:
- Gran ahorro en el tiempo de ejecución
- La programación moderna se basa en la lógica asíncrona.
- No requiere la gestión manual de subprocesos o procesos
👎 Desventajas:
- No es tan fácil de dominar
- No respeta el orden de las URL en la lista
- Requiere bibliotecas asíncronas dedicadas
5. Otros consejos y enfoques para acelerar el scraping
Otras formas de acelerar el Scraping web son:
- Optimización de la tasa de solicitudes: ajusta los intervalos de solicitud para encontrar el equilibrio óptimo entre la velocidad y evitar la limitación de la tasa o el bloqueo.
- Proxies rotativos: utilice proxies rotativos para distribuir las solicitudes entre varias direcciones IP, lo que reduce las posibilidades de ser bloqueado y permite un rastreo más rápido. Vea los mejores proxies rotativos.
- Rastreo paralelo con sistemas distribuidos: distribuya las tareas de rastreo entre varias máquinas en línea.
- Reducir la representación de JavaScript: Intente evitar las herramientas de automatización del navegador y dé preferencia a herramientas como los clientes HTTP como analizadores HTML. Recuerde que los navegadores consumen muchos recursos y son mucho más lentos que la mayoría de los analizadores HTML tradicionales.
Conclusión
En esta guía, hemos visto cómo acelerar el Scraping web. Hemos descubierto las principales razones por las que un script de Scraping web puede ser lento y hemos examinado varias técnicas para abordar estos problemas utilizando un script de Python de ejemplo. Con solo unos pocos ajustes en la lógica de Scraping web, hemos logrado una mejora de 8 veces en el tiempo de ejecución.
Si bien la optimización manual de la lógica de Scraping web es fundamental para acelerar el proceso de recuperación de datos, el uso de las herramientas adecuadas es igualmente importante. Cuando se trata de sitios dinámicos que requieren soluciones de automatización de navegadores, las cosas pueden complicarse, ya que los navegadores tienden a ser lentos y a consumir muchos recursos.
Para superar estos retos, pruebael Navegador de scraping, una solución totalmente alojada en la nube y diseñada para el scraping. Se integra a la perfección con Puppeteer, Selenium, Playwright y otras herramientas populares de automatización de navegadores. Equipado con un solucionador automático de CAPTCHA y respaldado por una red de proxies residenciales de más de 150 millones de IPs, ofrece una escalabilidad ilimitada para cubrir cualquier necesidad de scraping.
Regístrese ahora y comience su prueba gratuita.