Python 爬虫实战:8 大反爬与健壮性技巧详解

📅 2026/7/19 3:01:35 👁️ 阅读次数 📝 编程学习
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'}

技巧要点AcceptAccept-LanguageConnection三个字段建议一并设置,让你的请求看起来更像正常浏览器行为。


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 分钟
状态码含义应对策略
429Too Many Requests(请求频率过高)等待 10 分钟再继续
403Forbidden(拒绝访问)等待 10 分钟,IP 可能被暂时封禁
503Service 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 框架,支持并发、中间件、分布式

本文代码均来自真实项目,核心思想:用最小的代价,换取最大的数据采集成功率。希望这些技巧对你的爬虫开发有所帮助!