Cloudflare拦截Python requests请求却放行Postman请求的原因及解决方法
我完全懂你这种挫败感——明明Postman能轻松拿到200响应和目标数据,把Postman生成的代码直接搬到Python里用requests跑,就立刻被Cloudflare扔403。哪怕你把headers都逐字复制过去也没用,甚至Chrome和Firefox导出的curl在Postman里表现都不一样。咱们先把背后的原因说透,再给你落地的解决办法。
为什么会出现这种差异?
1. Cloudflare的会话Cookie是“一次性绑定”的
你从Postman里复制的__cf_bm和__cflb这类Cookie,是Cloudflare为当前会话生成的临时验证凭证。它们不仅有时效性(几分钟就会过期),还和生成它们的请求环境(比如浏览器的指纹、IP、会话上下文)深度绑定。直接把这些Cookie复制到Python的requests里,请求环境变了,Cloudflare就会判定这是“异常请求”,直接拦截。
2. 请求指纹的细微差异触发了拦截
Cloudflare的反爬系统(Bot Management)会检测全维度的请求指纹,不只是你能看到的User-Agent和Cookie:
- TLS握手参数:requests默认的TLS配置和Firefox/Postman不一样,Cloudflare会通过TLS的加密套件、版本、扩展字段来识别是不是“真实浏览器”
- HTTP头的顺序/细节:你提到Chrome里把
user-agent改成User-Agent才有用,虽然HTTP标准里头是不区分大小写的,但Cloudflare会把这种细节作为指纹的一部分;还有一些你没注意到的隐式头,比如Postman可能自动添加了X-Requested-With这类头,而requests默认不会加 - 会话连贯性:Postman会自动维护会话状态,而你用
requests.request每次都是独立请求,更像“机器人”的行为
你说Firefox导出的curl在Postman里直接能用,Chrome的却要删一堆头才行,本质就是Firefox的请求指纹更符合Cloudflare的“可信浏览器”模板,而Chrome的一些默认头被判定为“可疑”。
3. Postman的底层请求逻辑更贴近浏览器
Postman本身是基于Chromium内核的,它的请求发送逻辑和浏览器更接近,而requests是基于urllib3的纯HTTP库,底层的请求特征和浏览器有本质区别,哪怕你手动加了所有可见的头,隐藏的差异还是会被Cloudflare抓到。
解决办法,从临时到长期
方法1:临时测试——实时更新有效Cookie
这是最快的临时解决方案,适合单次测试:
- 打开Firefox,访问目标页面(或者直接打开你要请求的API URL)
- 打开开发者工具(F12)→ 网络面板,找到对应请求,复制最新的
__cf_bm和__cflbCookie值 - 把这些Cookie替换到Python代码里,立刻运行,这时候大概率能拿到200响应
⚠️ 注意:这个方法只能用几分钟,Cookie过期后就会再次403,不适合长期爬取。
方法2:优化请求指纹,模拟真实浏览器
如果要长期用requests,得把请求的每一个细节都对齐Firefox的请求:
- 补全所有浏览器级别的HTTP头
不要只留User-Agent和Cookie,把Firefox请求里的所有头都复制过来,比如Accept、Accept-Language、Accept-Encoding、Referer、Connection、Sec-Fetch-*系列头 - 用requests.Session维护会话
Session会自动维护Cookie和会话状态,比每次用requests.request更像浏览器的连贯请求 - 调整TLS配置,匹配Firefox
requests默认的TLS加密套件和Firefox不一样,可以通过适配器修改:
优化后的代码示例:
import requests from requests.adapters import HTTPAdapter from urllib3.util.ssl_ import create_urllib3_context # 模拟Firefox的TLS加密套件 FIREFOX_CIPHERS = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:RSA-AES128-GCM-SHA256:RSA-AES256-GCM-SHA384:RSA-AES128-SHA:RSA-AES256-SHA" class FirefoxTLSAdapter(HTTPAdapter): def init_poolmanager(self, *args, **kwargs): context = create_urllib3_context(ciphers=FIREFOX_CIPHERS) kwargs['ssl_context'] = context return super().init_poolmanager(*args, **kwargs) url = "https://io.dexscreener.com/dex/pair-details/v3/solana/HKj2acbuMto92TvjtBfpPyG2im28og5b647gh1K62CAm" # 完全复制Firefox的请求头 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0', 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'en-US,en;q=0.5', 'Accept-Encoding': 'gzip, deflate, br, zstd', 'Referer': 'https://dexscreener.com/', 'Connection': 'keep-alive', 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-site', # 替换成你刚从Firefox复制的最新Cookie 'Cookie': '__cf_bm=Pa25nvnzsUCW1AnI5oCV6ZDLXV8HY.VjsZM1squ8D30-1735078602-1.0.1.1-7WIFetntuMUCOgKEH0XlF8uKq8e4t5sS403X8XEwzkrACUTs1woAlaXzzmN65nZksyujfMGKiD_HqyD9MRED6mASCQxtAbySJGLSyGVVvII; __cflb=04dTof7UnGZLJbSktrXU5s6TEcm2ZGkWquifmfRNx5' } # 创建会话并挂载自定义TLS适配器 session = requests.Session() session.mount('https://', FirefoxTLSAdapter()) response = session.get(url, headers=headers) print(response.text)
方法3:用专门的库绕过Cloudflare验证
如果上面的方法还是被拦,就用专门针对Cloudflare的工具,这些库会自动处理JS验证、Cookie生成和指纹模拟:
用cloudscraper(推荐,轻量)
cloudscraper是requests的封装,会自动处理Cloudflare的Turnstile和JS验证,用法和requests几乎一样:
import cloudscraper # 创建模拟浏览器的scraper scraper = cloudscraper.create_scraper(browser={'browser': 'firefox', 'platform': 'windows', 'mobile': False}) url = "https://io.dexscreener.com/dex/pair-details/v3/solana/HKj2acbuMto92TvjtBfpPyG2im28og5b647gh1K62CAm" response = scraper.get(url) # 直接获取JSON数据 print(response.json())
用undetected-chromedriver(适合更严格的验证)
如果Cloudflare的验证级别很高,用修改过的ChromeDriver来模拟真实浏览器,它会隐藏所有自动化痕迹,比普通Selenium更难被检测:
from undetected_chromedriver import Chrome, ChromeOptions import json options = ChromeOptions() options.add_argument('--headless=new') # 无头模式,不弹出浏览器 options.add_argument('--disable-gpu') options.add_argument('--no-sandbox') # 启动浏览器 driver = Chrome(options=options) driver.get("https://io.dexscreener.com/dex/pair-details/v3/solana/HKj2acbuMto92TvjtBfpPyG2im28og5b647gh1K62CAm") # 获取页面的JSON内容(因为API返回的是纯JSON,直接取页面源码) data = json.loads(driver.page_source) print(data) driver.quit()
最后补充
你提到用Selenium/无头Selenium能成功,本质就是因为它们模拟了真实浏览器的所有特征,而requests是纯HTTP库,缺少这些“浏览器指纹”。上面的方法就是在requests里补全这些指纹,或者直接用工具帮你做这件事。
备注:内容来源于stack exchange,提问作者nilesh jodhani




