curl_cffi vs requests: 修正できることとできないこと
requestsをcurl_cffiに切り替えると、多くの403が200に変わります。しかし、焼き尽くされたIPには何の効果もありません。ここでは、その両者の境界線と、問題がどちらにあるのかを教えてくれる15分のテストを紹介します。
curl_cffi vs requestsの問題は通常、インシデントの途中で発生します。数ヶ月間動作していたスクレイパーが最初の呼び出しで403を返し始め、Redditの誰かがクライアントを切り替えるように言い、それが機能します。それは実際の効果であり、実際の説明がありますが、「curl_cffiを使うだけ」という繰り返し方は、何が起こっているのか、どこで助けが止まるのかを隠しています。requestsは遅いわけでも、悪く書かれているわけでもありません。スクレイピングの文脈で唯一の欠点があり、それはAPIの入力とは関係ありません。ここが両クライアントの本当の違いであり、切り替えたときに何が変わるのか、そしてどのライブラリを選んでも偽装が決して修正しない1つのことです。
唯一重要な違い
両方のライブラリは同じヘッダーを送信します。違いは1層下にあり、HTTPバイトが移動する前に接続を開くTLSハンドシェイクにあります。requestsはurllib3とOpenSSLの上にあり、Pythonに属する暗号リスト、拡張セット、順序を広告します。curl_cffiは、ブラウザのClientHelloをバイト単位で再現するパッチ付きcurlのフォークへのバインディングであり、HTTP/2 SETTINGSフレームも同様です。そのため、JA3、JA3N、Akamaiのハッシュはスクリプトライブラリではなく、実際のChromeと一致します。アンチボットベンダーはこれらの署名のデータベースを保持しており、Chrome User-AgentヘッダーとPythonハンドシェイクの不一致は、言い逃れできない矛盾です。JA3とJA4のフィンガープリンティングでメカニズムを解説し、同じ効果がcurlが403を返すがブラウザが200を返す理由を説明します。
比較の他のすべては実装から派生します。curl_cffiがlibcurlをラップしているため、HTTP/2、HTTP/3、websockets、asyncioを継承しますが、requestsはこれらをサポートしたことがありません。requestsは純粋なPythonであるため、どこにでもインストールでき、10年のエコシステムを持っています。どちらのステートメントも同時に真であり、どちらが支配的になるかは完全にターゲットに依存します。
5分で実行できる再現可能なテスト
誰の合格率表も鵜呑みにしないでください。私たちのものも含めて。フィンガープリントの違いは直接観察可能です。両方のクライアントをTLSエコーエンドポイントに向け、返されたハッシュを比較してください。2つの行が一致する場合、ビルドは何も偽装していません。
# pip install requests curl_cffi
import requests
import curl_cffi
URL = "https://tls.browserleaks.com/json"
a = requests.get(URL, timeout=30).json()
b = curl_cffi.get(URL, impersonate="chrome", timeout=30).json()
print("requests ja3n:", a["ja3n_hash"], "| akamai:", a.get("akamai_hash", "-"))
print("curl_cffi ja3n:", b["ja3n_hash"], "| akamai:", b.get("akamai_hash", "-"))
# Two different ja3n hashes = impersonation is working. The curl_cffi README
# documents aa56c057ad164ec4fdcb7a5a283be9fc as a Chrome-matched ja3n value.
# The akamai (HTTP/2) fingerprint is usually empty for requests: it is
# HTTP/1.1 only, so there is no SETTINGS frame to fingerprint in the first place.
v0.15以降、同じチェックのワンライナー版があります: curl-cffi get tls.browserleaks.com/json --impersonate chrome。他のデバッグを始める前に実行してください。「偽装が誤って構成されている」から「偽装は正常で他の何かがブロックしている」という2つの全く異なる午後を分けます。

