コンテンツにスキップ

Web スクレイピングの速度: プロセス、スレッド、非同期

5 年以上の経験を持つ Web スクレイピングの専門家として、私は遅くて非効率なスクレイパーがプロジェクトに重大な影響を与える可能性があることをこの目で見てきました。ただし、適切な最適化を行うと、Python Web スクレイパーを桁違いに高速化できます。

この包括的なガイドでは、マルチプロセッシング、マルチスレッド、および非同期を使用してスクレイピング速度を向上させるために私がピックアップしたテクニックを共有します。

パフォーマンスのボトルネックを診断する

私の経験から言えば、Web スクレイパーのパフォーマンスを悩ませる主な原因は 2 つあります。

I/O バインドされたタスク: HTTP リクエストの作成やデータベースからのデータのフェッチなど、外部リソースでの待機が必要な操作。これらのタスクは、応答を待機している間のコードの実行をブロックします。

CPU バウンドタスク: HTML からの情報の解析と抽出、ファイルの変換、画像処理など、広範な処理能力を必要とする操作。これらのタスクは CPU 使用率を最大化します。

この 2 つのうち、I/O バウンド タスクは、スクレイパーが常にリクエストを作成し、応答を待機しているため、速度がさらに遅くなる傾向があります。ただし、解析などの CPU タスクも無視できません。

スクレイパーにどこが欠けているかを評価するには、Python の組み込み機能を使用します。 timeit 遅い部分を分離するモジュール:

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

これにより、リクエストなどの I/O 操作がほとんどの時間を占めているか、解析などの CPU タスクが大半の時間を占めているかが明らかになります。

Python スクレーパーをスケーリングするための戦略

ボトルネックを特定したら、ボトルネックを最適化するために私が見つけた最良の戦略を以下に示します。

I/O バウンド タスクの場合:

  • asyncio を使用してブロックせずに I/O を同時に実行する

CPU バウンドタスクの場合:

  • マルチプロセッシングを活用して CPU コア間で作業を並列化する

Python は、これらのアプローチを実装するための素晴らしいネイティブ ツールを提供します。それらについて詳しく説明しましょう。

Asyncio: I/O バウンドタスクの同時実行性

スクレイパーがリクエストなどの I/O 操作の完了を常に待機している場合、asyncio を使用すると、I/O を同時に実行することでこの無駄な時間を排除できます。

この同期スクレーパーについて考えてみましょう。

# 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

30 件のリクエストを完了するには 50 秒以上かかります。この時間の大部分は、ただ何もせずに応答を待っているだけです。

次に、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

asyncio を使用すると、すべてのリクエストを待たずに同時に発行できます。これにより、I/O 負荷の高いワークロードが大幅に高速化されます。

私の経験から、asyncio を効果的に使用するためのヒントをいくつか紹介します。

  • 常に非同期呼び出しを待機します await
  •   asyncio.gather() 複数の非同期タスクを結合するには
  • タスクを作成する loop.create_task() 裸の代わりに async 呼び出し
  • 同期コードをラップする asyncio.to_thread()
  • 非同期 I/O には httpx などの非同期ライブラリを使用する

Asyncio は、大量の I/O 操作を実行するスクレーパーの最適化に最適です。次に、CPU ボトルネックを高速化する方法について説明します。

マルチプロセッシング: CPU ワークロードの並列化

asyncio は I/O に役立ちますが、解析、データ処理、計算の CPU パフォーマンスを最適化するにはマルチプロセッシングが最も効果的な方法であることがわかりました。

最新の CPU には、並列実行を可能にする複数のコアが搭載されています。現在のマシンには 8 コアがあります。

import multiprocessing
print(multiprocessing.cpu_count())

# 8

これらすべてのコアを活用するために、マルチプロセッシングを使用して複数の Python プロセスに作業を分散できます。

シリアル処理と並列処理を比較する例を次に示します。

# 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

これは 1 コアのみで実行されます。マルチプロセッシングを使って並列化してみましょう。

# 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

8 つのワーカーのプールを使用することで、利用可能なすべての CPU コアを利用してデータを 5 倍以上高速に処理することができました。

スクレーパーにおける一般的な CPU ボトルネックのいくつかは次のとおりです。

  • HTML/XMLドキュメントの解析
  • Regex を使用したテキストとデータの抽出
  • スクレイピングされたメディアのエンコード/デコード
  • サイトマップのクロールと処理
  • スクレイピングされたデータの圧縮

マルチプロセッシングを使用すると、これらのタスクを簡単に並列化して、処理時間を大幅に短縮できます。

Asyncio とマルチプロセッシングの組み合わせ

最高のパフォーマンスを得るには、スクレイパーで asyncio とマルチプロセッシングの両方を組み合わせることをお勧めします。

非常にうまく機能するテンプレートを次に示します。

  1. 作る async_scrape() asyncio を使用したリクエストの作成など、I/O バウンドの作業を処理する関数。

  2. コー​​ル async_scrape() マルチプロセッシング プールから取得して、複数のコア間で並列実行します。

