上个月接的一个活:客户做学术文献检索的SaaS,要我方每天从三个学术库拉取约12万条文献元数据(DOI、被引数、作者机构),用于自己的检索索引更新。三个库的访问限制完全不同——PubMed相对宽松但要控制频率,IEEE Xplore 对并发敏感,中文库最狠,触发验证码之后会把同一出口IP关联过的会话一起降权。
第一周我用的是某家便宜动态代理,结果第三天开始,采集任务从“偶发验证码”变成“稳定403”,而且换了IP也不恢复。这篇就写清楚这个坑的根因,以及我最后固化下来的参数。
一、“高匿”标签不可信,我用24行代码验了真身
代理的匿名级别分三档:透明代理会把你的真实IP写进 X-Forwarded-For(XFF,链路IP传递头),普通匿名代理暴露自己是代理但不暴露你的IP,高匿代理两头都不露。学术库的风控恰好会读这些头——一旦在XFF里看到真实出口IP,它封的不只是那个代理IP,还会顺着账号和Cookie做关联降权。
我一开始以为只要IP不被封就行,跑了三天才发现账号也被连坐,这个判断错得挺贵。后来写了个探针脚本,逐个检查提取出来的IP:
import requests
LEAK = ["X-Forwarded-For", "X-Real-IP", "Via", "Forwarded", "Proxy-Connection"]
def probe(proxy, my_ip):
try:
r = requests.get("http://httpbin.org/headers",
proxies={"http": proxy, "https": proxy}, timeout=8)
h = r.json()["headers"]
hit = [k for k in LEAK if k in h]
if not hit:
return "elite" # 高匿
if my_ip in h.get("X-Forwarded-For", ""):
return "transparent" # 透明,直接弃用
return "anonymous" # 普通匿名
except Exception:
return "dead"
实测那批便宜IP里,约37%是普通匿名或透明,而商品页上写的是“高匿”。更隐蔽的一类:XFF是干净的,但 Via 头里塞了供应商自己的节点标识,同一段ASN的IP在IEEE那边被整段标记。这类问题不看响应头永远发现不了。
二、封禁的根因不是IP脏,是会话跳得太快
换到高匿之后,403没消失,只是变少了。抓了两天的失败请求日志才看明白:学术库的风控是“IP + 行为”双维度,而我的轮换策略是每个请求换一次IP。一个检索会话里出口IP变了三次,验证码几乎是必出的。
但反过来说,粘太久同样不行。我做过一组对照:单IP粘30分钟,前20分钟正常,之后请求量累积过阈值,同样进验证码。拐点大概在单IP每小时40-60个请求、会话粘性10-15分钟这个区间,超过60就开始掉可用率。这个数字放到其他站点不一定成立,但在学术类库里我复现了三次。
| 轮换策略 | 平均延迟 | 验证码触发率 | 有效采集量/天 |
|---|
| 每请求换IP | 210ms | 18.4% | 6.2万条 |
| 粘性5分钟 | 180ms | 7.1% | 9.8万条 |
| 粘性12分钟 | 175ms | 2.3% | 11.6万条 |
| 粘性30分钟 | 170ms | 9.6% | 8.4万条 |
注意最后一行——粘太久反而更差,很多人卡在这里找不到原因。
三、三档供应商跑四周的实测账
四周里我把同一套采集逻辑分别跑在三档代理上,每档三天轮换,记录可用率和封禁率。免费池那档第二周就放弃了,纯粹是浪费时间。
| 档位 | 单请求成本 | 平均延迟 | 4周可用率 | 学术库封禁率 |
|---|
| 免费公开池 | 0元 | 1400ms | 31.2% | 46.8% |
| 动态短效(0.0022元/IP起) | ≈2.2元/万次 | 95ms | 96.4% | 11.2% |
| 隧道代理(16元/天起) | 按天固定 | <10ms | 99.9% | 2.6% |
隧道那档我用的是一家长期合作的服务商,IP池3000万+、覆盖365+城市,隧道模式下粘性时长可以在请求头里直接指定,不用自己维护IP列表——这点对学术库这种要求“会话内不跳IP”的场景特别省事。如果你的量在每天十万条以内,隧道按天计费比动态按条算更划算;量再往上,才需要回去算单IP成本。接入和参数说明在 mayihttp.com 上都有,不用额外问客服。
四、把参数固化下来
最后落地的配置是这样,直接抄:
- 提取阶段先用探针脚本过滤,
transparent 和 dead 直接丢弃,别心疼 - 会话粘性设 12 分钟,单IP请求上限 60 次,到任一条件即换
- 请求间隔加 300-800ms 随机抖动,别用固定值——固定间隔本身就是指纹
- 失败重试最多2次,第二次仍失败就换会话,不要原地硬刚
- 每2小时抽10个IP验一次匿名级别,供应商的头处理策略会变,我遇到过中途改配置的
这套参数把日均有效采集量从6万提到11.6万,验证码率压到2.3%。但要说清楚边界:这是每天十来万条的规模,如果你要做千万级,会话池的调度和失败队列得单独设计,靠调参数解决不了。
最后给个明确判断——选代理IP供应商,先看它敢不敢让你测匿名级别,再看会话粘性能不能自己控,池子大小排在第三位。池子大但轮换不听话的,在学术库这种场景里一点用没有。