curl_cffiが修正しないこと: IPの評判
ここがライブラリを交換するというアドバイスが省略する部分です。TLSフィンガープリントは「これはどのソフトウェアか?」という質問に答えます。「どこから来ているのか?」については何も言いません。そしてその2番目の質問は、出口IPに対する別のルックアップによって答えられます: どのASNが所有しているのか、それはホスティングプロバイダーか消費者ISPか、悪用フィードに登場したことがあるか、過去1時間に同じアドレスからこのサイトに何回他のセッションがアクセスしたか。データセンター範囲のクラウドVMから到着する完璧なChromeハンドシェイクは、サーバーラックにインストールされたかのように見えるChromeブラウザです。それはpython-requestsよりも説得力があるわけではありません。場合によっては、矛盾がより鋭いため、より少ない説得力があります。
プロジェクト自身のFAQでは、IP品質をリクエスト率やJavaScriptフィンガープリントよりも優先して、偽装だけでは不十分な理由を説明しています。その順序は偶然ではありません。評判は防御者にとって評価が最も安価で、攻撃者にとって偽造が最も難しいシグナルです。ヘッダーや暗号リストとは異なり、ローカルで生成できないからです。requestsベースのスクレイパーがすでにデータセンタープールを通じて実行され、ブロックされていた場合、同じプールでcurl_cffiに移行すると、2つの失敗したチェックのうち1つが変わります。ソフトターゲットでは部分的な改善が見られ、ハードターゲットでは全く改善が見られません。これはまさに人々が報告する混乱した結果です。
その軸の修正はコードではなくアドレス品質です: 本物の消費者ISP割り当てからの住宅IP、これは200以上の国で9000万以上のアドレスを購入するものです。何も変更する前に現在の出口がどのように見えるかを確認したい場合、私たちの無料のIP品質チェッカーはターゲットが見るASNと分類を報告します。
どの軸が壊れているかを教えてくれる2x2
推測するのではなく、実際のターゲットに対して両方の変数を独立してテストしてください。4つのリクエスト、4つの出力行、結果が問題を特定します:
import requests
import curl_cffi
TARGET = "https://your-target.example/api/items"
DC = "http://USER:PASS@your-datacenter-gateway:PORT"
RES = "http://USER:PASS@gate.quantumproxies.io:PORT"
def probe(label, fn):
try:
print(f"{label:26} -> {fn().status_code}")
except Exception as e:
print(f"{label:26} -> {type(e).__name__}")
probe("requests + datacenter",
lambda: requests.get(TARGET, proxies={"https": DC}, timeout=30))
probe("curl_cffi + datacenter",
lambda: curl_cffi.get(TARGET, proxy=DC, impersonate="chrome", timeout=30))
probe("requests + residential",
lambda: requests.get(TARGET, proxies={"https": RES}, timeout=30))
probe("curl_cffi + residential",
lambda: curl_cffi.get(TARGET, proxy=RES, impersonate="chrome", timeout=30))
4つの結果を真理表のように読みます:
- データセンター行のみが失敗する — それはIPの評判です。クライアントは関係ありません。より良い出口を購入してください。
- requests行のみが失敗する — それはTLSフィンガープリントです。クライアントを切り替えて既存のプールを維持してください。
- 最後の行のみが通過する — 両方のチェックがライブです。偽装とクリーンなIPが一緒に必要です。これは真剣なターゲットでの一般的なケースです。
- すべての4つが失敗する — それはHTTPクライアントができることを超えています。それはJavaScriptチャレンジ、生成していないトークン、またはアカウントレベルのブロックを意味します。実際のブラウザまたは管理されたScraper APIを使用してください。
- すべての4つが通過する — あなたはフィンガープリントの問題を持っていませんでした。依存関係を追加しないでください。
1回ではなく数十回実行してください。両方のブロック層は確率的であり、1つの200はほとんど何も教えてくれません。

