You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python Requests中TLS握手配置调整及403问题解决问询

Python Requests TLS握手填充导致403:如何取消握手填充?

用Python Requests爬取目标站点已有一年,近一周每次请求均返回HTTP 403错误,但浏览器和curl工具可正常访问。通过WireShark抓包发现核心差异:Python/OpenSSL的TLS握手会添加填充,而浏览器和curl则不会。现需调整TLS握手行为取消填充,问题如下:

  • 是否可通过Requests的配置选项直接实现该修改?
  • 若不行,如何通过修改SSL库调用或源码实现?

返回403的Python Requests代码

import requests

s = requests.session()

s.headers = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'Accept-Language': 'pt-BR,pt;q=0.9',
    'Cache-Control': 'max-age=0',
    'Connection': 'keep-alive',
    'DNT': '1',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'none',
    'Sec-Fetch-User': '?1',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
    'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
}

response = s.get(
    'https://www63.bb.com.br/portalbb/djo/id/resgate/dadosResgate.bbx'
)

可正常访问的curl命令

curl "https://www63.bb.com.br/portalbb/djo/id/resgate/dadosResgate.bbx" ^
  -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" ^
  -H "Accept-Language: pt-BR,pt;q=0.9" ^
  -H "Cache-Control: max-age=0" ^
  -H "Connection: keep-alive" ^
  -H "DNT: 1" ^
  -H "Sec-Fetch-Dest: document" ^
  -H "Sec-Fetch-Mode: navigate" ^
  -H "Sec-Fetch-Site: none" ^
  -H "Sec-Fetch-User: ?1" ^
  -H "Upgrade-Insecure-Requests: 1" ^
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" ^
  -H "sec-ch-ua: \"Chromium\";v=\"130\", \"Google Chrome\";v=\"130\", \"Not?A_Brand\";v=\"99\"" ^
  -H "sec-ch-ua-mobile: ?0" ^
  -H "sec-ch-ua-platform: \"Windows\""

解决方案

1. Requests原生配置无法直接控制TLS握手填充

Requests本身没有暴露控制TLS握手填充的配置选项,因为这属于底层OpenSSL的行为,Requests的SSL配置基于urllib3和Python标准库ssl模块,而ssl模块默认未提供该级别的控制接口。

2. 可行的实现方式

方法一:使用curl_cffi模拟curl的TLS行为(最简单可靠)

curl_cffi直接调用curl的底层实现,TLS握手逻辑与curl完全一致,可彻底避免填充差异导致的403。

首先安装依赖:

pip install curl_cffi

修改后的代码:

from curl_cffi import requests

s = requests.Session()
s.headers = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'Accept-Language': 'pt-BR,pt;q=0.9',
    'Cache-Control': 'max-age=0',
    'Connection': 'keep-alive',
    'DNT': '1',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'none',
    'Sec-Fetch-User': '?1',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
    'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
}

# 模拟Chrome 130的TLS行为,与curl匹配
response = s.get(
    'https://www63.bb.com.br/portalbb/djo/id/resgate/dadosResgate.bbx',
    impersonate="chrome130"
)

方法二:自定义SSL上下文(原生Requests适配)

若坚持使用原生Requests,可通过自定义SSL上下文,禁用部分可能导致指纹差异的选项,并匹配浏览器的TLS参数,虽无法完全取消填充,但可缩小差异:

import requests
from requests.adapters import HTTPAdapter
from urllib3.poolmanager import PoolManager
import ssl

class TLSAdapter(HTTPAdapter):
    def init_poolmanager(self, *args, **kwargs):
        # 创建自定义SSL上下文
        ctx = ssl.create_default_context()
        # 匹配浏览器的TLS版本范围
        ctx.min_version = ssl.TLSVersion.TLSv1_2
        ctx.max_version = ssl.TLSVersion.TLSv1_3
        # 禁用压缩和会话票证,减少指纹差异
        ctx.options |= ssl.OP_NO_COMPRESSION
        ctx.options |= ssl.OP_NO_TICKET
        # 使用浏览器常用的椭圆曲线
        ctx.set_ecdh_curve('secp256r1')
        # 禁用不必要的扩展
        ctx.options |= ssl.OP_NO_RENEGOTIATION
        
        kwargs['ssl_context'] = ctx
        return super().init_poolmanager(*args, **kwargs)

# 挂载适配器到会话
s = requests.Session()
s.mount('https://', TLSAdapter())
s.headers = {
    # 保持原headers不变
}

response = s.get('https://www63.bb.com.br/portalbb/djo/id/resgate/dadosResgate.bbx')

方法三:底层修改OpenSSL行为(复杂)

若需彻底取消TLS握手填充,需通过pyopenssl或cryptography库直接操作OpenSSL的Client Hello回调,手动构建无填充的Client Hello。该方法实现难度大,需对TLS协议和OpenSSL API有深入了解,示例如下(基于pyopenssl):

首先安装依赖:

pip install pyopenssl
import requests
from requests.adapters import HTTPAdapter
from urllib3.contrib.pyopenssl import PyOpenSSLContext
from urllib3.poolmanager import PoolManager
from OpenSSL import SSL

def client_hello_callback(conn, client_hello):
    # 手动修改Client Hello,移除填充字段
    # 需解析并重构Client Hello数据包,此处仅为示例框架
    modified_hello = client_hello[:-4]  # 假设最后4字节是填充,实际需根据协议解析
    return modified_hello

class NoPaddingTLSAdapter(HTTPAdapter):
    def init_poolmanager(self, *args, **kwargs):
        ctx = PyOpenSSLContext(SSL.TLSv1_2_METHOD)
        # 设置Client Hello回调
        ctx.set_client_hello_callback(client_hello_callback)
        kwargs['ssl_context'] = ctx
        return super().init_poolmanager(*args, **kwargs)

# 使用适配器
s = requests.Session()
s.mount('https://', NoPaddingTLSAdapter())
# 设置headers并请求

注意:该方法需要精准解析TLS Client Hello的结构,极易出错,仅适合高级场景。


内容的提问来源于stack exchange,提问作者Mauro Moreira

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 21:55:54