誰もが対策を考える失敗はわかりやすいものだ。403、429、タイムアウトする接続。これらは目に見え、数え上げられ、リトライできる。だが実際にデータセットを汚染する失敗はその逆だ。完全に成功したように見え、目的のものが何も含まれていない 200 OK。通常のレスポンスに見せかけたブロックページ。チャレンジ画面。空っぽの外殻。ボットに誤った情報を与えるために特別に作られたデータのページ。
エラーになるスクレイパーは煩わしい。だがゴミデータで「成功」するスクレイパーは危険だ。悪いデータが既にウェアハウスやレポート、モデルに取り込まれるまで気づかないからだ。これはスクレイパーがブロックされる理由のもう半分の側面でもある。現代のアンチボットシステムは、静かに欺くことをますます好むようになっている。サイレントな失敗の方が、見える失敗よりも彼らにとって価値が高いからだ。ここでは、それをどう見つけるかを説明する。
ステータスコードは真実ではない
まず捨てるべき習慣は、HTTPステータスコードを成功の指標として信頼することだ。200 はサーバーがレスポンスを送ったことを意味するだけで、求めたレスポンスを送ったことは意味しない。サイトはブロックページやCAPTCHA、同意ウォールを日常的に 200 で返す。そうすることで、単純なスクレイパーを何も与えずに満足させ、黙らせておけるからだ。ステータスコードは弱い信号の一つとして扱い、すべてのリクエストでボディを検証するべきだ。
欺瞞的な 200 のカテゴリーは、それぞれ異なる特徴があるため名前を付けておく価値がある。
- ソフトブロックとインタースティシャル。 チャレンジや「あなたが人間であることを確認してください」というページが
200で返され、しばしば実際のページよりずっと小さい。 - チャレンジとCAPTCHA画面。 コンテンツがあるべき場所にインラインで提供される。アンチボット防御がどのように進化したかに関連する。
- 劣化または削られたコンテンツ。 ログインウォール、「JavaScriptを有効にしてください」のスタブ、データが表示されるはずだった空のスケルトン。
- レート制限のソフトフェイル。 一定のしきい値を超えると、エラーの代わりに古い、キャッシュされた、あるいは空の結果が返される。
- 誤ったジオまたはパーソナライゼーション。 間違った国、通貨、ログアウト状態向けの正しいページ。技術的には有効だが、静かに無用。
- ハニーポットと汚染されたデータ。 ボットに向けて意図的に提供されるコンテンツ。偽の価格、トラップリンク、あり得ない値を持つ本物らしく見えるレコード。
配信だけでなくコンテンツを検証する
最も効果的な単一の防御策は、すべてのレスポンスに対するコンテンツアサーションだ。つまり、ページが本来含むべきものを含んでいるかを確認する安価なチェックだ。本物の結果には常に存在し、ブロックページには決して存在しない不変条件、具体的な要素、必須のフィールド、最低限妥当な長さを選び、それが欠けていればリクエストを失敗として扱う。
def is_valid_product_page(html, parsed): # A real product page always has these. A block page has none of them. if len(html) < 2000: # block pages are usually tiny return False if parsed.select_one("h1.product-title") is None: return False # the anchor element is gone if parsed.select_one('[data-price]') is None: return False # the field we came for is missing return True重要な転換は、アンカー要素が欠けていることを「失敗」として扱うことであり、空の結果として扱わないことだ。依拠しているセレクタが存在しない場合、空の行を記録してそのまま進めるのではなく、そのレスポンスをソフトブロックとして扱い、403 の場合と同様に新しいアイデンティティでリトライするべきだ。空の行を記録することが、ブロックが静かに何千もの空行に変わり、それがずっと後になるまで誰にも気づかれない原因となる。
個々のレスポンスだけでなく、レスポンス全体の形も見る
個別のチェックは明らかなゴミを捕まえる。分布的なチェックは微妙な変化を捕まえる。そしてそれこそが、堅牢なパイプラインと脆弱なパイプラインを分けるものだ。
- レスポンスサイズ。 ブロックページやチャレンジページは概して小さく、均一な傾向がある。バッチ全体で平均ページサイズが急に縮小したり、レスポンスが一つの正確なバイト数に集中してスパイクしたりする場合、それぞれが
200を返していてもブロックの兆候である。 - レスポンスのハッシュ化。 各レスポンスボディの正規化されたバージョンをハッシュ化する。同じハッシュが多数の異なるURLに突然繰り返されるようになったら、実際は本物の多様なコンテンツではなく、一つのブロックページを何度も配信されているということだ。
- フィールドの充填率。 各フィールドが実際に入力されたレコードの割合を追跡する。昨日は98パーセント充填されていたフィールドが今日は4パーセントしか充填されていないなら、それが少なくなったわけではなく、削られたページを受け取るようになったということだ。
- ホストごとの成功率。 他のターゲットは安定しているのに、特定のターゲット一つだけで低下が見られる場合、そのターゲットがあなたに対する姿勢を変えたということであり、実行全体を無駄にする前に捕まえる価値がある。これはKubernetesでスクレイピングパイプラインを大規模に監視する際に重要になる、同じターゲット別の信号だ。
これらのどれも機械学習を必要としない。カウンターと単純なベースラインであり、それが最初の百件のリクエストでブロックに気づくか、百万件後に気づくかの違いを生む。
既知のブロックページの署名
ブロックページ、チャレンジページ、エラーページにしか現れないフレーズやマーカーの小さなライブラリを維持し、ステータスコードに関わらずそれらを含むレスポンスにフラグを立てる。
BLOCK_MARKERS = ( "verify you are human", "unusual traffic", "access denied", "enable javascript to continue", "request blocked",)
def looks_blocked(text): low = text.lower() return any(marker in low for marker in BLOCK_MARKERS)これらの話題を偶然扱っている本物のコンテンツで誤検知しないよう、短く具体的に保ち、新しい防御に遭遇するたびに増やしていく。認識できるチャレンジページは、保存する代わりにリトライできるチャレンジだ。
ハニーポットと汚染されたデータ
最も厄介なカテゴリーは、本物に見えるよう設計されたコンテンツだ。ここでは二つの防御策が重要になる。
まず、トラップリンクをたどらないこと。ハニーポットリンクは一般的に display:none、visibility:hidden、サイズゼロ、画面外への配置、aria-hidden によって人間から隠され、すべてのアンカーをたどるボットを捕まえるためだけに存在する。可視性を尊重し、人間が決してクリックできないリンクを無視するクローラーは、そのほとんどを回避できる。
次に、データそのものの妥当性をチェックすること。汚染されたレコードは単純なパーサーを通過するように作られているが、基本的なドメインルールには違反する傾向がある。ゼロまたは異常に高い価格、未来の日付、存在し得ない数量、妥当な範囲外の値。HTMLがきれいにパースされたからといって信頼するのではなく、そのドメインが実際に許容する範囲に対して検証し、それに違反するレコードは隔離する。
カナリアリクエスト
最も信頼できる早期警告はカナリアだ。定期的に、正しいコンテンツを既に知っているページを取得し、それが依然として一致することを確認する。カナリアがブロックページや誤ったデータを返すようになったとき、データ品質のゆっくりとした低下から推測する必要なく、即座かつ明確にターゲットがあなたに対して切り替わったことがわかる。サイトは一つの国の出口IPをブロックしながら別の国には手を出さないこともあるため、ターゲットごと、ジオごとにカナリアを実行するべきだ。
プロキシがどこで役立つか
クリーンなIPは、そもそもソフトブロックされる頻度を減らす。良好なレピュテーションを持つプールは、フラグの付いたアドレスが静かにチャレンジページを与えられる場面をすり抜けていく。それによって捕まえなければならない欺瞞的なレスポンスの割合は低くなるが、捕まえる必要性そのものはなくならない。ブロックは設計上ますます見えにくくなっており、どのIPも無縁ではないからだ。この二つは連携して働く。検知はレスポンスがソフトブロックだったことを教えてくれ、ローテーションするレジデンシャルプールは、それをハードな失敗と同じように扱ってリトライするための新しいアイデンティティを提供してくれる。検知したすべてのソフトブロックをリトライとローテーションのロジックにフィードバックし(スティッキー対ローテーティング)、ターゲットごとの持続的なブロック率の急上昇は、押し続ける信号ではなく後退する信号として扱うべきだ(ブロックを避けると責任あるスクレイピングの両方が当てはまる)。
結論
レスポンスは、そうでないと証明されるまで嘘をついていると仮定すること。すべてのリクエストで、本物のページが常に持つ不変条件に対してコンテンツを検証し、欠けているアンカーは保存すべき空の行ではなくリトライすべき失敗として扱うこと。レスポンスサイズ、ハッシュ、充填率の分布を監視し、200 を返すブロックでも異常として現れるようにすること。既知のブロックページの短い署名リストを維持し、隠されたトラップリンクをたどることを拒否し、ドメインルールに対してデータの妥当性をチェックし、ターゲットがあなたに対して切り替わった瞬間を知るためにカナリアを実行すること。
それを実行すれば、サイレントな失敗、つまりデータセットを何週間も静かに汚染するあの失敗は、静かではなくなる。それをクリーンなレジデンシャルプールと組み合わせれば、そもそもソフトブロックされる頻度が減り、GB単位のプランを使えば、事前にコミットすることなく自分のターゲットに対してこれを検証できる。