Ir al contenido

Velocidad de raspado web: procesos, subprocesos y asíncrono

Como experto en web scraping con más de 5 años de experiencia, he visto de primera mano cómo los scrapers lentos e ineficientes pueden afectar gravemente a los proyectos. Pero con las optimizaciones adecuadas, puedes acelerar tus web scrapers de Python en órdenes de magnitud.

En esta guía completa, compartiré las técnicas que he aprendido para ayudarte a aumentar la velocidad de raspado mediante multiprocesamiento, subprocesos múltiples y asyncio.

Diagnóstico de los cuellos de botella en el rendimiento

Según mi experiencia, hay dos culpables principales que afectan el rendimiento del web scraper:

Tareas vinculadas a E/S: operaciones que requieren esperar recursos externos, como realizar solicitudes HTTP o recuperar datos de una base de datos. Estas tareas bloquean la ejecución del código mientras esperan una respuesta.

Tareas vinculadas a la CPU: Operaciones que requieren una gran potencia de procesamiento, como analizar y extraer información de HTML, convertir archivos, procesar imágenes, etc. Estas tareas maximizan el uso de la CPU.

De las dos, las tareas vinculadas a E/S tienden a provocar más ralentizaciones, ya que los raspadores realizan solicitudes y esperan respuestas constantemente. Pero tampoco se pueden ignorar las tareas de la CPU como el análisis.

Para evaluar dónde le falta su raspador, use la función incorporada de Python timeit módulo para aislar las partes lentas:

import timeit

# Time a request

timeit.timeit(lambda: requests.get("http://example.com"), number=50)
# 31.23 seconds

# Time parsing
timeit.timeit(lambda: parse_html(content), number=50)  
# 22.12 seconds

Esto puede revelar si las operaciones de E/S, como solicitudes o tareas de CPU, como el análisis, ocupan la mayor parte del tiempo.

Estrategias para escalar raspadores de Python

Una vez que haya identificado los cuellos de botella, estas son las mejores estrategias que he encontrado para optimizarlos:

Para tareas vinculadas a E/S:

  • Utilice asyncio para realizar E/S simultáneamente sin bloquear

Para tareas vinculadas a la CPU:

  • Aproveche el multiprocesamiento para paralelizar el trabajo entre núcleos de CPU

Python proporciona fantásticas herramientas nativas para implementar estos enfoques. Analicemoslos en detalle:

Asyncio: simultaneidad para tareas vinculadas a E/S

Si su raspador está esperando constantemente que se completen operaciones de E/S, como solicitudes, asyncio le permite eliminar esta pérdida de tiempo ejecutando E/S simultáneamente.

Considere este raspador sincrónico:

# Synchronous Scraper

import requests
import time

start = time.time()

for _ in range(50):
  requests.get("http://example.com")

end = time.time()  
print(f"Time taken: {end - start:.2f} secs")

# Time taken: 31.14 secs

Se necesitan más de 30 segundos para completar 50 solicitudes. La mayor parte de este tiempo se dedica a esperar respuestas.

Ahora hagámoslo asíncrono con asyncio:

# Asyncio Scraper

import asyncio
import httpx
import time

async def asyn_get(url):
  async with httpx.AsyncClient() as client:
    return await client.get(url)

start = time.time()

loop = asyncio.get_event_loop()
tasks = [loop.create_task(asyn_get("http://example.com")) for _ in range(50)]
wait_tasks = asyncio.wait(tasks)
loop.run_until_complete(wait_tasks)

end = time.time()
print(f"Time taken: {end - start:.2f} secs")

# Time taken: 1.14 secs

Al usar asyncio, podemos emitir todas las solicitudes simultáneamente sin esperar. Esto proporciona una enorme velocidad para cargas de trabajo pesadas de E/S.

En mi experiencia, aquí hay algunos consejos para usar asyncio de manera efectiva:

  • Espere siempre llamadas asíncronas con await
  • Uso asyncio.gather() para combinar múltiples tareas asíncronas
  • Crear tareas con loop.create_task() en lugar de desnudo async llamadas
  • Ajustar el código de sincronización con asyncio.to_thread()
  • Utilice bibliotecas asíncronas como httpx para E/S asíncronas

