Python 爬虫实战:8 大反爬与健壮性技巧详解
Python 爬虫实战:8 大反爬与健壮性技巧详解
前言
在爬虫开发中,如何做到不被封 IP、断点续爬、应对页面结构变化是每个爬虫工程师的必修课。本文基于一个真实的高校新闻爬虫项目,系统梳理其中用到的 8 大爬虫技巧,涵盖请求伪装、频率控制、异常退避、断点续爬、多路径回退、数据清洗与持久化等内容。
技术栈:
requests+lxml+re+pandas+csv
1. User-Agent 随机化
UA 池是反反爬的第一道防线。每次请求从 UA 列表中随机选取一个,避免固定 UA 被服务端识别为爬虫。
importrandom user_agents=["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.0.0","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0","Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15"]header={'User-Agent':random.choice(user_agents),}技巧要点:UA 列表应常更新,覆盖 Chrome / Edge / Firefox / Safari 主流浏览器,且版本号不宜过旧。
2. 请求头完整伪装
除了 UA,还需要模拟完整浏览器请求头,尤其是Referer,让请求看起来像站内跳转。
header={'User-Agent':random.choice(user_agents),'Referer':'https://www.xyu.edu.cn/',# 伪装来源'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accept-Language':'zh-CN,zh;q=0.9','Connection':'keep-alive'}技巧要点:
Accept、Accept-Language、Connection三个字段建议一并设置,让你的请求看起来更像正常浏览器行为。
3. 随机延时 + 分段长休息
固定频率请求是触发反爬的常见原因。通过随机延时 + 分段长休息,模拟人类浏览节奏。
# 每次请求后随机休眠 2-6 秒wait=random.uniform(2,6)time.sleep(wait)# 每爬取 50 条,额外休息 30-60 秒ifid%50==0:rest=random.uniform(30,60)time.sleep(rest)| 策略 | 间隔 | 目的 |
|---|---|---|
| 单次延时 | 2~6s 随机 | 避免固定间隔被检测 |
| 分段休息 | 每 50 条休息 30~60s | 模拟人类阅读间歇,降低服务器压力 |
技巧要点:使用
random.uniform()而非固定整数,让间隔无法被简单统计规律识别。
4. 反爬状态码自动退避
当服务端返回限流/封禁状态码时,立即进入长时间休眠,避免持续冲击触发更严厉封锁。
ifresp.status_codein(429,403,503):time.sleep(600)# 休眠 10 分钟| 状态码 | 含义 | 应对策略 |
|---|---|---|
| 429 | Too Many Requests(请求频率过高) | 等待 10 分钟再继续 |
| 403 | Forbidden(拒绝访问) | 等待 10 分钟,IP 可能被暂时封禁 |
| 503 | Service Unavailable(服务不可用) | 等待 10 分钟,服务恢复后再试 |
技巧要点:根据实际反爬强度可调整休眠时间(600s / 1200s),激进场景可配合 IP 代理池使用。
5. 断点续爬(访问记录持久化)
大规模爬取不可避免会遇到中断(网络波动、程序异常、IP 被封)。断点续爬机制保证不重复爬取已访问页面,程序重新运行时从断点继续。
visited_file="visited_ids.txt"# 1. 启动时加载已访问 ID 集合try:withopen(visited_file,'r',encoding='utf-8')asf:visited_ids=set(line.strip()forlineinfifline.strip())exceptFileNotFoundError:visited_ids=set()# 2. 遍历时跳过已访问 IDforidinrange(23900,40000):ifstr(id)invisited_ids:continue# ... 爬取逻辑 ...# 3. 每条爬取后立即写入记录文件withopen(visited_file,'a',encoding='utf-8')asf_visited:f_visited.write(str(id)+"\n")visited_ids.add(str(id))设计要点:
- 使用
set做 O(1) 查找,避免大文件线性扫描 - 每条爬完后立即追写文件(
'a'模式),而非等全部结束再写 - 启动时从文件重建
visited_ids,实现真正的断点续爬
技巧要点:
visited_ids同时存在于内存(set)和磁盘(txt),内存用于快速判重,磁盘用于持久化恢复。
6. 多路径 XPath 回退
目标网站的 HTML 结构可能存在多种模板,单一 XPath 容易漏数据。通过多路径回退策略,依次尝试不同模板,提高数据覆盖率。
# 第一优先级:id="vsb_content_4" 的模板content=html.xpath('//div[@id="vsb_content_4"]//div[@class="v_news_content"]//p//text()')# 回退路径 1:id="vsb_content" 的模板ifnotcontent:content=html.xpath('//div[@id="vsb_content"]//div[@class="v_news_content"]//p//text()')# 回退路径 2:不分段落,直接取所有文本ifnotcontent:content=html.xpath('//div[@id="vsb_content"]//div[@class="v_news_content"]//text()')技巧要点:回退路径按优先级排列——先精确再宽松,最后兜底。确保不同版本的页面模板都能正确提取正文。
7. 正则数据清洗
爬取的原始文本常包含\xa0(不间断空格)、多余换行、HTML 实体等脏数据,需要用正则统一清洗。
importre# 删除浏览量后缀(如 "浏览:123")conttime=re.sub(r' 浏览:.*$','',conttime)# 去除 \xa0、\n、\rcontent=content.replace('\xa0','').replace('\n','').replace('\r','')# 合并连续空白为单个空格content=re.sub(r'\s+',' ',content)# 审核信息的多字段拼接与清洗sssx=' '.join([part.strip().replace('\r','').replace('\n','').replace('\xa0','')forpartinsssxifpart.strip()])| 清洗目标 | 方法 | 说明 |
|---|---|---|
| 浏览量信息 | re.sub(r' 浏览:.*$', '', conttime) | 删除时间后的浏览量尾巴 |
| 不间断空格 | .replace('\xa0', '') | 网页中常见的特殊空白字符 |
| 多余换行 | .replace('\n', '').replace('\r', '') | 去除 HTML 排版带来的换行 |
| 连续空白 | re.sub(r'\s+', ' ', content) | 多个空白合并为一个 |
技巧要点:优先用
str.replace()处理已知字符,再用re.sub()处理通用模式,性能更好。
8. 实时刷盘 + 去重
数据安全是爬虫的底线——爬了 2 万条然后崩溃没存盘等于白干。每条写入后立即flush()到磁盘,爬取完成后用 Pandas 去重。
withopen("../data/output.csv",'a+',newline='',encoding='utf-8')asf:writer=csv.writer(f)# 空文件先写入表头iff.tell()==0:writer.writerow(["标题","发行信息","正文","审核信息"])foridinrange(start,end):# ... 爬取逻辑 ...writer.writerow([title,conttime,content,sssx])f.flush()# ← 每条写入立即刷盘!# 爬取完毕后基于多列去重df=pd.read_csv("../data/output.csv",encoding="utf-8")df_dedup=df.drop_duplicates(["标题","发行信息","正文","审核信息"])df_dedup.to_csv("../data/final.csv",index=False,encoding="utf-8")设计要点:
f.tell() == 0判断文件是否为空,仅在空文件时写入表头f.flush()保证每一条数据都实时落盘,程序崩溃也不丢数据drop_duplicates()基于多列组合去重,比单列更安全
整体架构总结
启动 ──→ 加载 visited_ids(断点恢复) │ ▼ 遍历 ID 范围 ──→ 已在 visited_ids?──→ 跳过 │ 否 ▼ │ 发送请求 ◄──────────────┘ │ ├─ UA 随机化 │ ├─ 完整请求头伪装 │ └─ 429/403/503 退避休眠 ▼ XPath 解析(多路径回退)──→ 正则清洗 │ ▼ 写入 CSV + flush() + 记录 visited_id │ ├─ 每次请求后 sleep(2~6s) └─ 每 50 条 sleep(30~60s) ▼ 全部完成 ──→ Pandas 去重 ──→ 最终输出进阶建议
本文介绍的策略适合轻量级礼貌爬虫场景。如果你面对更强的反爬(如 Cloudflare 5 秒盾、滑块验证码、动态渲染页面),还需要补充以下能力:
| 场景 | 进阶方案 |
|---|---|
| IP 被封频繁 | 引入代理池(付费/免费 IP 代理) |
| 动态渲染页面 | 使用 Selenium / Playwright 模拟浏览器 |
| 验证码拦截 | OCR 识别(ddddocr / tesseract)或打码平台 |
| Cookie/登录态 | requests.Session 维持会话,保持登录态 |
| 大规模采集 | 改用 Scrapy 框架,支持并发、中间件、分布式 |
本文代码均来自真实项目,核心思想:用最小的代价,换取最大的数据采集成功率。希望这些技巧对你的爬虫开发有所帮助!