Python 设置代理

Python 里所有主流 HTTP 库都接受同一个代理地址:http://USERNAME:PASSWORD@HOST:PORT,从控制台“我的代理”页面的接入信息区域复制即可。它以 http:// 开头,访问 https:// 的目标也一样。本文以 requests 为主,每一步都给出一份可以直接保存运行的完整文件:先发第一个请求,再处理真正会出错的地方,即环境变量覆盖、密码里的特殊字符、超时、SOCKS5 依赖和连接复用,最后用一个对比检查确认 http:// 和 https:// 两种目标都走了代理。curl 和账号密码的基础见快速入门,返回 407 见代理返回 407 怎么办,本文不重复。

准备工作

  • Python 3 和 pip。本文示例在 Python 3.14 和 requests 2.34 上运行过,更早的 Python 3 版本和 requests 2.x 写法相同。Windows 用户在 PowerShell 或 cmd 里用 py -m pip 调用 pip,避免调到别的 Python。
  • 安装 requests:pip install requests。需要 SOCKS5 时再安装 pip install "requests[socks]"(PowerShell 里引号必须保留)。
  • 控制台里的账号密码和代理地址。免费套餐可以完成固定 IP 的 HTTP(S) 示例(每月 1 GB 流量);动态网关、住宅网关和 SOCKS5 需要对应的付费套餐,免费账号连接网关会返回 407。
  • 把代理地址放进环境变量 NODE4_PROXY_URL(形式 http://USERNAME:PASSWORD@HOST:PORT),示例从它读取,不要把账号密码写进代码或提交到仓库。Node4 生成的用户名和密码只含字母、数字、下划线和连字符,可以原样写进地址。

操作步骤

  1. 第一个请求:requests 加 proxies 字典

    目的:确认账号密码、地址和端口正确,并拿到出口 IP。依赖:requests。把下面的文件保存为 first.py,设置好 NODE4_PROXY_URL 后运行 python first.py(Windows 用 py first.py),应打印 200 和一个不是您本机的 IP 地址。

    字典的键是目标 URL 的协议,值是代理地址。https 这一项的值仍然以 http:// 开头:程序和 Node4 之间是明文 HTTP,访问 https 目标时库会让代理建立 CONNECT 隧道,然后在隧道里和目标做 TLS 握手,TLS 校验由您的机器照常完成,代理看不到内容。写成 https:// 会对一个明文端口做 TLS 握手,requests 报 ProxyError,提示 Your proxy appears to only use HTTP and not HTTPS,或 SSLError 的 wrong version number。这时改回 http://,不要用 verify=False 去绕,它掩盖了真正的原因并关掉了保护连接的 TLS 校验。两个键都要写:只写 https 这一项时,http:// 的请求会直接从本机发出,而且不报错(实测)。

    timeout 必须显式设置,requests 默认没有超时,不写的话卡住的请求会一直挂着。单个数值同时用于连接和读取两个阶段;需要分别控制时传两个值(连接,读取),示例用 (10, 30)。读取超时限制的是两次收到数据之间的间隔,不等于整次请求的总时限:一个慢慢持续返回数据的响应不会触发它。需要总时限时在程序层面另行控制(例如把任务放进带超时的线程或进程)。

    字段填写内容
    预期输出200 203.0.113.7 这样的一行:状态码加出口 IP,IP 不是本机的公网 IP
    失败时ProxyError 且信息里有 407:按 407 排查;connection refused 或 ConnectTimeout:没连上代理,核对地址和端口,见连接排查;KeyError: NODE4_PROXY_URL:环境变量没设;401:目标网站要自己的登录,与代理无关
    first.py
    # first.py  (Python 3, pip install requests)
    import os
    import requests
    
    proxy_url = os.environ["NODE4_PROXY_URL"]  # http://USERNAME:PASSWORD@HOST:PORT
    proxies = {"http": proxy_url, "https": proxy_url}
    
    r = requests.get("https://api.ipify.org", proxies=proxies, timeout=(10, 30))
    print(r.status_code, r.text)
  2. 环境变量会覆盖 Session 里的代理

    requests 会读取 HTTP_PROXY、HTTPS_PROXY、ALL_PROXY、NO_PROXY(大小写都认)。优先级要记清楚:按请求传入的 proxies= 参数最高,不受环境变量影响;session.proxies 低于环境变量,CI 或容器里残留的一个 HTTPS_PROXY 会悄悄替换掉代码里配置的代理,表现为 407 或走了另一个代理。代码应该是唯一来源时,创建 Session 后立刻设置 trust_env = False,第 5 步和最后的检查脚本都是这样写的。

    NO_PROXY 里列出的目标域名不走代理。只靠环境变量配置的问题是变量缺失时不报错,流量直接从本机发出,所以最后一步的对比检查很重要。换了机器或在 CI 里行为不同,先看 env | grep -i proxy(Windows 用 Get-ChildItem Env: | findstr -i proxy)。

    字段填写内容
    只信代码session = requests.Session(); session.trust_env = False; session.proxies = proxies
    按请求覆盖requests.get(url, proxies=proxies, timeout=(10, 30)),优先级高于环境变量
  3. SOCKS5:装 requests[socks],写 socks5h

    requests 自己不支持 SOCKS,需要安装附加依赖 pip install "requests[socks]",它会带上 PySocks。没装时用 socks5:// 地址会报 InvalidSchema: Missing dependencies for SOCKS support。

    两个值都写 socks5h,不是 socks5:socks5h 把域名交给代理、在出口解析;socks5 在本机先解析,每个访问的域名都经过您自己的解析器,按地区分流的网站会把您分到离本机近的节点而不是离出口近的。SOCKS5 的端口和 HTTP 的不同,以控制台接入信息区域切换到 SOCKS5 后显示的端口为准;例如动态数据中心网关的 SOCKS5 是 gw-rotating_datacenter.node4.io:1083,住宅网关是 1082 端口。付费产品都支持 SOCKS5,免费套餐仅 HTTP(S)。下面的文件按 requests 的 SOCKS 文档写,未在本文的测试环境中运行。

    字段填写内容
    安装依赖pip install "requests[socks]"
    成功判断同第 1 步;SOCKS5 认证失败没有 407 状态码,表现为连接被拒,排查时先用 HTTP 端口试一次
    socks.py
    # socks.py  (pip install "requests[socks]")
    import os
    import requests
    
    socks_url = os.environ["NODE4_SOCKS_URL"]  # socks5h://USERNAME:PASSWORD@HOST:SOCKS_PORT
    proxies = {"http": socks_url, "https": socks_url}
    
    r = requests.get("https://api.ipify.org", proxies=proxies, timeout=(10, 30))
    print(r.status_code, r.text)
  4. 密码里的特殊字符要做 URL 编码

    代理地址里的账号密码按 URL 的规则解析,密码含 @、:、/、#、% 时含义就变了:requests 可能报 InvalidURL,也可能把一个不同的密码发给代理,得到 407。Node4 生成的用户名和密码只含字母、数字、下划线和连字符,原样放进地址即可;自定义的值或来自其他工具的密码请先编码。

    用 urllib.parse.quote 分别编码用户名和密码再拼接,safe="" 要显式写,默认值不编码斜杠。不要手工编码,已编码的密码里的 % 会被编码两次。

    字段填写内容
    导入from urllib.parse import quote
    编码并拼接proxy = f"http://{quote('USERNAME', safe='')}:{quote('PASSWORD', safe='')}@HOST:PORT"
  5. Session:复用连接,也就复用出口 IP

    不是一次性脚本就用 Session。每新建一个连接都要重复进行到代理的 TCP 握手、一次 CONNECT 往返和一次与目标的 TLS 握手,Session 复用连接让这些开销只发生一次。连接数多时,给 HTTPAdapter 传 pool_maxsize 匹配并发数,否则多出来的连接开了又丢。保存为 session.py 运行,三行输出的 IP 相同:三次请求走的是同一条隧道。

    动态和住宅代理的用户请注意出口怎么分配:访问 HTTPS 网站时,每个 CONNECT 隧道使用一个出口 IP,隧道复用期间出口不变,所以 Session 内的 https 请求出口相同;SOCKS5 按连接分配出口;普通 HTTP 请求(http:// 目标)逐次选择出口,即使在同一个 Session 里。按状态码重试时,重试通常仍走同一个隧道、同一个出口;要重新选择出口就新建连接(新的 Session,或关闭 keep-alive),重新选择后仍可能分到之前的 IP。需要一段时间尽量保持同一出口,在用户名后加 -session-<id>,详见动态代理 IP 不变怎么办。

    创建 Session 时就设好 proxies,不要用过之后再改:requests 把直连和代理连接分开放在不同的池里,改了之后生效,但在直连时收集的 Cookie 会跟着发到代理请求里。用 with 语句包住 Session,退出时自动关闭连接。

    字段填写内容
    预期输出三行 200 加同一个 IP;动态网关下也相同,因为是一条隧道
    重试Retry 只重试 GET、HEAD 等幂等方法;按状态码重试通常复用原来的连接和出口,网络错误后的重试会新建连接,出口可能变
    session.py
    # session.py  (pip install requests)
    import os
    import requests
    from requests.adapters import HTTPAdapter
    from urllib3.util.retry import Retry
    
    proxy_url = os.environ["NODE4_PROXY_URL"]
    proxies = {"http": proxy_url, "https": proxy_url}
    retry = Retry(total=3, backoff_factor=1.5, status_forcelist=[429, 500, 502, 503, 504],
                  allowed_methods=["GET", "HEAD"])
    
    with requests.Session() as session:
        session.trust_env = False
        session.proxies = proxies
        session.mount("https://", HTTPAdapter(max_retries=retry, pool_maxsize=10))
        session.mount("http://", HTTPAdapter(max_retries=retry, pool_maxsize=10))
        for _ in range(3):
            r = session.get("https://api.ipify.org", timeout=(10, 30))
            print(r.status_code, r.text)

动态网关示例和 Windows 注意事项

动态数据中心代理(Rotating Datacenter)的 HTTP 网关是 gw-rotating_datacenter.node4.io:8083,把它作为 HOST:PORT 放进 NODE4_PROXY_URL 即可运行上面的示例;指定国家在用户名后加 -country-us 等参数,密码不变。分开运行两次 first.py 通常打印不同的 IP,偶尔相同,都是产品正常工作的表现;同一个 Session 内的多次 https 请求打印同一个 IP。需要动态网关或住宅网关的账号必须有对应的付费套餐。

Windows:在 PowerShell 里 pip 命令中的引号要保留,pip install "requests[socks]" 不加引号会被 PowerShell 解析掉方括号;py -m pip 比裸的 pip 更不容易调错解释器;设置环境变量用 $env:NODE4_PROXY_URL = "http://USERNAME:PASSWORD@HOST:PORT",cmd 里用 set NODE4_PROXY_URL=...。路径里有空格时用引号包住脚本路径。

确认流量走了代理

每次接入都用同一个出口检查收尾:直连查一次本机 IP,再通过程序实际使用的那份 Session 配置分别访问 https:// 和 http:// 的 IP 回显地址,两次结果都必须和直连不同。保存为 verify.py 运行。每次响应先查状态码,再用标准库 ipaddress 确认正文是一个 IP 地址:状态码不是 200、正文是错误提示、HTML 或空白,都抛出 RuntimeError,不会被当成出口 IP;任何一种目标没走代理时同样抛错。不用 assert,因为 python -O 会把 assert 整个去掉,检查就失效了。两个 Session 都写在 with 里,无论成败都会关闭。要查两种目标,是因为只给 https 设了代理、没给 http 设的情况下,https 那次照常通过,只有 http:// 那次会暴露(实测)。这个检查只证明到 IP 回显服务这一条路走了代理,不代表某个目标网站一定接受该代理。

把它放进部署和 CI,它能抓住过期的账号密码、没有被应用的代理配置、缺了一个键的 proxies 字典。程序从环境变量或 Session 取代理的话,检查也要走同一条配置:示例里的 Session 和您的程序应该由同一段代码构造,不要在检查里单独传 proxies=,它的优先级更高,会把问题盖住。走动态网关时两次代理请求可能打印不同的 IP,正常。

只想确认账号密码,向一个 http:// 的地址发一次请求(例如 http://api.ipify.org):密码错时代理返回可读的 407 状态码,而 https 目标会在建隧道时抛 ProxyError,信息里带 Tunnel connection failed: 407。回显类网站帮不上忙:Proxy-Authorization 头被代理消费掉,不会到达任何页面。

版本说明(最近验证 2026-11-06):first.py、session.py 和 verify.py 在 Python 3.14.3、requests 2.34.2、urllib3 2.8.0 上通过本地代理运行通过,包括 timeout=10 和 timeout=(10, 30) 两种写法;socks.py 和无头浏览器部分未在该环境验证。

verify.py
# verify.py  (pip install requests)
# Egress check: proves the route to an IP-echo service goes through the proxy.
# It does not prove that any other target accepts the proxy.
import ipaddress
import os

import requests

proxy_url = os.environ["NODE4_PROXY_URL"]
proxies = {"http": proxy_url, "https": proxy_url}


def egress_ip(session, url, timeout):
    """Only HTTP 200 with an IP address as the whole body counts.
    An error page, an HTML page or an empty body is a failure, not an exit IP."""
    r = session.get(url, timeout=timeout)
    body = r.text.strip()
    if r.status_code != 200:
        raise RuntimeError(f"{url}: HTTP {r.status_code} {body[:80]}")
    try:
        ipaddress.ip_address(body)
    except ValueError:
        raise RuntimeError(f"{url}: body is not an IP address: {body[:80]}") from None
    return body


with requests.Session() as direct:
    direct.trust_env = False
    mine = egress_ip(direct, "https://api.ipify.org", 10)

with requests.Session() as session:
    session.trust_env = False
    session.proxies = proxies
    via_https = egress_ip(session, "https://api.ipify.org", (10, 30))
    via_http = egress_ip(session, "http://api.ipify.org", (10, 30))

print(mine, via_https, via_http)
if mine == via_https or mine == via_http:
    raise RuntimeError("traffic is not leaving through the proxy")

常见问题

报 ProxyError,提示 Your proxy appears to only use HTTP and not HTTPS?

代理地址写成了 https:// 开头。改成 http://,https 这一项的值也以 http:// 开头。不要用 verify=False,它既不解决问题又关掉了 TLS 校验。

curl 能通,Python 返回 407?

多半是环境变量 HTTPS_PROXY 或 HTTP_PROXY 里有一个旧的代理,优先级高于 session.proxies。设置 session.trust_env = False,或按请求传 proxies=。再核对密码有没有需要 URL 编码的字符。

为什么每次请求 IP 都一样,动态代理不是会换吗?

因为用了 Session 或连接池,访问 HTTPS 网站时每个 CONNECT 隧道一个出口 IP,隧道复用期间出口不变。要重新选择出口,就新建连接(新的 Session,或关闭 keep-alive),注意这样会重复进行握手;重新选择后仍可能分到之前的 IP。普通 HTTP 请求(http:// 目标)则逐次选择出口。详见动态代理 IP 不变怎么办。

socks5:// 报 Missing dependencies for SOCKS support?

没有安装 SOCKS 依赖。运行 pip install "requests[socks]",然后把地址写成 socks5h://,端口换成该产品的 SOCKS5 端口。免费套餐没有 SOCKS5。

请求偶尔卡住几分钟不返回?

多半没有设超时:requests 默认不限时。请显式设置超时,单个数值同时用于连接和读取;需要分别控制时传两个值,例如 (10, 30)。读取超时不等于整次任务的总时限,一个持续缓慢返回数据的响应不会触发它,需要总时限时在程序层面另行控制。配合 Retry 做有限次数的重试,只重试 GET、HEAD 等幂等方法。

Scrapy 和无头浏览器怎么配?

Scrapy 的代理在下载中间件里配置,有自己的约定。Playwright 请在 launch 或 new_context 的 proxy 参数里传入 server、username 和 password,由 Playwright 回答代理的认证挑战,不要把代理账号密码填进 http_credentials(那是给目标网站的 HTTP 认证用的);Puppeteer 在启动参数里只传代理地址,账号密码用 page.authenticate 提供;Chromium 的代理命令行参数本身不携带账号密码。两者都在英文帮助中心有专门的文章,本文的 requests 写法不直接适用。