Python · requests、httpx、aiohttp

Python 移动代理配置指南

所有主流 Python HTTP 客户端都原生支持代理,所以无需安装任何东西,也无需学习厂商 SDK——接入只需一个环境变量和一个参数。真正值得做好的是底层细节:复用连接、正确重试,以及在长任务写入错误数据之前,先断言出口确实在您预期的位置。

问题所在:简单的部分很简单,稳定运行却不简单

让 requests 走代理只需一行代码,而大多数教程也就止步于此。问题会在之后出现:代码用的是 requests.get 而不是 Session,导致每次调用都新建 TCP 连接和 CONNECT 隧道,任务慢了好几倍;重试循环不遵守 Retry-After,反复冲击 429;异步客户端同时打开数百条隧道,被当作攻击。这些问题在报错堆栈里看起来都不像代理问题,这正是它们如此耗费时间的原因。

如果您遇到过以下情况

  • 吞吐量远低于连接应有的水平,大部分时间花在建立连接上。
  • 并发时出现 ProxyError 或 TunnelError,单线程运行时却消失。
  • 添加代理后出现 SSL 错误,通常是因为试图对代理而不是目标网站做证书校验。
  • 重试策略退避太少甚至不退避,429 越来越多。
  • 任务跑完,数据却全是错误地区的,因为没有任何地方断言出口国家。

可运行的代码

只要 PROXY_URL 中填入真实的连接字符串,下面的每段代码都能直接运行。首次充值后,可在控制台复制您的连接字符串。

设置连接 URL

bash
export PROXY_URL="http://USERNAME-mbl-us-rot-auto10:[email protected]:7000"

# 同一个字符串同时适用于 http:// 和 https:// 目标——
# 客户端会自动为 TLS 建立 CONNECT 隧道。

requests — 一个可以全天运行的 Session

python
import os
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

PROXY = os.environ["PROXY_URL"]

session = requests.Session()
session.proxies = {"http": PROXY, "https": PROXY}

retry = Retry(
    total=4,
    backoff_factor=1.5,                    # 1.5 秒、3 秒、6 秒、12 秒
    status_forcelist=(429, 500, 502, 503, 504),
    respect_retry_after_header=True,
    allowed_methods=frozenset(["GET", "HEAD"]),
)
# pool_maxsize 让隧道保持存活,而不是每个请求都重新连接。
session.mount("https://", HTTPAdapter(max_retries=retry, pool_maxsize=20))
session.mount("http://", HTTPAdapter(max_retries=retry, pool_maxsize=20))

# 在任务写入任何数据之前,先断言出口位置。
who = session.get("https://ipinfo.io/json", timeout=30).json()
print(f"exit {who['ip']} — {who['country']} — {who['org']}")

r = session.get("https://example.com", timeout=30)
print(r.status_code, len(r.content))

Session 加 pool_maxsize,就是"复用一条隧道"与"每个请求一条隧道"的区别。

httpx — 同一份配置,同步异步皆可

python
import os, asyncio, httpx

PROXY = os.environ["PROXY_URL"]

# 同步
with httpx.Client(proxy=PROXY, timeout=30.0) as client:
    print(client.get("https://api.ipify.org?format=json").json())

# 异步——请限制并发数,否则每个任务都会打开一条隧道。
async def main(urls):
    limits = httpx.Limits(max_connections=10, max_keepalive_connections=10)
    sem = asyncio.Semaphore(10)
    async with httpx.AsyncClient(proxy=PROXY, limits=limits, timeout=30.0) as c:
        async def one(u):
            async with sem:
                r = await c.get(u)
                return u, r.status_code, len(r.content)
        return await asyncio.gather(*(one(u) for u in urls))

print(asyncio.run(main(["https://example.com"] * 5)))

httpx 0.26 及以上版本使用 proxy=,旧版本使用 proxies=。请显式限制并发数——不受限的异步请求正是客户端被限流的原因。

aiohttp — 代理按请求设置

python
import os, asyncio, aiohttp

PROXY = os.environ["PROXY_URL"]

async def main():
    conn = aiohttp.TCPConnector(limit=10)
    async with aiohttp.ClientSession(connector=conn) as s:
        # aiohttp 的 proxy= 要写在每次调用上,而不是会话上。
        async with s.get("https://api.ipify.org?format=json", proxy=PROXY) as r:
            print(r.status, await r.json())

asyncio.run(main())

常见陷阱:在 ClientSession 上设置 proxy 不起作用——它必须设置在每个请求上。

无需改代码即可切换国家或会话

python
import os, re

def with_country(url: str, cc: str) -> str:
    return re.sub(r"-(us|gb|de|fr|es|nl|pl|ch|pa|am|ge)-", f"-{cc}-", url, count=1)

def with_session(url: str, sid: str) -> str:
    if "-sid-" in url:
        return re.sub(r"-sid-[a-z0-9_]+", f"-sid-{sid}", url)
    return url.replace("-rot-", f"-sid-{sid}-rot-")

base = os.environ["PROXY_URL"]
print(with_country(base, "de"))
print(with_session(with_country(base, "fr"), "worker07"))

路由信息都在用户名字符串中,所以切换市场只是一次字符串替换,而不是重新部署。

应该使用哪种轮换模式