Asyncio funciona muy bien para optimizar scrapers que realizan grandes volúmenes de operaciones de E/S. A continuación, analicemos cómo acelerar los cuellos de botella de la CPU.

Multiprocesamiento: paralelización de cargas de trabajo de CPU

Si bien asyncio ayuda con la E/S, descubrí que el multiprocesamiento es la forma más efectiva de optimizar el rendimiento de la CPU para el análisis, el procesamiento de datos y los cálculos.

Las CPU modernas tienen múltiples núcleos que permiten la ejecución en paralelo. Mi máquina actual tiene 8 núcleos:

import multiprocessing
print(multiprocessing.cpu_count())

# 8

Para aprovechar todos estos núcleos, podemos utilizar el multiprocesamiento para distribuir el trabajo entre múltiples procesos de Python.

A continuación se muestra un ejemplo para comparar el procesamiento en serie con el paralelo:

# Serial Processing

import time
from slugify import slugify

start = time.time()

articles = ["Article One","Article Two",..."Article One Thousand"]

for title in articles:
  slugify(title)

print(f"Serial time: {time.time() - start:.2f} secs")

# Serial time: 5.14 sec

Esto se ejecuta en solo 1 núcleo. Paralelicemos con el multiprocesamiento:

# Parallel Processing 

from multiprocessing import Pool
import time
from slugify import slugify

start = time.time()

with Pool(8) as p:
  p.map(slugify, articles)

print(f"Parallel time: {time.time() - start:.2f} secs")

# Parallel time: 1.04 secs

Al utilizar un grupo de 8 trabajadores, pudimos procesar los datos 5 veces más rápido utilizando todos los núcleos de CPU disponibles.

Algunos cuellos de botella comunes de la CPU en los raspadores:

  • Análisis de documentos HTML/XML
  • Extraer texto y datos con Regex
  • Codificación/decodificación de medios raspados
  • Rastreo y procesamiento de mapas de sitio
  • Comprimir datos extraídos

El multiprocesamiento le permite paralelizar fácilmente estas tareas para reducir significativamente el tiempo de procesamiento.

Combinando Asyncio y Multiprocesamiento

Para obtener el mejor rendimiento, recomiendo combinar asincio y multiprocesamiento en sus scrapers.

Aquí tienes una plantilla que funciona muy bien:

  1. Crear una async_scrape() función que maneja el trabajo vinculado a E/S, como realizar solicitudes usando asyncio.

  2. Llamar async_scrape() desde un grupo de multiprocesamiento para ejecutarlo en paralelo en varios núcleos.

¡Esto le permite maximizar el paralelismo de E/S y CPU!

Aquí hay un ejemplo:

import asyncio
from multiprocessing import Pool
import httpx
import time

async def async_scrape(urls):

  async with httpx.AsyncClient() as client:

    tasks = [client.get(url) for url in urls]
    results = await asyncio.gather(*tasks)

    # CPU-heavy processing
    for data in results:
      analyze_data(data)

def multiproc_wrapper(urls):
  asyncio.run(async_scrape(urls))

