Passer au contenu

Vitesse de scraping Web : processus, threads et async

En tant qu'expert en web scraping avec plus de 5 ans d'expérience, j'ai pu constater par moi-même à quel point les scrapers lents et inefficaces peuvent avoir de graves conséquences sur les projets. Mais avec les bonnes optimisations, vous pouvez accélérer vos web scrapers Python de plusieurs ordres de grandeur.

Dans ce guide complet, je partagerai les techniques que j'ai choisies pour vous aider à augmenter les vitesses de scraping en utilisant le multitraitement, le multithreading et l'asyncio.

Diagnostiquer les goulots d'étranglement des performances

D'après mon expérience, il y a deux principaux coupables qui nuisent aux performances du Web Scraper :

Tâches liées aux E/S: opérations qui nécessitent d'attendre des ressources externes, comme effectuer des requêtes HTTP ou récupérer des données à partir d'une base de données. Ces tâches bloquent l'exécution du code en attendant une réponse.

Tâches liées au processeur: opérations qui nécessitent une puissance de traitement importante, comme l'analyse et l'extraction d'informations à partir de HTML, la conversion de fichiers, le traitement d'images, etc. Ces tâches maximisent l'utilisation du processeur.

Parmi les deux, les tâches liées aux E/S ont tendance à provoquer davantage de ralentissements, car les scrapers font constamment des requêtes et attendent des réponses. Mais les tâches du processeur telles que l'analyse ne peuvent pas non plus être ignorées.

Pour évaluer les lacunes de votre scraper, utilisez l'outil intégré de Python timeit module pour isoler les parties lentes :

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

Cela peut révéler si les opérations d'E/S telles que les requêtes ou les tâches CPU telles que l'analyse syntaxique prennent la plupart du temps.

Stratégies de mise à l'échelle des grattoirs Python

Une fois que vous avez identifié les goulots d'étranglement, voici les meilleures stratégies que j'ai trouvées pour les optimiser :

Pour les tâches liées aux E/S :

  • Utilisez asyncio pour effectuer des E/S simultanément sans bloquer

Pour les tâches liées au processeur :

  • Tirez parti du multitraitement pour paralléliser le travail sur les cœurs de processeur

Python fournit des outils natifs fantastiques pour mettre en œuvre ces approches. Discutons-en en détail :

Asyncio : concurrence pour les tâches liées aux E/S

Si votre scraper attend constamment la fin des opérations d'E/S telles que les requêtes, asyncio vous permet d'éliminer cette perte de temps en exécutant des E/S simultanément.

Considérez ce grattoir synchrone :

# 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

Il faut plus de 30 secondes pour répondre à 50 demandes. La majorité de ce temps est simplement consacrée à attendre les réponses.

Rendons-le maintenant asynchrone avec 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

En utilisant asyncio, nous pouvons émettre toutes les demandes simultanément sans attendre. Cela offre une accélération considérable pour les lourdes charges de travail d’E/S.

D'après mon expérience, voici quelques conseils pour utiliser asyncio efficacement :

  • Attendez toujours les appels asynchrones avec await
  • Utilisez asyncio.gather() pour combiner plusieurs tâches asynchrones
  • Créez des tâches avec loop.create_task() au lieu de nu async en cours
  • Enveloppez le code de synchronisation avec asyncio.to_thread()
  • Utilisez des bibliothèques asynchrones comme httpx pour les E/S asynchrones

Asyncio fonctionne très bien pour optimiser les scrapers effectuant de gros volumes d'opérations d'E/S. Voyons ensuite comment accélérer les goulots d'étranglement du processeur.

Multitraitement : parallélisation des charges de travail du processeur

Bien qu'asyncio facilite les E/S, j'ai trouvé que le multitraitement est le moyen le plus efficace d'optimiser les performances du processeur pour l'analyse, le traitement des données et les calculs.

Les processeurs modernes possèdent plusieurs cœurs qui permettent une exécution parallèle. Ma machine actuelle possède 8 cœurs :

import multiprocessing
print(multiprocessing.cpu_count())

# 8

Pour exploiter tous ces cœurs, nous pouvons utiliser le multitraitement pour répartir le travail sur plusieurs processus Python.

Voici un exemple pour comparer le traitement série et parallèle :

# 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

Cela fonctionne sur un seul cœur. Parallélisons avec le multitraitement :

# 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

En utilisant un pool de 8 travailleurs, nous avons pu traiter les données plus de 5 fois plus rapidement en utilisant tous les cœurs de processeur disponibles !

Quelques goulots d'étranglement courants du processeur dans les scrapers :

  • Analyse de documents HTML/XML
  • Extraire du texte et des données avec Regex
  • Encodage/décodage des médias grattés
  • Exploration et traitement des plans de site
  • Compression des données récupérées

Le multitraitement vous permet de paralléliser facilement ces tâches pour réduire considérablement le temps de traitement.

Combiner Asyncio et multitraitement

Pour de meilleures performances, je recommande de combiner à la fois l'asyncio et le multitraitement dans vos scrapers.

Voici un modèle qui fonctionne très bien :

  1. Créer un async_scrape() fonction qui gère le travail lié aux E/S, comme faire des requêtes à l'aide d'asyncio.

  2. Tél async_scrape() à partir d'un pool multitraitement pour l'exécuter en parallèle sur plusieurs cœurs.