推荐-rot-auto10

对于常见的 Python 任务——爬取大量相互独立、无需保持会话的 URL——auto10 是合适的默认选择。您无需做任何编排,大约每 10 分钟就会获得一个新的运营商 IP,长任务因此分散到多个出口上。轮换期间请继续使用同一个 requests.Session:Session 是您的连接池和 cookie 容器,不受请求之间出口变化的影响。

何时选择其他模式

模式适用场景
sticky任何有状态的操作——已登录的账户、多步骤表单、购物车。为每个 worker 加上一个独立的 -sid-。
auto30长时间后台任务,较慢的 IP 更替已经足够,并希望隧道保持更久。
none临时的一次性脚本,不在乎调用之间出口如何变化。

预计带宽用量

从 Python 发起普通 HTTP 请求是使用代理池最省钱的方式,因为您只下载请求的内容,别无其他。在这类任务中,1 GB 流量能用很久。

任务类型单次用量每 GB 可完成
抓取 HTML 页面(典型内容网站)100–400 KB约 2,600–10,000 页
JSON API 响应2–50 KB约 20,000–500,000 次调用
启用 gzip/brotli 压缩的 HTML30–120 KB约 8,700–35,000 页
HEAD 请求 / 状态检查约 1 KB几乎不消耗流量

请发送 Accept-Encoding: gzip, br——requests 和 httpx 默认会发送,但手写的请求头经常把它覆盖掉,而仅压缩一项通常就能减少 70% 的 HTML 传输量。只需要状态码或某个响应头时使用 HEAD;对大响应使用 stream=True 流式读取,拿到所需内容后即可中止,不必为剩余部分付费。以上为典型范围,请先对自己的目标计量,再做规划。

带宽价格每 GB节省
1 GB$7$7—
5 GB$35$7—
10 GB$68$6.803%
25 GB$150$614%
50 GB$283$5.6619%
100 GB$500$529%

适用国家

修改代理用户名中的一个参数即可切换国家——无需重新部署,也无需第二个账户。 IP 池规模因国家而异,也取决于您使用的是移动(mbl) 还是住宅(peer)IP 池;实时可用情况请查看各国家页面和您的控制台。

仅限合法用途

ProxyMobile 仅用于在您拥有或已获授权测试的系统上进行测量、质量保证和研究。购买带宽并不代表您获得了访问第三方系统的许可。您仍需自行遵守每个目标网站的服务条款、robots 指令和速率限制,以及适用于您的数据保护法律。

本场景的使用要求

  • 只从您拥有或已获授权采集的网站采集数据,并遵守其条款。
  • 遵守 robots 指令、Retry-After 和任何公开的速率指引——重试策略不仅关乎稳定性,也是合规的一部分。
  • 限制并发数。无论您的本意如何,对同一主机发起不受限的异步请求,与攻击无异。
  • 没有合法依据时不得采集个人数据,也不要保留您本不需要采集的内容。

任何套餐均严禁

  • 禁止撞库、盗用账户,或任何冒充他人进行身份验证的行为。
  • 禁止绕过您未获授权测试的访问控制、付费墙或安全措施。
  • 禁止收集您没有合法依据处理的个人数据。
  • 禁止产生合理运营方会视为拒绝服务攻击的负载——请限速,遇到 429 时退避,并遵守 Retry-After。

用于欺诈、入侵或批量滥用的账户将被终止且不予退款,我们会配合有效的法律程序。完整条款请参阅我们的可接受使用政策。

Python常见问题

需要特殊的库或 SDK 吗?

不需要。requests、httpx、aiohttp、urllib3 和 Scrapy 都原生支持标准 HTTP 代理。您只需设置一个代理 URL,代码的其他部分保持不变。

为什么加了代理后爬虫变慢了?

几乎都是因为每次调用都新建了连接。整个任务请使用同一个 requests.Session(或同一个 httpx.Client),并调大 pool_maxsize,这样 CONNECT 隧道只需建立一次并被复用,而不是每个请求都建立一次。

如何通过代理访问 HTTPS?

同一个 URL 同时适用于两种协议——对于 TLS 目标,客户端会自动建立 CONNECT 隧道。请保持证书校验开启;它校验的是目标网站的证书,而不是代理,所以没有理由关闭它。

httpx 中 proxy= 和 proxies= 有什么区别?

httpx 0.26 及以上版本接受单个 proxy= 参数;更早的版本使用 proxies= 映射。如果遇到"unexpected keyword argument"错误,请核对已安装的版本与示例所针对的版本是否一致。

为什么在 aiohttp 的 ClientSession 上设置 proxy 不起作用?

因为 aiohttp 是按请求而不是按会话设置代理的。请像上面的示例那样,在每次 .get() 或 .post() 调用中传入 proxy=PROXY。

Python 爬虫应使用哪种轮换模式?

无状态地爬取大量独立 URL 时使用 auto10。一旦需要在请求之间保持登录、购物车或其他状态,就改用粘性会话,并为每个 worker 设置独立的 -sid-。

相关指南

用您自己的目标网站试一试

本页的带宽估算是如实给出的参考范围,而非承诺——唯一重要的数字,是您的目标网站实际消耗的流量。首次充值额外赠送 $2,足够您在确定预算之前,先实际计量一次运行。