if __name__ == "__main__":

  urls = [# List of urls

  start = time.time()  

  with Pool(8) as p:
    p.map(multiproc_wrapper, batched_urls)

  print(f"Total time: {time.time() - start:.2f} secs")

Agrupamos las URL en grupos, las eliminamos simultáneamente con asyncio usando async_scrape()y procesar los lotes en paralelo utilizando un grupo de multiprocesamiento.

Esto proporciona capacidades de escalamiento masivas al optimizar el rendimiento de E/S y de la CPU.

Comparación de opciones de escala

Para resumir, aquí hay una descripción general de las diversas opciones de concurrencia en Python:

EnfoqueAcelerarCaso de usoGastos generales
MultiprocesamientoMuy AltaTareas vinculadas a la CPUAlta
multihiloModeradoTareas vinculadas a E/SBaja
asincioMuy AltaTareas vinculadas a E/SBaja

Basado en una extensa evaluación comparativa y experiencia en el mundo real, he encontrado multiprocesamiento y asíncio Proporciona el mejor rendimiento para el web scraping.

El multiprocesamiento ofrece un excelente paralelismo para cargas de trabajo vinculadas a la CPU con una aceleración de 8 a 10 veces en una máquina de 8 núcleos.

Mientras tanto, asyncio proporciona un manejo de E/S asíncrono aún más rápido, lo que permite miles de solicitudes por segundo en un solo subproceso.

Entonces combinar ambos funciona increíblemente bien. Asyncio elimina la espera en E/S, mientras que el multiprocesamiento distribuye la dispersión y el procesamiento de datos en todos los núcleos.

Evaluación comparativa del rendimiento de Asyncio

Para demostrar el rendimiento bruto de asyncio, comparé el scraping síncrono y asíncrono de 1,000 URL en mi máquina:

Sincrónico:

1000 URLs scraped sequentially
Total time: 63.412 seconds

asincio:

1000 URLs scraped asynchronously 
Total time: 1.224 seconds

¡Eso es más de 50 veces más rápido para la misma carga de trabajo!

De hecho, los puntos de referencia muestran que asyncio puede lograr miles de solicitudes por segundo en un solo hilo.

Aquí hay una tabla de referencia asyncIO del excelente biblioteca httpx:

Marco conceptualSolicitudes/s
asincio15,500
evento14,000
Tornado12,500

Como puede ver, asyncio proporciona un rendimiento increíble para las operaciones de E/S.

Utilícelo para cualquier flujo de trabajo con gran cantidad de E/S, como realizar solicitudes simultáneas o leer archivos en sus scrapers.

Aprovechando los servicios de scraping

Ahora que comprende técnicas como asincio y multiprocesamiento, es posible que se pregunte: ¿vale la pena desarrollar todo esto usted mismo?

En muchos casos, recomendaría considerar un servicio API de raspado web como RaspadorAPI or mosca chatarra.

Estos servicios se encargan de todo el trabajo pesado de escalado y optimización por usted. Aquí hay algunos beneficios:

Concurrencia y velocidad

Servicios como ScraperAPI y Scrapfly tienen una infraestructura optimizada diseñada para una máxima concurrencia. Simplemente pase una lista de URL y sus sistemas se encargarán de solicitarlas a velocidades vertiginosas.

Gestión de proxy

Los servicios de scraping brindan acceso a miles de servidores proxy para evitar bloqueos y detección de bots. La configuración y rotación de servidores proxy se abstrae.

Reintentos y conmutación por error

Los servicios reintentan automáticamente las solicitudes fallidas y cambian a nuevos servidores proxy según sea necesario, lo que garantiza que usted obtenga datos.

Escalabilidad en la nube

Las API de scraping pueden escalarse instantáneamente para satisfacer la demanda sin ningún trabajo de ingeniería de su parte.

Por lo tanto, en muchos casos, puede ser preferible aprovechar una API de raspado especialmente diseñada y centrar sus esfuerzos en otras áreas.

Puntos clave

Estas son las técnicas principales que cubrí para optimizar el rendimiento del web scraping en Python:

  • Identificar cuellos de botella: Perfile su raspador para aislar las E/S lentas frente a las tareas de la CPU.

  • Optimizar E/S con asyncio: Utilice bibliotecas asyncio y async para eliminar la espera de solicitudes.

  • Paralelizar el trabajo de la CPU: aproveche el multiprocesamiento para distribuir el procesamiento de datos en todos los núcleos de la CPU.

  • combínalos: Asyncio para E/S y multiprocesamiento para CPU funcionan muy bien juntos.

  • Considere la posibilidad de extraer API: Servicios como ScraperAPI y Scrapfly se encargan de la optimización por usted.

Con estos enfoques, puedes acelerar tus raspadores en órdenes de magnitud. Asyncio y el multiprocesamiento son tus mejores amigos para el scraping de Python de alto rendimiento.

¡Déjame saber si tienes alguna otra pregunta! Siempre estaré feliz de ayudar a otros desarrolladores a implementar estas técnicas de concurrencia.

Tags:

Únase a la conversación

Su dirección de correo electrónico no será publicada. Las areas obligatorias están marcadas como requeridas *