上周帮一个做市场数据监测的朋友调爬虫,他代码写了两周,天天被目标站点返回403。我一看他的requests请求,IP写死了,同一个出口地址连续打了几千次,换谁都得把你拉黑。他问我:"能不能让每次请求走不同的IP?"我说能,而且不用你改什么框架,requests加个proxies参数就行。但真正让这套东西跑稳的,是你得有一个靠谱的动态代理IP池。今天就把我实际在用的配置方式摊开讲,从最基础的三行代码,到怎么挑代理、怎么避免踩坑,一次说清楚。
先搞清楚:requests里那个proxies参数到底在干嘛
很多人第一次用代理IP,会去搜"requests怎么设置代理",搜出来一堆帖子,有的让你改环境变量,有的让你装什么中间件,看得人头晕。其实最本质的东西就一行:
resp = requests.get("https://target-site.com/api/data", proxies={"http": "http://用户名:密码@代理IP:端口", "https": "http://用户名:密码@代理IP:端口"})
就这么简单。requests底层用的是urllib3,你把代理地址塞进proxies字典,它发请求的时候就会走你指定的那个出口,而不是你本机的真实IP。注意这里http和https两个key都要写,哪怕你只访问http站点,https那个也建议填上,不然遇到重定向的时候可能会出问题。
但如果你只是把上面这行代码跑一遍,那跟没用代理差不多——因为你每次请求走的都是同一个IP。所谓"动态代理",核心就是每次请求(或者每隔一段时间)换一个不同的出口IP。这才是解决403、429、频率限制的关键。
三行核心代码:让IP自动轮换起来
下面这套是我实际项目里在用的写法,不是那种教科书式的"完美方案",而是真正能跑、能维护、出了问题好排查的版本:
import requests
import random
从你的代理服务商API拉取一批可用IP(这里假设已经拿到了列表)
proxy_list = ["http://user:pass@112.85.xx.xx:8080", "http://user:pass@220.181.xx.xx:8080", "http://user:pass@183.232.xx.xx:8080"]
def get_response(url):
proxy = random.choice(proxy_list)
return requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=10)
调用
resp = get_response("https://target-site.com/api/list")
print(resp.status_code, resp.text[:200])
你看,核心逻辑就三件事:拿一个IP列表、随机挑一个、塞进proxies。如果你用的是神龙HTTP这类提供API接口的服务商,proxy_list不是你自己手写的,而是每次调一下他们的提取接口,实时拿一批当前可用的短效IP回来。这样你代码里不用维护任何IP地址,全部交给服务商的池子去轮。
如果你请求频率比较高,比如一个循环里要连续请求几百次,建议加一个Session对象复用TCP连接,别每次都新建:
session = requests.Session()
def fetch_with_proxy(url, proxy):
session.proxies = {"http": proxy, "https": proxy}
resp = session.get(url, timeout=10)
session.proxies = None 用完清掉,避免下次串了
return resp
这里有个细节:session.proxies用完记得置None,不然你下一个请求如果没设代理,它会继续走上一个代理,排查问题的时候你会怀疑人生。
动态代理IP怎么选?这几个指标比"便宜"重要得多
我见过太多人挑代理IP,第一反应是"哪家便宜用哪家"。结果用了三天,IP可用率不到70%,代码里一半请求超时,另一半返回502,比不用代理还折腾。选动态代理,我一般看这么几个硬指标:
| 指标 | 为什么重要 | 建议标准 |
|---|---|---|
| IP可用率 | 直接决定你代码里超时和报错的比例 | 99%以上,低于95%基本别用 |
| IP存活时长 | 短效IP到期后你代码里那个地址就废了,得重新拉 | 根据请求频率选,高频选3-5分钟,中频选10-30分钟 |
| 延迟 | 代理多一跳,延迟高了你的整体吞吐就下来了 | 同省内50ms以内算正常 |
| 城市级定位精度 | 有些业务需要指定地区IP,定位不准等于白搭 | 至少能精确到城市,最好支持指定省份 |
| 协议支持 | 你的目标站点如果只走HTTPS,你代理不支持HTTPS就废了 | HTTP/HTTPS/SOCKS5至少覆盖前两个 |
我目前长期用的是神龙HTTP,说几个实际感受。他们家是国内三大运营商正规授权的,IP资源池有3000万+,这个量级意味着你即使高并发提取,短时间内也不会出现"池子被抽干"的情况。我比较看重的一点是他们的IP纯度做到了99.8%,什么意思呢?就是给你的IP基本不会混进那些已经被大量爬虫用过的"脏IP",你拿到的地址在目标站点那边看起来就像一个正常用户,不容易触发风控。
另外他们的短效动态IP池支持3/5/10/15/30分钟不同时长,可以定制。我一般做数据监测类的项目用5分钟的,做一次性的大规模采集用30分钟的。计费方式是包量或者包时,个人开发者用包量比较灵活,企业项目用包时成本更可控。API接口兼容Python、Java、Go这些主流语言,文档里直接给了各语言的示例,集成进去基本半天就能跑通。
实际跑起来之后,这几个坑我替你踩过了
第一个坑:IP还没用就过期了。短效IP是有生命周期的,你从API拉回来一批,如果放在列表里躺了四分钟才轮到它被请求,那它大概率已经失效了。解决办法是别一次性拉太多,用多少拉多少,每次循环前现取。神龙HTTP的API支持按次提取,你代码里每次请求前调一下接口拿一个当前有效的IP就行,不用自己维护一个"待用队列"。
第二个坑:超时没设或者设太短。代理多了一跳网络,你原来直连2秒能返回的响应,走代理可能要3-4秒。如果你timeout设成3秒,大量请求会莫名其妙超时。建议timeout至少给10秒,如果目标站点本身响应就慢,给15秒。别为了"看起来快"把超时压到2秒,那你的错误率会飙升。
第三个坑:所有请求都打同一个URL,只是换了IP。你以为换了IP就安全了,但目标站点的WAF不光看IP,还看请求头、User-Agent、请求频率。你IP换了,但User-Agent还是"python-requests/2.28.1",人家一眼就认出来了。建议至少把User-Agent、Accept、Accept-Language这几个头随机化一下,模拟正常浏览器行为。
第四个坑:没做失败重试。动态IP再稳定,偶尔也会有个别IP连不通。你代码里如果一次失败就抛异常,整个任务就中断了。加一个简单的重试逻辑,失败后换一个IP再试一次,成功率能提升很多:
import time
def safe_get(url, max_retries=3):
for i in range(max_retries):
try:
proxy = fetch_one_proxy() 每次从API取一个
resp = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=10)
if resp.status_code == 200:
return resp
except requests.exceptions.RequestException:
pass
time.sleep(1) 别立刻重试,等一秒
raise Exception(f"重试{max_retries}次后仍失败: {url}")
常见问题
Q:我代码里用requests,目标站点是HTTPS的,代理配置有什么要注意的?
A:HTTPS请求走代理的时候,代理端需要做TLS连接。你proxies里https那个key填的代理地址,协议头写http就行(因为你的客户端到代理之间是明文传输,代理再去和目标站点建立TLS)。也就是说,哪怕目标站点是https,你代理地址前面也写http://,不要写https://。另外确认你的代理服务商支持HTTPS协议,神龙HTTP是HTTP/HTTPS/SOCKS5都支持的,这个不用担心。
Q:短效动态IP和长效静态IP,我该怎么选?
A:看你的请求频率和场景。如果你是一个循环里每分钟要请求几十次甚至上百次,用短效动态IP,IP池大、轮换快,不容易被目标站点识别为异常。如果你是做定时监测,比如每小时请求一次某个接口,那长效静态IP(1-24小时存活)就够了,IP稳定,你也不用频繁调API提取。神龙HTTP这两类都有,短效的3000万+资源每日更新去重,长效的每日去重量10万+,都能指定省份或城市。根据你实际频率选就行,不用纠结。
Q:我同时跑多个爬虫任务,代理IP会不会冲突?
A:如果你用的是动态IP池,每次提取的IP都是独立的,不同任务之间不会互相影响。但如果你用的是固定IP,那确实要注意,两个任务共用同一个固定IP,请求频率叠加之后可能触发目标站点的频率限制。神龙HTTP的固定IP是按个数售卖的,你几个任务就买几个,每个任务绑一个独立的固定IP,互不干扰。另外他们后台有可视化的使用数据统计,你能看到每个IP的调用量和状态,哪个IP快到期了、哪个IP异常了,一目了然。
Q:requests的proxies参数和urllib3的ProxyManager,我该用哪个?
A:99%的场景下,直接用requests的proxies参数就够了,简单、直观、好维护。ProxyManager是urllib3底层的接口,除非你要做非常底层的连接池定制(比如自定义代理的认证方式、设置特殊的连接超时策略),否则没必要绕到那一层去。用proxies参数,代码可读性好,出了问题也好排查。我见过有人非要用ProxyManager,结果代码多了三倍的行数,维护起来反而痛苦。


