Salta al contenuto

Velocità di web scraping: processi, thread e asincrono

In qualità di esperto di web scraping con oltre 5 anni di esperienza, ho visto in prima persona quanto gli scraper lenti e inefficienti possano avere un impatto grave sui progetti. Ma con le giuste ottimizzazioni, puoi velocizzare i tuoi web scraper Python di ordini di grandezza.

In questa guida completa, condividerò le tecniche che ho raccolto per aiutarti ad aumentare la velocità di scraping utilizzando multiprocessing, multithreading e asyncio.

Diagnosi dei colli di bottiglia delle prestazioni

Dalla mia esperienza, ci sono due principali colpevoli che affliggono le prestazioni del web scraper:

Attività associate a I/O: operazioni che richiedono l'attesa di risorse esterne come l'esecuzione di richieste HTTP o il recupero di dati da un database. Queste attività bloccano l'esecuzione del codice durante l'attesa di una risposta.

Attività legate alla CPU: operazioni che richiedono una potenza di elaborazione estesa come l'analisi e l'estrazione di informazioni da HTML, la conversione di file, l'elaborazione di immagini ecc. Queste attività massimizzano l'utilizzo della CPU.

Delle due, le attività legate all'I/O tendono a causare maggiori rallentamenti poiché gli scraper effettuano costantemente richieste e attendono risposte. Ma neanche le attività della CPU come l'analisi possono essere ignorate.

Per valutare dove manca il tuo raschietto, usa il built-in di Python timeit modulo per isolare le parti lente:

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

Ciò può rivelare se le operazioni di I/O come le richieste o le attività della CPU come l'analisi occupano la maggior parte del tempo.

Strategie per ridimensionare gli scraper Python

Una volta identificati i colli di bottiglia, ecco le migliori strategie che ho trovato per ottimizzarli:

Per attività legate a I/O:

  • Utilizza asyncio per eseguire I/O contemporaneamente senza blocchi

Per le attività legate alla CPU:

  • Sfrutta il multiprocessing per parallelizzare il lavoro tra i core della CPU

Python fornisce fantastici strumenti nativi per implementare questi approcci. Discutiamoli in dettaglio:

Asyncio: concorrenza per attività legate a I/O

Se il tuo scraper è costantemente in attesa del completamento di operazioni I/O come le richieste, asyncio ti consente di eliminare questo tempo sprecato eseguendo I/O contemporaneamente.

Considera questo raschiatore sincrono:

# 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

Sono necessari più di 30 secondi per completare 50 richieste. La maggior parte di questo tempo è semplicemente in attesa di risposte.

Ora rendiamolo asincrono 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

Utilizzando asyncio, possiamo emettere tutte le richieste contemporaneamente senza attendere. Ciò fornisce un'enorme accelerazione per carichi di lavoro pesanti di I/O.

Nella mia esperienza, ecco alcuni suggerimenti per utilizzare asyncio in modo efficace:

  • Attendi sempre le chiamate asincrone con await
  • Usa il asyncio.gather() per combinare più attività asincrone
  • Crea attività con loop.create_task() invece che nudo async chiamate
  • Avvolgi il codice di sincronizzazione con asyncio.to_thread()
  • Utilizza librerie asincrone come httpx per I/O asincrono

Asyncio è ottimo per ottimizzare gli scraper che eseguono grandi volumi di operazioni I/O. Successivamente, discutiamo su come velocizzare i colli di bottiglia della CPU.

Multiprocessing: parallelizzazione dei carichi di lavoro della CPU

Sebbene asyncio aiuti con l'I/O, ho scoperto che il multiprocessing è il modo più efficace per ottimizzare le prestazioni della CPU per l'analisi, l'elaborazione dei dati e i calcoli.

Le CPU moderne hanno più core che consentono l'esecuzione parallela. La mia macchina attuale ha 8 core:

import multiprocessing
print(multiprocessing.cpu_count())

# 8

Per sfruttare tutti questi core, possiamo utilizzare il multiprocessing per distribuire il lavoro su più processi Python.

Ecco un esempio per confrontare l'elaborazione seriale e parallela:

# 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

Funziona su solo 1 core. Parallelizziamo con il multiprocessing:

# 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

Utilizzando un pool di 8 lavoratori, siamo stati in grado di elaborare i dati oltre 5 volte più velocemente utilizzando tutti i core della CPU disponibili!

Alcuni colli di bottiglia comuni della CPU negli scraper:

  • Analisi di documenti HTML/XML
  • Estrazione di testo e dati con Regex
  • Codifica/decodifica di supporti raschiati
  • Scansione ed elaborazione delle Sitemap
  • Compressione dei dati raschiati

Il multiprocessing consente di parallelizzare facilmente queste attività per ridurre significativamente i tempi di elaborazione.

Combinazione di Asyncio e Multiprocessing

Per ottenere le migliori prestazioni, ti consiglio di combinare sia asyncio che multiprocessing nei tuoi scraper.