これにより、I/O と CPU の両方の並列処理を最大化できます。

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

URL をグループにまとめて、asyncio を使用して同時にスクレイピングします。 async_scrape()、マルチ処理プールを使用してバッチを並列処理します。

これにより、I/O と CPU のパフォーマンスの両方を最適化することにより、大規模なスケーリング機能が提供されます。

スケーリングオプションの比較

要約すると、Python のさまざまな同時実行オプションの概要を以下に示します。

アプローチスピードアップUse Caseオーバーヘッド
マルチプロセッシングすごく高いCPU バウンドのタスクハイ
マルチスレッド適度なI/Oバウンドのタスクロー
アシンシオすごく高いI/Oバウンドのタスクロー

広範なベンチマークと実際の経験に基づいて、私は次のことを発見しました。 マルチプロセッシング & 非同期 Web スクレイピングに最高のパフォーマンスを提供します。

マルチプロセッシングは、8 コア マシンで 10 倍から 8 倍の速度向上を実現し、CPU バウンドのワークロードに優れた並列処理を実現します。

一方、asyncio はさらに高速な非同期 I/O 処理を提供し、単一スレッドで 1 秒あたり数千のリクエストを許可します。

したがって、両方を組み合わせると非常にうまく機能します。 Asyncio は I/O での待機を排除し、マルチプロセッシングによりスパーシングとデータ処理をすべてのコアに分散します。

Asyncio パフォーマンスのベンチマーク

asyncio の生のパフォーマンスを実証するために、私のマシン上で 1,000 個の URL の同期スクレイピングと非同期スクレイピングをベンチマークしました。

同期:

1000 URLs scraped sequentially
Total time: 63.412 seconds

アシンシオ:

1000 URLs scraped asynchronously 
Total time: 1.224 seconds

同じワークロードであれば 50 倍以上高速です。

実際、ベンチマークでは、asyncio が単一スレッドで 1 秒あたり数千のリクエストを達成できることが示されています。

これは、優れた Web サイトからの asyncIO ベンチマーク テーブルです。 httpx ライブラリ:

フレームワークリクエスト/秒
アシンシオ15,500
げんき14,000
竜巻12,500

ご覧のとおり、asyncio は I/O 操作に驚異的なスループットを提供します。

そのため、同時リクエストの作成やスクレイパーでのファイルの読み取りなど、I/O を多く使用するワークフローにこれを利用してください。

スクレイピングサービスの活用

asyncio やマルチプロセッシングなどのテクニックを理解したところで、「これをすべて自分で構築する価値があるのか​​?」と疑問に思うかもしれません。

多くの場合、次のような Web スクレイピング API サービスを検討することをお勧めします。 スクレイパーAPI or スクラップフライ.

これらのサービスは、スケーリングと最適化という面倒な作業をすべて処理します。以下にいくつかの利点があります。

同時実行性と速度

ScraperAPI や Scrapfly などのサービスは、同時実行性を最大化するように設計された最適化されたインフラストラクチャを備えています。 URL のリストを渡すだけで、システムはそれらのリクエストを驚異的な速度で処理します。

プロキシ管理

スクレイピング サービスは、ブロックやボットの検出を回避するために、何千ものプロキシへのアクセスを提供します。プロキシの構成とローテーションは抽象化されています。

再試行とフェイルオーバー

サービスは失敗したリクエストを自動的に再試行し、必要に応じて新しいプロキシに切り替えて、データを確実に取得します。

クラウドのスケーラビリティ

スクレイピング API は、お客様側でエンジニアリング作業を行わなくても、需要に合わせて即座に拡張できます。

したがって、多くの場合、専用のスクレイピング API を活用し、他の領域に注力することが望ましいと考えられます。

主要な取り組み

Python で Web スクレイピングのパフォーマンスを最適化するために取り上げた中心的なテクニックは次のとおりです。

  • ボトルネックを特定する: スクレーパーをプロファイリングして、遅い I/O タスクと CPU タスクを分離します。

  • asyncio で I/O を最適化する: asyncio および async ライブラリを使用して、リクエストの待機を排除します。

  • CPUの作業を並列化する: マルチプロセッシングを活用して、すべての CPU コアにデータ処理を分散します。

  • それらを組み合わせる: I/O の Asyncio と CPU のマルチプロセッシングは非常にうまく連携します。

  • APIのスクレイピングを検討する: ScraperAPI や Scrapfly などのサービスが最適化を処理します。

これらのアプローチを使用すると、スクレイパーを桁違いに高速化できます。 Asyncio とマルチプロセッシングは、パフォーマンスの高い Python スクレイピングの親友です。

他にご質問がございましたら、お知らせください。私は、他の開発者がこれらの同時実行技術を実装するのをいつでも喜んで支援します。

タグ:

参加する

あなたのメールアドレスは公開されません。 必須フィールドは、マークされています *