あなたはレジデントプロキシを購入し、新しいUser-Agent Chromeを設定しましたが、サイトは最初のリクエストで403を返します。 これはよくあることですか?問題はIPやヘッダーではありません。サーバーがHTTPヘッダーを一つでも読む前に、TLSハンドシェイクによってあなたが特定されてしまったのです。2026年にはこれが検出の主要なベクトルであり、通常の requests は自動的にこれを突破できません。これがどのように機能し、curl_cffiを使って数行のコードで修正できるかを見ていきましょう。
何が起こっているのか:TLSハンドシェイクによって特定される
クライアントがHTTPS接続を確立する際、最初にClientHelloパケットを送信します — HTTPの前にです。この中には、TLSのバージョン、サポートされている暗号スイートのリスト、TLS拡張(SNI、ALPN、supported_groups)、楕円曲線および点の形式が含まれています。これらのフィールドの順序と構成は異なるクライアントによって異なります — そのため、クライアントは言葉を発する前に特定されるのです。
これらのフィールドからフィンガープリンツが計算されます。JA3(2017年の標準)は、TLSVersion,Ciphers,Extensions,EllipticCurves,ECPointFormatsの形式の文字列を取得し、それをMD5でハッシュ化して32文字のシグネチャを生成します。JA3の問題は、2023年1月からChromeが拡張の順序をランダム化するため、16の拡張が16!(20兆以上)の組み合わせを生み出し、同じブラウザが異なるJA3を生成することです。
そのため、業界はJA4(FoxIO、2024–2025年の大規模導入)に移行しました。JA4は、ハッシュ化の前に拡張コードを16進数の値でソートします — Chromeのランダム化によって壊れることはありません。ハッシュは切り捨てられたSHA-256で、人間が読める形式で三部構成(a_b_c)になっており、ALPNとQUIC/HTTP3のサポートが含まれています。例:Chrome 124はt13d1516h2を生成します(15の暗号、16の拡張、ALPN h2)、一方、素のPython requestsはt13d1715h2を生成します。ボット対策において、二つ目のシグネチャは「これはスクリプトです」という直接的なマーカーです。
なぜ2026年にはこれが必要なのか
JA4検出はすべての主要なベンダーに組み込まれています:Cloudflareはフィンガープリンツを許可リストと照合し、AkamaiはHTTP/2 SETTINGSフレームのための別のハッシュを追加し、DataDomeは既知のボットのデータベースと比較します。論理はシンプルで致命的です:あなたがUser-Agent: Chrome 131を送信し、TLSフィンガープリンツが「urllib3/OpenSSL」と叫んでいる場合 — これは同期のずれであり、あなたは即座にブロックされます。どんなプロキシも助けになりません:完璧なレジデントIPがPython requestsのフィンガープリンツを持っていても、結局は負けてしまいます。
そのため、「プロキシ + フィンガープリンツの偽装」という組み合わせは、2026年にはスクレイピングの基本的な衛生状態となり、上級者向けのオプションではなくなりました。
解決策:curl_cffiを5分で
curl_cffiは、curl-impersonate(ChromeのBoringSSLまたはFirefoxのNSSを使用して構築された修正されたcurl)に対するPythonラッパーです。これは本物のブラウザのハンドシェイクを再現し、ほぼ従来のrequestsと同じAPIを持っています。
ステップ1. インストール。 Windows/macOS/Linux用のcurl-impersonateのバイナリは自動的に取得されます:
pip install curl-cffi
ステップ2. 基本的なリクエスト。 インポートを変更し、一つのパラメータを追加します:
from curl_cffi import requests
resp = requests.get("https://target.com/", impersonate="chrome")
print(resp.status_code)
print(resp.http_version) # HTTP/2 — 本物のブラウザのように
一行のimpersonate="chrome"は、TLSフィンガープリンツ(JA3/JA4)、HTTPのバージョン(HTTP/2ではなくHTTP/1.1)、ヘッダーの順序、ALPNネゴシエーションの4つの層を同時に偽装します。
ステップ3. 常にgenericエイリアスを使用し、バージョンを固定しないでください。 impersonate="chrome"(または"safari", "safari_ios")と記述してください — エイリアスは自動的に最新のプロファイルに解決されます。ハードコーディングされたimpersonate="chrome124"は古くなります:Chromeは約4週間ごとに更新され、古いプロファイルは自ら異常になります。信頼できるターゲットはChrome、Edge、Safari/iOSです(chrome99からchrome131、safari15–18のプロファイル)。
ステップ4. プロキシとセッション。 実際のスクレイピングのためには、セッション内で状態を保持し、プロキシを接続します。レジデントまたはモバイルIPがここでは必須です — データセンターはTLSとは別にASNによって特定されます:
from curl_cffi import requests
session = requests.Session(impersonate="chrome")
headers = {
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Referer": "https://www.google.com/",
}
proxies = {
"http": "http://user:pass@proxy-host:port",
"https": "http://user:pass@proxy-host:port",
}
resp = session.get("https://target.com", headers=headers, proxies=proxies)
ステップ5. ボリュームのための非同期性。 requestsとは異なり、curl_cffiにはデフォルトでasyncとHTTP/2があります:
import asyncio
from curl_cffi.requests import AsyncSession
async def fetch(session, url):
r = await session.get(url, impersonate="chrome")
return r.status_code
async def main(urls):
async with AsyncSession() as session:
return await asyncio.gather(*[fetch(session, u) for u in urls])
asyncio.run(main(["https://target.com"] * 20))
フィンガープリンツを確認してください — 推測しないでください
本番トラフィックを送信する前に、偽装が実際に機能していることを確認してください。公開の検証ツールにリクエストを送信し、JA4を基準のブラウザと比較してください:
- tls.peet.ws — JA3、JA4、Akamaiフィンガープリンツ、HTTP/2フレームをJSONで返します。これを
curl_cffiと実際のChromeを通じてリクエストし、ハッシュを照合してください。 - ja4db.com — 知られているJA4のデータベースで、あなたが誰に似ているかを理解するのに役立ちます。
- browserleaks.com/tlsとScrapflyのJA3/JA4ツール — フィールドの詳細な内訳。
ステージングでは、mitmproxyをスクレイパーとターゲットの間に置き、各リクエストの実際のJA4ハッシュを監視するのが便利です。
診断:まだ403/429を受け取っていますか
フィンガープリンツが正しい場合、ブロックが残っている場合は、一般的なものから珍しいものへとチェックリストを進めてください:
- データセンターIP。 理由№1。レジデントプロキシまたはモバイルプロキシに切り替えてください — フィンガープリンツだけでは不十分な理由は、IPインテリジェンスによるレジデントプロキシの検出に関する資料で詳しく説明されています。
- 古くなったプロファイル。
pip install -U curl-cffiとgenericエイリアス"chrome"。 - レートが高すぎる。 リクエストの間に1〜3秒のランダムな間隔を追加してください。
- 裸のヘッダー。
Accept-Language、Accept-Encoding、Refererを必ず送信してください — これらが欠如していることも異常です。 - セッションとIPの同期のずれ。 ルール:一つのセッションには一つのIPをそのライフタイム全体にわたって使用します。
- ステータス200 ≠ 成功。 レスポンスのボディを確認してください:200コードの下にはCAPTCHAのページがあるかもしれません。
curl_cffiが壁にぶつかるところ
curl_cffiはネットワーク層を閉じます — それがすべてです。JavaScriptを実行しません。したがって、JSチャレンジに対しては無力です:Cloudflare Turnstile、ページ「ブラウザを確認中…」(IUAM)、スクリプトがチェック後に設定するクッキーcf_clearance — これらはすべて本物のブラウザ環境を必要とします。2026年にキャプチャソルバーがこれらの予防システムに対してほぼ機能しなくなった理由については、CAPTCHAの回避に関する別の分析で説明しました。
JSの壁にぶつかったときに何をすべきか:
- ハイブリッド。 PlaywrightやNodriverがチャレンジを通過し、
cf_clearanceを取得し、その後クッキーを迅速なcurl_cffiに渡して主要なリクエストを処理します — これにより、重いブラウザに一度だけ支払うことになります。 - ソルバーサービス(CapSolver、2Captcha)によるトークンの自動発行。
- マネージドスクレイピングAPI、インフラを維持したくない場合。
スレッドセーフ性を忘れないでください:各スレッドに独自のセッションを持たせてください。requirements.txtにcurl-cffiのバージョンを固定し、ブラウザが更新される際に6〜12週間ごとにプロファイルを見直してください。
curl_cffiの代替
- tls-client — uTLSベースのGoライブラリのラッパーで、プロファイル(
chrome_124、safari_ios_17)とフラグrandom_tls_extension_order=Trueを持っています。フィンガープリンツの柔軟な微調整が可能です。 - primp — Rustで書かれたクライアントで、
impersonate_osを独立して設定でき、より高いスループットを提供します;欠点は、APIがrequestsと完全には一致せず、ライブラリが若いことです。
どのプロキシが必要で、なぜか
フィンガープリンツの偽装とプロキシは、一つのタスクの異なる側面を解決します:curl_cffiは「接続がどのように見えるか」という問題を解決し、プロキシは「どこから来るのか」という問題を解決します。アンチボットは両方の信号を独立して確認するため、完璧なJA4を持つ黒いデータセンターASNは無意味です。保護された目的(マーケットプレイス、ソーシャルネットワーク、旅行アグリゲーター)には、レジデントまたはモバイルプロキシを使用してください:それらはクリーンなオペレーターの出所を持ち、モバイルはCGNATの「群衆効果」に隠れています。データセンターは、感受性の低い目的や高いボリュームのために残しておいてください。
結論
2026年のスクレイピングは、IPだけでなくアイデンティティのゲームです。素のrequestsはTLSハンドシェイクのレベルでスクリプトとして認識され、最初のヘッダーの前に負けてしまいます。インポートをcurl_cffiに置き換え、impersonate="chrome"を使用することで、この失敗を5分で解消できますが、クリーンなレジデントまたはモバイルIPとの組み合わせと、ネットワーク層 — はい、JavaScriptチャレンジ — いいえという理解が必要です。正しいフィンガープリンツ、正しいプロキシ、JSの壁があるところではブラウザとのハイブリッドを組み合わせて、最初のリクエストでの403を過去のものにしましょう。
