← すべての記事

ブラウザプロファイルの内部構造: `unblocker: true` の実際の動作

1つのフラグで3つの機能(実際のブラウザのheader、一致する接続シグネチャ、gzipとbrotliの自動展開)が有効になります。その機能と理由を説明します。

大半のスクレイパーは、ヘッダーが1つも読み取られる前にブロックされます。

サーバーはクライアントが送信する接続レベルのシグネチャを検査し、本物のブラウザか、ブラウザを偽装したクライアントライブラリかを判別します。Pythonのrequests、Goのnet/http、標準のcurlはいずれも、接続開始の瞬間に特徴的なフィンガープリントを渡してしまいます。対策を行っているサイト(Datadome、Akamai、Imperva、Cloudflareのマネージドルールなど)は、User-Agent文字列が評価される前に接続を切断するか、チャレンジページを表示します。

FourAのunblocker: trueは、まさにこの問題を解決します。先月、これを安定して動作させるための各要素を確定させました。

新機能

unblocker: trueは、/api/singleの呼び出しで使用できる単一のフラグです。これを有効にすると、ブラウザヘッダーセットの挿入、実際のブラウザと同等のトランスポートによるリクエスト送信、サーバーからのレスポンス(gzip、brotli、deflate)の自動解凍という3つの処理が実行されます。最初の2つはベータ版から利用可能でした。3つ目のbrotli自動解凍は3月25日にリリースされ、ヘッダーとトランスポートの整合性を維持するためのバージョン固定機能はその翌日に導入されました。

仕組み

リクエストの例は以下の通りです。

curl -X POST "https://api.foura.ai/api/single" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "url": "https://example.com/products",
    "method": "GET",
    "unblocker": true
  }'

内部では3つのレイヤーが動作しています。

Header injection. User-Agent、Sec-Ch-Ua、Sec-Ch-Ua-Platform、Sec-Fetch-Site、Sec-Fetch-Mode、Sec-Fetch-Dest、Accept、Accept-Language、Accept-Encodingといったブラウザのヘッダーバンドル全体を設定します。順序が重要です。実ブラウザはこれらを特定のシーケンスで送信し、検知ライブラリはその順序をチェックします。

Connection signature. トランスポート層は最新ブラウザセッションのバイトレベルの形状に一致させています。同じ拡張順序、同じ暗号スイートの優先順位、同じハンドシェイクの挙動を再現します。標準のcurl、Python requests、Goのnet/httpが生成するシグネチャは、保護されたインフラ上ではミリ秒単位で機械的にフラグ付けされます。

Auto-decompression. unblockerが有効な場合、Accept-Encodingをgzip, deflate, brに設定し、トランスポート層がボディを展開します。デコードされた文字列(returnBuffer: trueを渡した場合はBuffer)が返されます。手動でのbrotli処理や、サイトがgzipではなくdeflateを選択した際のヘッダーとボディの不一致に悩まされることはありません。

バージョン固定が重要な理由

接続シグネチャはバージョンに紐づいています。今月のブラウザのワイヤレベルの詳細は先月と同一ではなく、厳格にフィンガープリントを行うサイトはその差異を検知します。ヘッダー、navigatorオブジェクト、接続シグネチャがすべて同じブラウザバージョンを報告するよう、変動要素を連動させて固定しています。

手間に思えるかもしれませんが、実際その通りです。3月のモノレポ移行時、1つのコンポーネントが自動更新されたことで全体の同期が崩れ、不一致による問題に直面しました。修正は2つのコミットで完了しました。変動パーツをピン留めすること、そして整合性の維持をパッケージマネージャー任せにしないことです。

効果

リクエスト元を検査するサイト(金融、旅行、大規模eコマースなど)に対する内部テストでは、unblocker: falseとunblocker: trueの差は、チャレンジページが表示されるか200が返るかの差になります。通常のHTTPクライアントは最初のリクエストで403を返されるケースが多くあります。unblocker: trueを指定した同じURLでは、リクエストが宣言通りのブラウザとして認識されるため、正常にページを取得できます。

ただし、フィンガープリントを行わないサイト(大半のパブリックAPI、古いCMSテンプレート、IPレート制限のみで制御されている対象など)では、unblockerをオフのままにしても問題なく、ネゴシエーションの数ミリ秒を節約できます。必要な場所でのみ使用してください。

パワーユーザー向け

知っておくべきいくつかのパターンです。

ターゲットがIPレピュテーションもチェックしている場合は、unblockerをレジデンシャルプロキシと組み合わせてください。接続シグネチャが完璧でも、データセンターIPである場合はサイトがブラックリストに登録しているASNでフラグが立ちます。FourAのプロキシエンドポイント(/api/proxy)はターゲットドメインごとにローテーションするため、リクエストに"proxy": "residential"を追加するだけで通常は十分です。

ブラウザを意識しないJSON APIを呼び出す場合は、unblockerを省略してください。バックエンドが独自のマイクロサービスを呼び出す場合など、プログラムによるクライアントを前提とするAPIに対しては、余分なヘッダーが逆に不審に見えることがあります。

ページを表示する前にサイトがJavaScriptでビジターを検証する場合、unblocker単体では不十分です。ページのJavaScriptを完全なブラウザ環境で実行するbrowser endpointが必要になります。これはクレジット体系が異なる別製品であり、詳細はBrowser Tasks: How to Scrape JavaScript-Heavy Sitesにまとめています。

また、unblockerをvalidateブロックと組み合わせることで、形式上は200を返しつつもチャレンジページが含まれているresponseを拒否できます。

{
  "url": "https://example.com/products",
  "method": "GET",
  "unblocker": true,
  "validate": {
    "data": { "fail": ["captcha", "Access Denied"] }
  }
}

これによりサイレント障害が分類済みの障害に変換され、ダッシュボードでの成功率トラッキングに役立ちます。

今後の展望

ブラウザは4週間ごとに新しい安定版をリリースします。FourAもこれに合わせてスタックを更新します。ユーザー側の設定変更は不要です。unblocker: trueはFourAがエンドツーエンドで検証済みのブラウザバージョンを常に参照し続けます。

今後はさらに難度の高い課題が控えています。大規模サイトではすでにHTTP/3のチェックが導入され始めており、QUICトランスポートの完全な模倣は従来のトランスポートよりも複雑です。また、静的なヘッダーセットから真に動的なエミュレーションへの移行も始まっています。保護対象サイトはHTTP/2のフレーム順序の検証へ移行しており、「ブラウザのように見えるライブラリ」と「実際のブラウザ」の差は双方のアプローチから狭まりつつあります。実装完了時に改めて詳細を公開します。