用 Python 做代理轮换,大约十行
requests 和 httpx 的可用配置,外加两个让人误以为轮换坏了、实际上是连接池在作怪的坑。
轮换比多数教程写得要简单。用轮换入口的话,你不需要自己维护一个 IP 列表。你只连一个主机,服务商每条请求给你换一个 IP。
下面所有代码都用 HOST、PORT、USERNAME 和 PASSWORD 作占位符。ProxyPanda 没有一个可以从博客里直接抄走的统一网关地址:控制台在每个服务的页面上列出主机、端口和凭据,请从那里复制。
全部代码
import requests
PROXY = "http://USERNAME:PASSWORD@HOST:PORT"
proxies = {"http": PROXY, "https": PROXY}
for url in urls:
r = requests.get(url, proxies=proxies, timeout=30)
print(r.status_code, len(r.content))
就这些。住宅服务在控制台里设成随机 IP 时,每条请求都从不同的 IP 出去。
想亲眼看到轮换,就向一个回显 IP 的服务问几次:
for _ in range(3):
print(requests.get("https://httpbin.org/ip", proxies=proxies, timeout=30).json())
三次调用,三个不同的 origin 地址。
让几条请求保持同一个 IP
有时候你需要 IP 别动:先登录,再访问登录之后的页面。在控制台里把住宅服务切换成粘性 IP,并设定会话保持多久。控制台会生成一个带会话 ID 的凭据,按显示的样子整串复制即可。
STICKY = "http://USERNAME:STICKY_PASSWORD@HOST:PORT"
sticky = {"http": STICKY, "https": STICKY}
同一个会话 ID,同一个出口 IP,在你选定的时长内,网络能保持多久就保持多久。想换新的,就在控制台里生成一个新会话。出口 IP 属于某户人家的宽带,那条连接断了,粘性会话也可能提前结束;所以把登录这一步写成可以重跑的。
两个坑
坑一:复用 Session 还指望它轮换。
s = requests.Session()
s.proxies = proxies
Session 会保持连接。对 HTTPS 目标来说,一个被复用的连接就是穿过代理的同一条隧道,所以出口 IP 也是同一个。轮换不会发生,因为你没建立新连接。如果你要每条请求换一个 IP,要么别用 session,要么发 Connection: close。
这是「你们的轮换坏了」这类反馈里最常见的一条,而且几乎每次都是这个原因。
坑二:没处理重试。
轮换意味着有些请求会落在目标站已经不喜欢的 IP 上。这很正常,而且正是轮换奏效的原因:你重试一次,就换到另一个。
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
retry = Retry(total=3, backoff_factor=0.4, status_forcelist=[429, 502, 503])
s = requests.Session()
s.mount("https://", HTTPAdapter(max_retries=retry))
在我们按 GB 计量的表上,网站发回来的 429 或 5xx 和其他响应一样,所以每次重试都要花掉那个响应的字节。它们通常很小,只有正常页面的零头,但不做退避的重试循环会收下很多个,所以 backoff_factor 要留着。超时、连接重置和我们自己网关的错误不计费。各家服务商把这条线划在不同的地方,记得问清楚你的服务商。哪些失败值得再试一次,重试不烧流量那篇讲得更细。
如果你更习惯 httpx 和异步
import asyncio
import httpx
PROXY = "http://USERNAME:PASSWORD@HOST:PORT"
async def main(urls):
async with httpx.AsyncClient(proxy=PROXY, timeout=30) as client:
return await asyncio.gather(*(client.get(u) for u in urls))
results = asyncio.run(main(["https://httpbin.org/ip"] * 3))
注意同一个注意事项:AsyncClient 也会复用连接池。要每次都拿新 IP,就每条请求建一个 client,或者有意识地用粘性会话,自己去切换会话。
轮换你自己的一组静态 IP
ISP 代理和数据中心代理的思路正好相反。你买下一组固定 IP,控制台逐个列出它们的端口和凭据,轮换由你自己来做。多写一行就够:
from itertools import cycle
pool = cycle([
"http://USERNAME:PASSWORD@HOST_1:PORT_1",
"http://USERNAME:PASSWORD@HOST_2:PORT_2",
"http://USERNAME:PASSWORD@HOST_3:PORT_3",
])
for url in urls:
proxy = next(pool)
r = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=30)
该买哪个产品
如果目标站接受数据中心 IP,像这样循环使用几个,通常是分散请求最便宜的办法,而且你可以选它们所在的国家。如果遇到验证码或者空白页,再换住宅代理:它替你轮换,但按 GB 计费,也不能指定国家。别因为某篇教程这么说就一上来用住宅,多数任务并不需要它。当前价格见价格页。
哪一步卡住了?贴到 Discord 里,一分钟内就会有人看出问题。