本地跑得好好的爬虫,一放到服务器上跑量就开始各种报错——403、412、429,严重一点直接甩给你一个验证码页面。
代码逻辑我检查了三遍,没毛病。
说实话,做数据采集这几年,被"封IP"坑过的次数我自己都数不清了。后来才慢慢想明白:封禁的锅,很多时候真不在你的代码,而在于——你在服务器眼里不像个正常用户。
这篇文章我想把这件事一次讲透。分三块:
平台到底靠什么判断你是爬虫
从轻到重,6 种应对方案(含代码)
重点落地:怎么给爬虫接一个代理IP池
一、平台怎么"认出"你是爬虫的?
很多人以为反爬是"检测你是不是脚本",其实没那么玄乎。平台手里有一堆维度,任何一个异常都可能让你进黑名单。常见的有这几类:
- 请求频率与行为特征
这是最基础的。正常用户点一下停几秒,你的爬虫一秒发几十次请求,这特征太扎眼了。
除了频率,还有"行为模式"——比如你总是按固定顺序翻页、请求间隔精确到毫秒、深夜三点还在稳定高频请求,这些都不像人。 - 请求指纹
你的浏览器和脚本发出去的请求,长得不一样。平台看这些:
User-Agent 是 Python-requests 还是真实 Chrome
请求头字段的顺序、大小写、有没有缺失字段
TLS 握手的指纹(JA3 之类),python 的 TLS 库跟 Chrome 天然不同
Accept、Accept-Language、Referer 是否合理
一个 User-Agent 是 Chrome,但其它头跟 Chrome 完全对不上的请求,一眼假。
3. IP 维度
这是很多人忽略的一环。平台会看:
单 IP 的请求量:同一个 IP 短时间内请求过多
IP 信誉 / 纯净度:这个 IP 之前有没有被封过、是不是数据中心 IP
IP 的地理与运营商:你昨天在广州,今天 IP 跳到哈尔滨,明天又在成都,行为很怪
机房 IP(IDC 数据中心 IP)经常被平台提前标记,因为真实用户很少从机房上网。
4. 登录态与签名
像 B站、抖音、小红书这类平台,接口都带了签名参数(比如各种 w_rid、a_bogus、X-s 之类),还会校验登录 Cookie。你签名算法不对、Cookie 无效,请求直接就被拒了。
这块属于"硬对抗",本文不展开,重点是前面三个——因为它们才是大多数人被封的主因。
二、6 种解决方案,从轻到重
按"投入成本"排序,你可以按自己的量级选。
方案 1:请求头伪装(成本最低,先做这个)
别再用裸的 requests.get(url) 了。先把请求头补齐:
python复制import requests
headers = {
“User-Agent”: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
“(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36”,
“Referer”: “https://目标网站.com/”,
“Accept”: “text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8”,
“Accept-Language”: “zh-CN,zh;q=0.9”,
}
resp = requests.get(“https://目标网站.com/list”, headers=headers, timeout=10)
print(resp.status_code)
能解决:最简单的"一眼假"检测。
解决不了:频率限制、IP 封禁、签名校验。
小提醒:User-Agent 别老用同一个。可以准备一个列表随机挑,配合主流浏览器版本。
方案 2:随机延时 + 退避重试(性价比很高)
就算你用了代理,也不建议毫无间隔地狂发请求。加一点随机性,效果提升明显:
python复制import time
import random
import requests
def fetch(url, retries=3):
for i in range(retries):
try:
resp = requests.get(url, timeout=10)
if resp.status_code == 200:
return resp.text
# 被限流了,按指数退避再试
time.sleep((2 ** i) + random.uniform(0, 1))
except requests.RequestException:
time.sleep(1)
return None
for page in range(1, 51):
html = fetch(f"https://目标网站.com/list?page={page}“)
# 每页之间随机停一下,别让它看起来像机器
time.sleep(random.uniform(1, 3))
关键点:延时一定要"随机”,固定 sleep(1) 反而是一种特征。
方案 3:Cookie / 登录态池
有些数据必须登录才能拿到。这时候单个账号不够,需要准备多个账号的 Cookie,轮换使用。
思路很简单:把多个账号的 Cookie 存成一个列表,每次请求随机取一个。注意每个账号的请求频率也要控制,否则账号会被封。
python复制import random
import requests
cookie_pool = [
“sessionid=xxx1; other=…”,
“sessionid=xxx2; other=…”,
]
def get_headers():
return {
“User-Agent”: “Mozilla/5.0 … Chrome/124.0.0.0 Safari/537.36”,
“Cookie”: random.choice(cookie_pool),
}
方案 4:浏览器自动化(对抗强一些的网站)
当网站大量依赖 JS 渲染、或者有复杂指纹检测时,requests 就不够用了。这时候上 Playwright / Selenium,直接驱动一个真实浏览器:
python复制from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(“https://目标网站.com”)
content = page.content()
browser.close()
代价:速度慢、资源消耗大、不适合大规模采集。
适用:数据量不大、但对真实性要求高的场景。
方案 5:代理IP池(规模采集的核心)
如果你的采集量上来了,前几种方法都只是"缓解",真正决定能不能稳定跑下去的,是代理IP。
原理不复杂:请求不再直接从你的服务器发出去,而是先经过代理服务器转发,目标网站看到的是代理的 IP,不是你自己的。
这样你就能把请求压力分散到大量不同的 IP 上,让每个 IP 看起来都"没那么高频"。
python复制proxies = {
“http”: “http://用户名:密码@代理地址:端口”,
“https”: “http://用户名:密码@代理地址:端口”,
}
resp = requests.get(“https://目标网站.com/list”, proxies=proxies, timeout=10)
方案 6:遵守 robots + 控制规模(合规底线)
前面说的都是"技术对抗",但有一条底线得先讲清楚:爬虫要合法合规。
先看 robots.txt,别碰明确禁止的路径
涉及个人隐私、付费内容、需要授权才能拿的数据,别碰
控制规模,别把人家网站压垮
用途合法,别拿数据去做违法的事
技术只是工具,用在什么地方,是你自己的选择。
三、重点实战:给爬虫接一个代理IP池
上面方案 5 只是最基础的用法——单个代理。真实场景里,你需要的是一个会自己换 IP 的池子。我把自己常用的一个轻量实现贴出来。
第一步:从服务商 API 批量提取 IP
正规的代理服务商都会提供 API 提取接口,直接拉一批 IP 回来:
python复制import random
import requests
class ProxyPool:
definit(self, api_url):
self.api_url = api_url
self.pool = []
self.refresh()
def refresh(self): """从代理服务商 API 批量提取 IP""" resp = requests.get(self.api_url, timeout=10) self.pool = [line.strip() for line in resp.text.splitlines() if line.strip()] print(f"已补充 IP:{len(self.pool)} 个") def get(self): """随机取一个 IP,池子空了就补""" if not self.pool: self.refresh() return random.choice(self.pool) def check(self, proxy): """检测这个 IP 还能不能用""" try: r = requests.get( "http://httpbin.org/ip", proxies={"http": f"http://{proxy}", "https": f"http://{proxy}"}, timeout=5, ) return r.status_code == 200 except Exception: return False第二步:带着池子跑采集
python复制pool = ProxyPool(“https://你的代理服务商/api/get?count=10”)
headers = {
“User-Agent”: “Mozilla/5.0 … Chrome/124.0.0.0 Safari/537.36”,
}
for page in range(1, 101):
proxy = pool.get()
proxies = {“http”: f"http://{proxy}“, “https”: f"http://{proxy}”}
try: r = requests.get( f"https://目标网站.com/list?page={page}", headers=headers, proxies=proxies, timeout=10, ) parse(r.text) # 你的解析逻辑 except Exception: # 这个 IP 挂了,剔掉,下次自动换 pass # 每请求一次,随机停一下 time.sleep(random.uniform(1, 3))第三步:几个容易踩的坑
坑 1:不检测直接用。
代理池里总有失效的 IP。开跑前、用之前,最好过一遍 check()。不然你的解析逻辑会莫名其妙拿不到数据。
坑 2:返回空页面却没报错。
有些网站被限流时,不返回错误码,而是返回一个"看起来正常但内容是空的"页面,或者验证码页。所以解析完一定要校验关键字段——比如一页 20 条数据,如果 15 条标题是空的,那就说明出问题了。
坑 3:只看 IP 数量,不看 IP 质量。
机房 IP 便宜,但被标记得厉害,很多平台直接拒。住宅 IP(家庭宽带 IP)纯净度高、更像真实用户,适合对隐蔽性要求高的采集。
四、代理IP 怎么选,才不踩坑?
市面上的代理 IP 服务商很多,参数看得人眼花。我按自己踩坑的经验,给你几个真正影响结果的指标:
| 指标 | 怎么理解 | 建议 |
|---|---|---|
| IP 类型 | 住宅IP / 机房IP | 要稳定隐蔽选住宅;纯速度场景可用机房 |
| 纯净度 | 该 IP 被风控标记的程度 | 越低越好,直接决定可用性 |
| 可用率 | 提取出来的 IP 能用的比例 | 越高越省心,别只看 IP 池大小 |
| IP 时效 | 长效(不变)/ 短效(常换) | 需会话保持选长效,高频轮换选短效 |
| 并发 | 同时能发多少请求 | 按你的量级配,别一步到位 |
| 覆盖地区 | 能指定的省市 | 要按地区采集,就得看这个 |
一句话:别只比价格和 IP 数量,纯净度和可用率才是决定你效率的东西。
五、避坑清单(照着自查)
请求头补齐了吗?别裸奔
延时是随机的吗?固定 sleep 也是特征
代理用之前检测过可用性吗?
解析后校验了关键字段吗?(防"空页面陷阱")
IP 类型选对了吗?(住宅 vs 机房)
频率压得住吗?再好的 IP 也扛不住无脑高频
用途合法吗?robots 看了吗?
六、常见问题(FAQ)
Q1:用了代理为什么还是被封?
大概率是代理 IP 本身不干净(机房 IP 被标记),或者你的请求行为特征太明显(频率、头信息)。IP 只是其中一环。
Q2:免费代理能用吗?
偶尔测试可以,正式项目别用。免费代理普遍存活时间短、速度慢、数量少,反而拖慢你的效率,还可能被拿去做中间人攻击。
Q3:多长时间换一次 IP 合适?
看网站风控强度和你的量。一般小规模采集 1~3 分钟换一次,量大的话可以一次一换。核心是让单个 IP 的请求频率看起来正常。
Q4:住宅IP 和机房IP 到底差在哪?
住宅IP 来自真实家庭宽带,行为跟普通用户一致,难被识别,适合隐蔽性要求高的场景;机房IP 来自数据中心,速度快、便宜,但容易被平台标记。
写在最后
爬虫被封,说到底是一场"像不像真人"的博弈。请求头、延时、Cookie、指纹、代理IP,都是围绕这一件事做文章。
量级小的时候,前两三种方案就够用;真要长期、稳定、大规模地跑,代理IP池是绕不开的基础设施。
我这边用的是国内住宅静态代理,覆盖 30 多个省份、300 多个城市,纯净度不错,跑数据采集和电商类的项目比较省心。感兴趣可以去官网看看:90daili.com(合法合规用途,仅提供国内节点)。
技术这东西,多踩几次坑就熟了。有问题的欢迎评论区聊,看到都会回。
本文为原创技术分享,涉及的采集思想仅供学习与合法用途参考。请遵守目标网站的 robots 协议及相关法律法规。