追加の依存関係が価値がないとき
率直さは書き直しよりも安価です。次の場合はrequestsに留まります:
- あなたが呼び出すことを許可されたAPIを呼び出している場合。 自分のキーを持つ文書化されたエンドポイントはフィンガープリントされません。そこに偽装を追加するのはカルトです。
- デプロイメントターゲットが厄介な場合。 curl_cffiはコンパイル済みのホイールを出荷し、v0.14以降Python 3.10以上が必要です。requestsは古いイメージや制約された組み込み環境を含むほぼすべてで動作します。
- requestsエコシステムに依存している場合。 カスタムアダプター、requests-cache、requests-oauthlibなどはすべて、curl_cffiが意図的に公開しないトランスポート層にフックします。
- ステータスコードのリトライが必要な場合。 urllib3の
Retryはstatus_forcelistで429と503をリトライします。curl_cffiのretryパラメータはトランスポート例外でのみ再実行します。 - ブロックが行動的な場合。 レート制限、アカウント禁止、セッションごとのクォータはハンドシェイクを全く気にしません。
そして、多くの人が見逃している中間の道があります: ハンドシェイクを得るためにrequestsを放棄する必要はありません。メンテナはcurl-adapterを指摘しており、これはcurl_cffiをrequestsのトランスポートアダプターとしてマウントし、httpx-curl-cffiはPyPIでhttpxに同じことを行います。既存のコードとエコシステムを維持し、ワイヤ上のバイトだけが変わります。
移行の注意点
APIはほとんどのスクリプトがインポートを変更した後に動作するほど近いですが、互換性ページには実際の違いが記載されており、大規模なポートの前に読む価値があります。リダイレクト応答ボディはResponse.historyに保持されません。空のドメインを持つクッキーはリダイレクトを超えて失われる可能性があります。ストリーミング応答オブジェクトはピクル化できませんが、通常の応答はできます。ファイルAPIはわずかに異なります。そして、トランスポートやアダプターは全くなく、ライブラリは意図的にlibcurl-impersonateに溶接されています。プロキシ設定も人をつまずかせる小さな方法で異なります:
# requests: dict, and retries mounted on an adapter
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
s = requests.Session()
s.mount("https://", HTTPAdapter(max_retries=Retry(total=4, status_forcelist=[429, 503])))
r = s.get(url, proxies={"https": "http://USER:PASS@gate.quantumproxies.io:PORT"}, timeout=30)
# curl_cffi: single proxy string preferred, retries are a session parameter
from curl_cffi import Session
from curl_cffi.requests import RetryStrategy
s = Session(
impersonate="chrome",
proxy="http://USER:PASS@gate.quantumproxies.io:PORT",
retry=RetryStrategy(count=4, delay=1.0, backoff="exponential"),
timeout=30,
)
r = s.get(url) # note: retry fires on transport errors, not on 429/503
完全なプロキシサーフェス — dictキー、proxy_auth、リクエストごとのローテーション、非同期、そして役に立たないWRONG_VERSION_NUMBERエラーを生成するhttps://プレフィックス — は、私たちのcurl_cffiプロキシガイドでステップバイステップでカバーされています。留まる場合、他の側の同等のリファレンスは私たちのPython Requestsプロキシガイドです。
よくある質問
curl_cffiはrequestsより速いですか?
はい、プロジェクトのベンチマークはrequestsではなく、aiohttpやpycurlと同等としています。利得は、Cで作業を行うlibcurlとHTTP/2多重化から来ており、巧妙なPythonからではありません。少数の連続呼び出しでは違いは目に見えませんが、高い同時実行性、特に非同期では顕著です。
curl_cffiは安全に使用できますか?
MITライセンスで広く展開され、事前コンパイルされたホイールを出荷しているため、監査するビルドステップはありません。1つの注意点は行動に値します: v0.15.0のアドバイザリはリダイレクトベースのSSRFをカバーしています。他の人が提供するURLを取得する場合、allow_redirects="safe"を設定するか、リダイレクトを無効にしてください。ブラウザを偽装することは技術的な手段であり、サイトの利用規約を無視する許可ではありません。
curl_cffiはCloudflareをバイパスしますか?
TLSとHTTP/2のフィンガープリントを除去し、基本的な保護レベルをクリアします。JavaScriptチャレンジを実行したり、Turnstileを解決したり、フラグが立てられた出口IPを修正することはできません。メンテナはFAQでそれを述べており、より良いプロキシプールとブラウザ自動化を高いレベルのために推奨しています。
curl_cffi vs httpxまたはtls_client — どれを使うべきですか?
httpxはHTTP/2と非同期を提供しますが、フィンガープリント偽装はありません。したがって、requestsとcurl_cffiの間に位置します。tls_clientもTLSプロファイルを偽装し、同様にベンチマークされます。curl_cffiはより大きなコミュニティを持ち、HTTP/3とwebsocketsを追加します。httpxがすでにスタックにある場合、httpx-curl-cffiトランスポートは書き直しなしで偽装を提供します。
短いバージョン: ターゲットがハンドシェイクを読むときはcurl_cffiに切り替え、そうでないときはrequestsに留まり、どちらの選択もデータセンターIPを洗浄することを期待しないでください。クライアントは1つの軸で異なり、プロキシは別の軸で異なり、ブロックされたスクレイパーはほとんど常に両方に関する話です。4つのプローブを実行し、真理表を読み、インターネットが叫んだ軸ではなくデータが指し示す軸を修正してください。