Meteen naar de inhoud

Webscrapingsnelheid: processen, threads en async

Als webscraping-expert met meer dan 5 jaar ervaring heb ik uit de eerste hand gezien hoe langzame en inefficiënte scrapers een ernstige impact kunnen hebben op projecten. Maar met de juiste optimalisaties kunt u uw Python-webschrapers met ordes van grootte versnellen.

In deze uitgebreide handleiding deel ik de technieken die ik heb geleerd om u te helpen de scrapingsnelheid te verhogen met behulp van multiprocessing, multithreading en asyncio.

Diagnose van de prestatieknelpunten

Uit mijn ervaring zijn er twee primaire boosdoeners die de prestaties van webschrapers teisteren:

I/O-gebonden taken: bewerkingen waarbij op externe bronnen moet worden gewacht, zoals het doen van HTTP-verzoeken of het ophalen van gegevens uit een database. Deze taken blokkeren de uitvoering van code tijdens het wachten op een antwoord.

CPU-gebonden taken: Bewerkingen die uitgebreide verwerkingskracht vereisen, zoals het parseren en extraheren van informatie uit HTML, het converteren van bestanden, beeldverwerking enz. Deze taken maximaliseren het CPU-gebruik.

Van deze twee hebben I/O-gebonden taken de neiging om meer vertragingen te veroorzaken, omdat scrapers voortdurend verzoeken indienen en op antwoorden wachten. Maar CPU-taken zoals parseren kunnen ook niet worden genegeerd.

Om te beoordelen waar uw schraper ontbreekt, gebruikt u de ingebouwde Python timeit module om de langzame delen te isoleren:

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

Hieruit kan blijken of I/O-bewerkingen zoals verzoeken of CPU-taken zoals parseren het grootste deel van de tijd in beslag nemen.

Strategieën voor het schalen van Python-schrapers

Nadat je de knelpunten hebt geïdentificeerd, zijn hier de beste strategieën die ik heb gevonden om ze te optimaliseren:

Voor I/O-gebonden taken:

  • Gebruik asyncio om gelijktijdig I/O uit te voeren zonder te blokkeren

Voor CPU-gebonden taken:

  • Maak gebruik van multiprocessing om werk over CPU-kernen te parallelliseren

Python biedt fantastische native tools om deze benaderingen te implementeren. Laten we ze in detail bespreken:

Asyncio: gelijktijdigheid voor I/O-gebonden taken

Als uw scraper voortdurend wacht op I/O-bewerkingen zoals voltooide verzoeken, kunt u met asyncio deze verspilde tijd elimineren door I/O gelijktijdig uit te voeren.

Overweeg deze synchrone schraper:

# 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

Het duurt meer dan 30 seconden om 50 verzoeken te voltooien. Het grootste deel van deze tijd wacht nutteloos op reacties.

Laten we het nu asynchroon maken met 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

Door asyncio te gebruiken, kunnen we alle verzoeken tegelijkertijd indienen zonder te wachten. Dit zorgt voor een enorme snelheid bij zware I/O-workloads.

In mijn ervaring zijn hier enkele tips om asyncio effectief te gebruiken:

  • Wacht altijd op asynchrone oproepen met await
  • Te gebruiken asyncio.gather() om meerdere asynchrone taken te combineren
  • Maak taken aan met loop.create_task() in plaats van kaal async gesprekken
  • Wikkel synchronisatiecode in met asyncio.to_thread()
  • Gebruik asynchrone bibliotheken zoals httpx voor asynchrone I/O

Asyncio werkt prima voor het optimaliseren van scrapers die grote hoeveelheden I/O-bewerkingen uitvoeren. Laten we vervolgens bespreken hoe we CPU-knelpunten kunnen versnellen.

Multiprocessing: CPU-workloads parallelliseren

Hoewel asyncio helpt bij I/O, heb ik ontdekt dat multiprocessing de meest effectieve manier is om de CPU-prestaties te optimaliseren voor parseren, gegevensverwerking en berekeningen.

Moderne CPU's hebben meerdere kernen die parallelle uitvoering mogelijk maken. Mijn huidige machine heeft 8 kernen:

import multiprocessing
print(multiprocessing.cpu_count())

# 8

Om al deze kernen te benutten, kunnen we multiprocessing gebruiken om het werk over meerdere Python-processen te verspreiden.

Hier is een voorbeeld om seriële en parallelle verwerking te vergelijken:

# 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

Deze draait op slechts 1 kern. Laten we parallelliseren met 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

Door een pool van 8 werknemers te gebruiken, konden we de gegevens ruim 5x sneller verwerken door alle beschikbare CPU-kernen te gebruiken!

Enkele veelvoorkomende CPU-knelpunten in scrapers:

  • Parseren van HTML/XML-documenten
  • Tekst en gegevens extraheren met Regex
  • Coderen/decoderen van geschraapte media
  • Sitemaps crawlen en verwerken
  • Geschrapte gegevens comprimeren

Dankzij multiprocessing kunt u deze taken eenvoudig parallelliseren, waardoor de verwerkingstijd aanzienlijk wordt verkort.

Combineer Asyncio en Multiprocessing

Voor de beste prestaties raad ik aan om zowel asyncio als multiprocessing in uw schrapers te combineren.

