1736513491
2025-01-09 09:10:00
その順番で。
デニス・シューベルト、Mozilla のエンジニア、注目すべき貢献者 ディアプソラ分散型のオープンソース ソーシャル ネットワークである は、最近、ディアスポラのサーバーの負荷の 70% が、少数の大手組織の LLM に餌を与える行儀の悪いボットから来ていることを観察しました。合計トラフィックの 40% に相当する最悪の攻撃者は、OpenAI と Amazon でした。
クロールの一般的なエチケットに関する記事は無数にありますが、実際にルールを遵守しながら、迅速かつ効率的にクロールする方法に関する記事は数百万件しかありません。最近趣味のクローラーに取り組んでいるので、詳細が最優先事項なので、よく説明されない部分を見てみましょう。
技術的背景
私のキューはPostgresのテーブルです。私のクローラーは Python で書かれており、 紳士 同時実行と非同期 IO 用。 (Python3 以前は暗黙的非同期 IO に夢中だったので、好きではありませんでした) 非同期/待機。)
レート制限
丁寧なクロールの最低限の条件はレート制限です。サイトでクロール遅延が指定されていない場合は、 ロボット.txtデフォルトでは 5 秒ごとに 1 つのリクエストを設定しています。 429 を取得したら、速度を落とします。原理的には複雑ではありません。
実際には、これも複雑ではありません。ドメインごとに 1 つのフェッチ コンテキスト (ワーカー、スレッド、またはコルーチン) に制限する場合、ローカル変数を使用して、最後のリクエストを行ってからの経過時間を追跡するのと同じくらい簡単です。
もちろん、これは大規模で堅牢なサイトを非常にゆっくりとクロールすることを意味しますが、私はむしろ、礼儀正しくすることをデフォルトの動作にする設計から始めることをお勧めします。したがって、ドメインごとに 1 つのコルーチンを実行します。ドメイン時間ごとにディスペンスするアイテムの数を追跡する複雑なキューは必要ありません。私はまだ複数のフェッチ ワーカーを持っていません (単一のプロセス/マシンで最大 10,000 個のドメインを並行して処理できます) が、そうなった場合は、ハッシュ スキームを介してワーカーにドメインを分散します。
ワーカー ロジックをシンプルに保つために、レート制限ロジックをネットワーク層のすぐ上にプッシュし、呼び出しのラッパーに含めます。 リクエスト.get、コルーチンのローカル値で最後のリクエスト時間を追跡します。これがフェッチ ワーカーの中心部です。
def fetch(url):
log.info(lib.url.get_path(url))
if (doc := Doc.get(url)) and not Doc.should_fetch(doc):
log.info(' still fresh, skipping')
elif doc := Doc.fetch(url):
log.info(' fetched')
Doc.upsert(doc)
if Doc.should_process(doc):
Q.process.nq(doc.url)
一意のドメイン (パーティション) がキューに追加されると、Postgres は 通知する ワーカーがリッスンしているイベントを取得し、そのパーティション専用の新しいコルーチンを作成します。
敬意を持って効率的にキューに入れる
以来 robots.txt/禁止: URL をキューに追加する前に参照され、キューへの URL の追加は、多くの異なるドメインを含む可能性のあるバッチで行われ、すべてをフェッチします。 ロボット.txt■ キューのエンキュー関数内で、指定された URL を並列処理します。このようにして、許可されていない URL をフィルターで除外する準備が整う前に、エンキュー バッチごとに最大 1 つの GET リクエストを待機します。
再フェッチを最小限に抑える…
これも原理的には単純ですが、複数のデータ ソースを参照する必要があるため、実際には少し面倒です。これがスケッチです Doc. should_fetch():
- 最後の応答に 有効期限が切れます 将来のヘッダー値。
- いいえ、HEAD リクエストに eタグ または 変更された場合-以降 ヘッダーは 304 を返します。
- HEAD 応答ヘッダーに 最終更新日 前回の訪問より古い値 (可能性は低いですが、サーバーが HEAD を正しく処理しない場合に発生する可能性があります)。
- 前回の訪問が当社独自の基準から見てかなり最近のものである場合は、いいえ。
- それ以外の場合は、はい!
…効率的な再クロールのために
一瞬電話してみようかと思いながらも Doc. should_fetch() (あるいは、おそらく、 Doc. should_enqueue()) フェッチ キューに空の作業が追加されるのを避けるためにプロセス ワーカー内でそのチェックを行うと、ネットワーク IO (ドキュメントが変更されたかどうかを確認するための潜在的な HEAD リクエスト) をプロセス ワーカーから排除するという利点があります。より効率的な CPU 飽和を可能にします。
また、ワーカーとキューの関係もシンプルに保たれます。プロセス ワーカーが新しいドキュメントのフェッチ キューをスキップしたい場合は、ドキュメントをプロセス キューにエンキューする必要があります。これにより、プロセス キューに別のエントリポイントが作成され、ロジックが重複する可能性があります。
そしてどのように
その結果、レート制限、鮮度、必要性を考慮して、ロジックとリソース使用率が明確に分割されます。
これは包括的とは程遠いですが、年間 50 万ドルを稼ぐ一部のコード モンキーが行っていることよりもはるかに優れているように思えます。クリックベイトのタイトルを正当化するための印象的な数字が得られたら、また報告します。
#礼儀正しく高速な #Web #クローラーの構築