Hướng Dẫn Sử Dụng
如何在 Python Requests 中处理包含特殊字符的代理密码

目录
配置 Python Requests 代理时,一个常见问题是登录凭据在其他工具中可用,但放入 URL 后却无法使用。原因可能在于用户名和密码的表示方式,尤其是当它们包含 @、:、/、# 或 % 等字符时。
要处理包含特殊字符的代理密码,需要区分登录数据和 URL 语法。解决方法是分别对各个凭据组成部分进行百分号编码,然后再拼接成代理地址。
为什么特殊字符会导致代理 URL 被错误解析?
带身份验证的代理 URL 通常采用 scheme://username:password@host:port 结构。其中,冒号用于分隔用户名和密码,@ 符号则标记凭据部分的结束位置。
如果密码包含具有语法含义的字符,却被直接插入 URL,解析器可能会错误判断各个组成部分之间的边界。例如,# 符号可能被视为片段标识符的起始符,而 / 符号可能被理解为路径的起点。
有些库仍能处理部分未编码的情况。但是,不应依赖这种行为,因为不同解析器、库版本或中间工具的处理结果可能不同。从一开始就采用正确的表示方式,有助于保持配置的一致性。
需要注意的字符
- @ 和 : 与凭据结构直接相关。
- / 和 ?: 在确定路径和查询字符串时具有特殊含义。
- #: 可能被理解为片段标识符的起始符。
- %: 是百分号编码序列的起始字符。
- 空格和 Unicode 字符: 需要转换为适用于 URL 的表示形式。
什么是代理百分号编码?
代理百分号编码是一种使用百分号加上对应字节的十六进制值来表示字符的方法。例如,字符 @ 表示为 %40,# 表示为 %23,% 表示为 %25。
在 Python 中,urllib.parse.quote 函数可以完成这项工作。对于代理 URL 中的用户名和密码,应传入 safe='',以避免按照默认设置保留斜杠。
注意:百分号编码并不是安全加密。获取 URL 的人仍然可以还原登录凭据。因此,包含账号信息的代理 URL 必须像密码本身一样受到保护。
如何使用 Python 正确编码代理 URL
分别编码用户名和密码
最重要的原则是只对各个凭据组成部分分别进行编码。不要对整个 URL 应用 quote,因为这样会连必要的分隔符也一起转换。
以下示例使用虚构信息进行说明:
from urllib.parse import quote
username = 'user@example'
password = 'p@ss:word/#%'
encoded_username = quote(username, safe='')
encoded_password = quote(password, safe='')
此时,用户名变为 user%40example,密码变为 p%40ss%3Aword%2F%23%25。属于登录数据的字符便不会再与 URL 分隔符混淆。
编码后再拼接 URL
完成上述步骤后,再将各个组成部分拼接成代理 URL。以下示例从环境配置中获取地址和端口:
import os
proxy_host = os.environ['PROXY_HOST']
proxy_port = os.environ['PROXY_PORT']
proxy_url = f'http://{encoded_username}:{encoded_password}@{proxy_host}:{proxy_port}'
该示例假设 PROXY_HOST 是有效的主机名或 IPv4 地址,不包含协议方案或登录凭据。如果使用 IPv6 字面地址,URL 中的地址部分需要用方括号括起来。
完整配置 Python Requests 代理
在实际应用中,应从配置来源获取用户名和密码,而不是直接写入源代码。以下代码使用原始凭据构建 URL,然后配置一个 Session:
import os
import requests
from urllib.parse import quote
username = quote(os.environ['PROXY_USERNAME'], safe='')
password = quote(os.environ['PROXY_PASSWORD'], safe='')
host = os.environ['PROXY_HOST']
port = os.environ['PROXY_PORT']
proxy_url = f'http://{username}:{password}@{host}:{port}'
session = requests.Session()
session.trust_env = False
session.proxies.update({'http': proxy_url, 'https': proxy_url})
response = session.get(os.environ['TARGET_URL'], timeout=float(os.environ['REQUEST_TIMEOUT']))
response.raise_for_status()
在此配置中,REQUEST_TIMEOUT 必须是应用选定的有效超时值。TARGET_URL 应指向你有权访问的资源,以便测试连接。
session.trust_env = False 可以避免测试过程受到环境中代理配置的影响。不过,该选项还会影响 .netrc 信息的使用以及某些通过环境设置的证书配置。只有在你主动管理相关配置时才应使用。
区分 https 键和代理的协议方案
proxies 字典中的 'https' 键指定用于 HTTPS 目标 URL 的代理。它并不意味着代理 URL 必须以 https:// 开头。
如果服务商提供的是 HTTP 代理,同一个以 http:// 开头的 URL 可以用于这两个键。对于 HTTPS 目标,通常通过 CONNECT 方法建立连接。只有当代理服务确实支持通过 TLS 连接到代理时,才应为代理使用 https:// 协议方案。
处理特殊字符时的常见错误
对整个 URL 进行编码
当 proxy_url 已经是完整地址时,quote(proxy_url, safe='') 这种写法并不合适。它还会编码协议方案中的冒号和斜杠,导致结果无法保持正确的代理 URL 结构。
请保留协议方案、主机、端口和分隔符。只在拼接前对 username 和 password 应用 quote。
对已经进行百分号编码的数据重复编码
如果原始密码包含 @,编码结果为 %40。再次编码该字符串时,% 会变为 %25,从而生成 %2540。发送给代理的信息可能不再对应原始密码。
应明确规定 URL 构建函数的输入是原始登录凭据。不要自动解码所有包含 % 的字符串,因为真实密码本身可能就包含字面字符串 %40。
使用 quote_plus 代替 quote
quote_plus 函数通常用于表单数据和查询字符串约定,其中空格会转换为加号。URL 的凭据部分不应默认采用这种约定。
要明确表示 username 或 password 中的空格,请使用 quote(value, safe='')。空格将被转换为 %20。
混淆代理身份验证与目标服务器身份验证
Requests 的 auth= 参数通常用于目标服务器的身份验证,在此场景下不能直接替代代理凭据。同样,也不应擅自将代理信息加入发送给网站的 Authorization 请求头。
将账号信息放入 URL 的方式,适用于采用 Requests 在该配置下所支持的身份验证机制的代理,通常是 Basic。如果代理要求其他机制,则需要使用相应的身份验证方案;百分号编码只解决数据的表示问题。
代理仍无法工作时的排查流程
修正代理 URL 编码后,应逐层检查,而不是继续随意更改密码:
- 检查输入数据:确认账号仍然有效,且没有意外的空格或换行符。
- 检查 URL 结构:确保主机、端口和协议方案与服务商提供的信息一致。
- 检查编码步骤:确认仅在构建 URL 时对原始 username 和 password 进行 quote 编码。
- 检查连接:区分域名解析错误、连接错误、超时和身份验证错误。
- 检查环境配置:查看 HTTP_PROXY、HTTPS_PROXY 和 NO_PROXY 是否影响请求。
- 检查服务策略:确认代理是否要求登记源 IP,或存在相关访问限制。
要求代理身份验证的响应通常与账号、密码或身份验证机制有关。相反,证书错误和隧道建立错误可能与 TLS、协议方案或连接策略有关。不应将所有 Requests 错误都视为特殊字符问题。
也不应使用 verify=False 来解决此问题。该选项会禁用相关连接的 TLS 证书验证,并不能修复 URL 解析错误。
在日志记录和部署过程中保护登录凭据
不要将 proxy_url 打印到屏幕上,也不要将完整 URL 发送到错误跟踪系统。经过百分号编码的字符串仍然包含可还原的登录凭据。异常消息在保存或分享之前也需要检查。
- 将凭据存储在秘密管理系统或适当的配置机制中。
- 需要诊断时,只记录主机、端口和检查状态。
- 在支持报告中同时隐藏 username 和 password。
- 如果完整 URL 已被公开,请更换登录凭据。
还需要单独评估到代理之间的传输安全性。百分号编码无法防止密码在传输途中被读取;对于使用 Basic 身份验证的 HTTP 代理,即使目标网站使用 HTTPS,到代理这一段连接中的代理凭据也不会因此受到 TLS 保护。
总结
处理包含特殊字符的代理密码的方法是,对 username 和 password 分别使用 urllib.parse.quote 并设置 safe='',然后再拼接 URL。同时,应统一使用原始输入,避免重复编码。
配置 Python Requests 代理时,还应检查协议方案、身份验证机制以及环境的影响。这种方法有助于区分 URL 表示错误与连接错误,同时降低诊断过程中泄露登录凭据的风险。
相关文章