Ecco un modello che funziona molto bene:

  1. Creare un async_scrape() funzione che gestisce il lavoro legato all'I/O come effettuare richieste utilizzando asyncio.

  2. Call async_scrape() da un pool multiprocessore per eseguirlo in parallelo su più core.

Ciò consente di massimizzare il parallelismo sia dell'I/O che della CPU!

Ecco un esempio:

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")

Raggruppiamo gli URL in gruppi, li raccogliamo contemporaneamente all'utilizzo di asyncio async_scrape()ed elaborare i batch in parallelo utilizzando un pool multiprocessing.

Ciò fornisce enormi capacità di scalabilità ottimizzando sia le prestazioni di I/O che della CPU.

Confronto delle opzioni di ridimensionamento

Per riassumere, ecco una panoramica delle varie opzioni di concorrenza in Python:

ApproccioSpeedupUsa casoCosti indiretti
multiprocessingMolto altoAttività legate alla CPUAlta
multithreadingModerareAttività legate all'I/OBasso
AsincioMolto altoAttività legate all'I/OBasso

Sulla base di un'ampia analisi comparativa e dell'esperienza del mondo reale, ho trovato multiprocessing ed asincio fornire le migliori prestazioni per il web scraping.

Il multiprocessing offre un eccellente parallelismo per carichi di lavoro legati alla CPU con velocità 8x-10x su una macchina a 8 core.

Nel frattempo, asyncio fornisce una gestione I/O asincrona ancora più veloce, consentendo migliaia di richieste al secondo su un singolo thread.

Quindi combinare entrambi funziona incredibilmente bene. Asyncio elimina l'attesa sull'I/O, mentre il multiprocessing distribuisce la ripartizione e l'elaborazione dei dati su tutti i core.

Benchmarking delle prestazioni Asyncio

Per dimostrare le prestazioni grezze di asyncio, ho confrontato lo scraping sincrono e asincrono di 1,000 URL sul mio computer:

Sincrono:

1000 URLs scraped sequentially
Total time: 63.412 seconds

Asincio:

1000 URLs scraped asynchronously 
Total time: 1.224 seconds

È oltre 50 volte più veloce per lo stesso carico di lavoro!

In effetti, i benchmark mostrano che asyncio può raggiungere migliaia di richieste al secondo su un singolo thread.

Ecco una tabella di benchmark asyncIO dall'eccellente libreria httpx:

ContestoRichieste/sec
Asincio15,500
gevent14,000
Tornado12,500

Come puoi vedere, asyncio fornisce un throughput incredibile per le operazioni di I/O.

Utilizzalo quindi per qualsiasi flusso di lavoro con I/O pesante, come effettuare richieste simultanee o leggere file nei tuoi scraper.

Sfruttare i servizi di raschiatura

Ora che conosci tecniche come asyncio e multiprocessing, potresti chiederti: vale la pena costruire tutto questo da solo?

In molti casi, consiglierei di prendere in considerazione un servizio API di web scraping come API raschietto or Scrapfly.

Questi servizi gestiscono per te tutto il lavoro pesante di ridimensionamento e ottimizzazione. Ecco alcuni vantaggi:

Concorrenza e velocità

Servizi come ScraperAPI e Scrapfly dispongono di un'infrastruttura ottimizzata progettata per la massima concorrenza. Basta passare un elenco di URL e i loro sistemi gestiranno la richiesta a velocità incredibile.

Gestione delle deleghe

I servizi di scraping forniscono l'accesso a migliaia di proxy per evitare blocchi e rilevamento di bot. La configurazione e la rotazione dei proxy vengono eliminate.

Nuovi tentativi e failover

I servizi ritentano automaticamente le richieste non riuscite e passano a nuovi proxy secondo necessità, assicurandoti di ottenere i dati.

Scalabilità del cloud

Le API di scraping possono scalare istantaneamente per soddisfare la domanda senza alcun lavoro di ingegneria da parte tua.

Pertanto, in molti casi, potrebbe essere preferibile sfruttare un'API di scraping appositamente creata e concentrare i propri sforzi su altre aree.

Punti chiave

Ecco le tecniche principali che ho trattato per ottimizzare le prestazioni di web scraping in Python:

  • Identificare i colli di bottiglia: profila il tuo scraper per isolare le attività I/O lente rispetto alle attività CPU.

  • Ottimizza I/O con asincio: utilizza le librerie asincrone e asincrone per eliminare l'attesa delle richieste.

  • Parallelizza il lavoro della CPU: Sfrutta il multiprocessing per distribuire l'elaborazione dei dati su tutti i core della CPU.

  • Combinali: Asyncio per I/O e multiprocessing per CPU funzionano molto bene insieme.

  • Prendi in considerazione lo scraping delle API: Servizi come ScraperAPI e Scrapfly gestiscono l'ottimizzazione per te.

Con questi approcci, puoi accelerare i tuoi raschiatori di ordini di grandezza. Asyncio e multiprocessing sono i tuoi migliori amici per uno scraping Python performante.

Fatemi sapere se avete altre domande! Sono sempre felice di aiutare gli altri sviluppatori a implementare queste tecniche di concorrenza.

Tag:

Partecipa alla conversazione

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati con *