ほとんどのスクレイパーは、1つのheaderが読み取られる前に失敗します。
サーバーはTLSハンドシェイク(暗号スイートの順序、拡張機能の順序、楕円曲線の設定)を確認し、あなたがブラウザなのか、あるいはブラウザを装ったクライアントライブラリなのかを判断します。Pythonのrequests、Goのnet/http、通常のcurlはすべて、接続を開始した瞬間に特徴的なfingerprintを渡してしまいます。対策を行っているサイト(Datadome、Akamai、Imperva、Cloudflareのマネージドサービスなど)は、User-Agent文字列が評価される前に、接続を切断するかチャレンジページを表示します。
これが、FourAでunblocker: trueが解決する課題です。先月、私たちはこれを確実に動作させるための要素を特定しました。
アップデート
unblocker: trueは、任意の/api/single呼び出しにおける単一のフラグです。これを有効にすると、ブラウザのheaderセットの挿入、実際のブラウザのTLS fingerprintを使用したcurl-impersonate経由のrequest送信、そしてサーバーが返すデータ(gzip、brotli、deflate)の自動展開という3つの処理が行われます。最初の2つはベータ版から利用可能でした。3つ目の機能(brotliの自動展開)は3月25日にリリースされ、翌日にはheaderとTLSの同期を維持するためのブラウザバージョンの固定(pinning)作業が完了しました。
仕組み
requestの例は以下の通りです。
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の挿入。 私たちは、User-Agent、Sec-Ch-Ua、Sec-Ch-Ua-Platform、Sec-Fetch-Site、Sec-Fetch-Mode、Sec-Fetch-Dest、Accept、Accept-Language、Accept-Encodingを含む、完全なブラウザのheaderバンドルを設定します。順序が重要です。実際のブラウザはこれらを特定の順序で送信し、検出ライブラリはその順序をチェックします。
TLS fingerprint。 curl-impersonate 0.8.2はBoringSSLに対してlibcurlをコンパイルし、対象のブラウザバージョンが実際に送信する内容と一致するようにTLS拡張の順序を並べ替えます。これにより、JA3およびJA4ハッシュは実際のブラウザセッションと同一になります。標準のcurl、Pythonのrequests、Goのnet/httpが生成するfingerprintは、保護されたインフラストラクチャ上では数ミリ秒以内に自動的にフラグが立てられます。
自動展開。 unblockerが有効な場合、Accept-Encodingをgzip, deflate, brに設定し、libcurlにbodyを展開させます。デコードされた文字列(returnBuffer: trueを渡した場合はBuffer)が返されます。手動でのbrotli処理や、サイトがgzipではなくdeflateを選択した際のheaderとbodyの不一致は発生しません。
バージョン固定が重要な理由
TLS fingerprintはバージョンに依存します。今月のブラウザの暗号順序は先月のものとは異なり、厳密にfingerprintをチェックするサイトはそのズレを検知します。curl-impersonateは特定のブラウザビルド用のプロファイルを提供しており、私たちは実際のブラウザバイナリをcurl-impersonateが現在ターゲットにしているビルドに固定しています。header、navigatorオブジェクト、TLSのすべてが同じバージョンを報告します。
これが面倒に聞こえるなら、実際にその通りです。3月のモノレポ移行時に、ブラウザが自動更新されたことでheaderとcurl-impersonateの同期が崩れ、不一致が発生する問題に直面しました。修正は2つのコミットで行われました。ブラウザバイナリを固定すること、およびパッケージマネージャーがそれらを同期させてくれると決して信用しないことです。
影響
厳密なfingerprintチェックを行うターゲット(金融、旅行、保護されたECサイト)に対する社内テストでは、unblocker: falseとunblocker: trueの違いは、チャレンジページが表示されるか、ステータスコード200が返されるかの違いになります。通常のcurlでマネージドCloudflareにアクセスすると、初回から403エラーになります。同じURLでもunblocker: trueを使用すれば、TLS helloが実際のブラウザのハンドシェイクのように見えるため、通過できます。
しかし、fingerprintチェックを行わないサイト(ほとんどのパブリックAPI、古いCMSテンプレート、IPのrate limitのみで制限されているサイトなど)では、unblockerをオフのままにしておくことで、TLSネゴシエーションの数ミリ秒を節約できます。必要な場所でのみ使用してください。
パワーユーザー向け
知っておくべきいくつかのパターンを紹介します。
ターゲットがIPレピュテーションもチェックしている場合は、unblockerと住宅用(residential)proxyを組み合わせてください。データセンターのIPと完璧なTLSハンドシェイクを組み合わせても、サイトがブラックリストに登録しているASNによってフラグが立てられます。当社のproxy endpoint(/api/proxy)はターゲットドメインごとにローテーションするため、通常はrequestに"proxy": "residential"を追加するだけで十分です。
ブラウザを考慮しないJSON APIを呼び出す場合は、unblockerをスキップしてください。プログラムによるクライアントを想定しているAPI(例えば、独自のマイクロサービスを呼び出すバックエンドなど)に対しては、余分なheaderが逆に不審に見えることがあります。
サイトがJavaScriptによるアンチボット(Turnstileのインタラクティブなチャレンジ、最も厳格な設定のPerimeter X、ヒューリスティックを最大にしたAkamai Bot Managerなど)を実行している場合、unblockerだけでは不十分です。実際のChromiumを実行し、チャレンジを処理できるブラウザのendpointが必要になります。これはクレジット料金体系が異なる別の製品であり、詳細についてはBrowser Tasks: JavaScriptを多用するサイトのスクレイピング方法で解説しています。
また、unblockerとvalidateブロックを組み合わせて、技術的には200を返しながらもチャレンジページが含まれているresponseを拒否することもできます。
{
"url": "https://example.com/products",
"method": "GET",
"unblocker": true,
"validate": {
"data": { "fail": ["captcha", "Access Denied"] }
}
}
これにより、サイレントな失敗が分類された失敗に変換され、dashboardでの成功率の追跡において重要になります。
今後の展望
ブラウザは4週間ごとに新しい安定版をリリースします。curl-impersonateのメンテナーは通常1か月遅れで追従し、追従された時点で当社のスタックも更新されます。お客様側で何かを変更する必要はありません。unblocker: trueは、当社がエンドツーエンドで検証したブラウザバージョンを常に指し示し続けます。
より困難な課題が控えています。HTTP/3のfingerprintingはすでにマネージドアンチボットに導入され始めており、QUICトランスポートはTLS 1.3よりも偽装が困難です。また、静的なheaderバンドルから真に動的なエミュレーションへの移行も始まっています。保護されたサイトはHTTP/2のフレーム順序やJA4+バリアントのチェックに移行しており、「ブラウザのように見えるcurl」と「実際のブラウザ」のギャップは双方から縮まっていくでしょう。これらについては、リリース時にお知らせします。