Hier is een sjabloon dat heel goed werkt:

  1. Maak een async_scrape() functie die I/O-gebonden werk afhandelt, zoals het doen van verzoeken met behulp van asyncio.

  2. Bel async_scrape() vanuit een multiprocessingpool om deze parallel over meerdere cores te laten draaien.

Hierdoor kunt u zowel de I/O- als de CPU-parallelliteit maximaliseren!

Hier is een voorbeeld:

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

We bundelen URL's in groepen en schrapen ze gelijktijdig met asyncio-gebruik async_scrape()en verwerk de batches parallel met behulp van een multiprocessingpool.

Dit biedt enorme schaalmogelijkheden door zowel I/O- als CPU-prestaties te optimaliseren.

Schaalopties vergelijken

Samenvattend volgt hier een overzicht van de verschillende gelijktijdigheidsopties in Python:

AanpakVersnellenUse CaseBoven het hoofd
multiprocessingHeel hoogCPU-gebonden takenHoge
multithreadingMatigI/O-gebonden takenLaag
AsynchroonHeel hoogI/O-gebonden takenLaag

Gebaseerd op uitgebreide benchmarking en praktijkervaring, heb ik ontdekt multiverwerking en asynchrone bieden de beste prestaties voor webscrapen.

Multiprocessing levert uitstekend parallellisme voor CPU-gebonden werklasten met een snelheid van 8x-10x op een 8-core machine.

Ondertussen zorgt asyncio voor een nog snellere asynchrone I/O-afhandeling, waardoor duizenden verzoeken per seconde op één thread mogelijk zijn.

Het combineren van beide werkt dus ongelooflijk goed. Asyncio elimineert het wachten op I/O, terwijl multiprocessing de sparsing en gegevensverwerking over alle cores verdeelt.

Benchmarking van Asyncio-prestaties

Om de ruwe prestaties van asyncio aan te tonen, heb ik het synchroon versus async schrapen van 1,000 URL's op mijn computer vergeleken:

Gelijktijdig:

1000 URLs scraped sequentially
Total time: 63.412 seconds

Asynchroon:

1000 URLs scraped asynchronously 
Total time: 1.224 seconds

Dat is ruim 50x sneller voor dezelfde werklast!

Uit benchmarks blijkt zelfs dat asyncio duizenden verzoeken per seconde op één thread kan bereiken.

Hier is een asyncIO-benchmarktabel van Excellent httpx-bibliotheek:

AchtergrondVerzoeken/sec
Asynchroon15,500
geven14,000
Tornado12,500

Zoals u kunt zien, biedt asyncio een ongelooflijke doorvoer voor I/O-bewerkingen.

Gebruik het dus voor alle I/O-zware workflows, zoals het gelijktijdig indienen van verzoeken of het lezen van bestanden in uw scrapers.

Gebruik maken van scrapservices

Nu je technieken als asyncio en multiprocessing begrijpt, vraag je je misschien af: is het de moeite waard om dit allemaal zelf te bouwen?

In veel gevallen raad ik aan om een ​​webscraping-API-service zoals SchraperAPI or Scrapvlieg.

Deze services verzorgen al het zware werk op het gebied van schaalvergroting en optimalisatie voor u. Hier zijn enkele voordelen:

Gelijktijdigheid en snelheid

Services zoals ScraperAPI en Scrapfly hebben een geoptimaliseerde infrastructuur die is ontworpen voor maximale gelijktijdigheid. Geef gewoon een lijst met URL's door, en hun systemen kunnen deze met enorme snelheid opvragen.

Proxybeheer

Scrapingservices bieden toegang tot duizenden proxy's om blokkades en botdetectie te voorkomen. Het configureren en roteren van proxy's wordt weggeabstraheerd.

Nieuwe pogingen en failover

De services proberen mislukte aanvragen automatisch opnieuw en schakelen indien nodig over op nieuwe proxy's, zodat u verzekerd bent van gegevens.

Schaalbaarheid van de cloud

Scraping-API's kunnen direct worden geschaald om aan de vraag te voldoen, zonder enig technisch werk aan uw kant.

In veel gevallen kan het dus de voorkeur verdienen om gebruik te maken van een speciaal gebouwde scraping-API en uw inspanningen op andere gebieden te richten.

Key Takeaways

Hier zijn de kerntechnieken die ik heb behandeld voor het optimaliseren van de webscraping-prestaties in Python:

  • Identificeer knelpunten: Profileer uw scraper om trage I/O- en CPU-taken te isoleren.

  • Optimaliseer I/O met asyncio: Gebruik asyncio- en asynchrone bibliotheken om het wachten op aanvragen te elimineren.

  • Parallelliseer CPU-werk: Maak gebruik van multiprocessing om de gegevensverwerking over alle CPU-kernen te verdelen.

  • Combineer ze: Asyncio voor I/O en multiprocessing voor CPU werken uitstekend samen.

  • Overweeg om API's te schrappen: Services zoals ScraperAPI en Scrapfly verzorgen de optimalisatie voor u.

Met deze benaderingen kunt u uw schrapers met ordes van grootte versnellen. Asyncio en multiprocessing zijn je beste vrienden voor performant Python-scrapen.

Laat het me weten als je nog vragen hebt! Ik help collega-ontwikkelaars altijd graag bij het implementeren van deze gelijktijdigheidstechnieken.

Tags:

Doe mee aan het gesprek

Uw e-mailadres wordt niet gepubliceerd. Verplichte velden zijn gemarkeerd *