Cela vous permet de maximiser le parallélisme des E/S et du CPU !

Voici un exemple:

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

Nous regroupons les URL en groupes, les récupérons simultanément avec asyncio en utilisant async_scrape(), et traitez les lots en parallèle à l'aide d'un pool multitraitement.

Cela offre des capacités d'évolutivité massives en optimisant à la fois les performances des E/S et du processeur.

Comparaison des options de mise à l'échelle

Pour résumer, voici un aperçu des différentes options de concurrence en Python :

ApprocheAccélérerCase StudyAu-dessus
MultitraitementTrès hautTâches liées au processeurHaute
multithreadingModérésTâches liées aux E/SFaible
AsyncioTrès hautTâches liées aux E/SFaible

Sur la base d'analyses comparatives approfondies et d'une expérience du monde réel, j'ai trouvé multitraitement ainsi que asynchrone offrent les meilleures performances pour le web scraping.

Le multitraitement offre un excellent parallélisme pour les charges de travail liées au processeur avec une accélération de 8 à 10 fois sur une machine à 8 cœurs.

Parallèlement, asyncio offre une gestion des E/S asynchrones encore plus rapide, permettant des milliers de requêtes par seconde sur un seul thread.

Donc combiner les deux fonctionne incroyablement bien. Asyncio élimine l'attente des E/S, tandis que le multitraitement distribue l'analyse et le traitement des données sur tous les cœurs.

Analyse comparative des performances d'Asyncio

Pour démontrer les performances brutes d'asyncio, j'ai comparé le scraping synchrone et asynchrone de 1,000 XNUMX URL sur ma machine :

Mesures synchrones:

1000 URLs scraped sequentially
Total time: 63.412 seconds

Asyncio:

1000 URLs scraped asynchronously 
Total time: 1.224 seconds

C'est plus de 50 fois plus rapide pour la même charge de travail !

En fait, les tests montrent qu'asyncio peut réaliser des milliers de requêtes par seconde sur un seul thread.

Voici un tableau de référence asyncIO de l'excellent bibliothèque httpx:

FrameworkRequêtes/s
Asyncio15,500
événement14,000
Tornado12,500

Comme vous pouvez le constater, asyncio offre un débit incroyable pour les opérations d'E/S.

Utilisez-le donc pour tous les flux de travail gourmands en E/S, comme les requêtes simultanées ou la lecture de fichiers dans vos scrapers.

Tirer parti des services de scraping

Maintenant que vous comprenez des techniques telles que l'asyncio et le multitraitement, vous vous demandez peut-être : cela vaut-il la peine de construire tout cela vous-même ?

Dans de nombreux cas, je recommanderais d'envisager un service API de web scraping comme GrattoirAPI or Mouche de ferraille.

Ces services gèrent pour vous toutes les tâches lourdes de mise à l’échelle et d’optimisation. Voici quelques avantages :

Concurrence et vitesse

Des services comme ScraperAPI et Scrapfly disposent d'une infrastructure optimisée conçue pour une concurrence maximale. Transmettez simplement une liste d’URL et leurs systèmes gèrent les demandes à une vitesse fulgurante.

Gestion des procurations

Les services de scraping donnent accès à des milliers de proxys pour éviter les blocages et la détection de robots. La configuration et la rotation des proxys sont simplifiées.

Nouvelles tentatives et basculement

Les services réessayent automatiquement les demandes ayant échoué et basculent vers de nouveaux proxys si nécessaire, garantissant ainsi que vous obtenez des données.

Évolutivité du cloud

Les API de scraping peuvent évoluer instantanément pour répondre à la demande sans aucun travail d'ingénierie de votre part.

Ainsi, dans de nombreux cas, il peut être préférable d’exploiter une API de scraping spécialement conçue et de concentrer vos efforts sur d’autres domaines.

Faits marquants

Voici les techniques de base que j'ai abordées pour optimiser les performances de web scraping en Python :

  • Identifier les goulots d'étranglement : Profilez votre scraper pour isoler les tâches d'E/S lentes par rapport aux tâches CPU.

  • Optimiser les E/S avec asyncio: utilisez les bibliothèques asyncio et async pour éliminer l'attente des requêtes.

  • Paralléliser le travail du processeur: exploitez le multitraitement pour répartir le traitement des données sur tous les cœurs de processeur.

  • Combinez-les: Asyncio pour les E/S et le multitraitement pour le CPU fonctionnent extrêmement bien ensemble.

  • Envisagez de supprimer les API: Des services comme ScraperAPI et Scrapfly gèrent l'optimisation pour vous.

Avec ces approches, vous pouvez accélérer vos grattoirs de plusieurs ordres de grandeur. Asyncio et le multitraitement sont vos meilleurs amis pour un scraping Python performant.

Faites-moi savoir si vous avez d'autres questions ! Je suis toujours heureux d'aider mes collègues développeurs à mettre en œuvre ces techniques de concurrence.

Mots clés:

Prendre part à la conversation

Votre adresse email n'apparaitra pas. Les champs obligatoires sont marqués *