多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Python爬虫突破反爬虫机制知识点总结

Python爬虫突破反爬虫机制知识点总结 前言标题里的「突破」两个字本文要明确改掉。反爬虫机制不是一道等着被「突破」的关卡而是站点在表达它对自动化访问的态度。把它当成对手来打你会默认两件事一是「我要的东西本来就该归我」二是「对方设的限制是技术障碍而非意愿表达」。这两条都不成立。所以本文讲的是三件事反爬机制的成因、如何识别自己已经被拦截、以及正确的应对方式。对应地本文不包含任何绕过手段不写 User-Agent 池轮换、不写验证码识别、不写 JS 挑战破解、不写浏览器指纹伪装、也不写用分布式出口规避封禁。不是因为篇幅不够而是因为这些方向本身就是错的——它们解决的是「怎么让拦截失效」而真正该解决的是「我为什么被拦以及我有没有资格继续访问」。还要先纠正一个常见误解反爬并不等于「技术对抗」。相当一部分所谓反爬只是限速——对方希望你慢一点而不是完全不让你来。把限速当成封禁然后用更激进的手法去「突破」是把本来还能谈的事情变成了对抗。本机没有 Python 解释器也没有装requests等第三方库本文代码未在本机运行验证只作逐行人工推演示例里的地址、页面内容全部是自造的本地占位数据不指向任何真实站点。一、反爬机制为什么存在理解成因才能理解哪些应对是合理的。常见的动因有四类成因站点在保护什么典型表现合理的应对保护服务不被压垮带宽、CPU、数据库连接限速、429、排队、超时降速、遵守Crawl-delay、错峰保护数据与版权内容本身、商业价值登录墙、付费墙、403走官方 API、获取授权法律与合规要求个人信息、受监管内容强制登录、实名、地域限制停止抓取走合规渠道保护用户体验与风控刷单、黄牛、爬取比价验证码、行为验证、风控封禁尊重判断不要伪装成正常用户绕过注意第二类和第三类当站点保护的是数据本身时反爬不是技术问题是授权问题。这时候无论你的技术多高明你拿到的都是你无权拿的数据。第四类更微妙——「保持像正常用户一样」这个目标本身就是伪装本文不会提供任何伪装手法。还有一个常被忽略的事实反爬是有成本的。站点每加一层验证正常用户也会被烦到。所以站点通常不会一上来就封你而是从温和的手段开始先限速、再观察、最后才升级。这个「渐进」的特性反过来给了你机会——在早期信号出现时就调整比在封禁之后想办法要好得多。二、反爬机制有哪些「面」以及它们留下的可识别信号从爬虫的视角看反爬机制作用在几个不同的层面。理解这些层面重点是为了识别不是为了破解。网络层基于来源 IP 的连接数、请求频率、并发数做限制。你能观察到的信号是请求开始变慢、出现 429、连接被重置、或者干脆超时。这类限制往往有明确的时间窗口过一段时间会恢复。协议层检查请求头。比如是否带了User-Agent、Referer是否合理、Accept-Language是否一致。注意——这一层存在的意义是区分「自动化客户端」和「浏览器」识别它的正确用途是反思自己的请求是否「坦诚」而不是去伪造一整套头。会话层依赖 Cookie。站点在首次响应下发一个 Cookie后续请求必须带回否则被当作异常访问。要理解的是Cookie 是服务端下发的状态凭据它对应的是一个真实会话比如你的登录态。用别人下发的 Cookie、或者把 Cookie 复制来复制去涉及的是身份问题不是技术问题。应用层验证码、行为验证、滑块、需要执行 JavaScript 才能拿到内容。这一层的信号最明显你拿到的 HTML 里没有你想要的正文而是一段要求验证的页面。这就是明确的「不欢迎」信号。内容层返回 200 但内容是假的或空的。这是最隐蔽的一种——状态码一切正常正文却是占位内容。识别的方法是对比同一个页面用浏览器打开和有脚本请求拿到的内容是否一致或者检查关键字段是否缺失、数值是否明显异常比如价格全是 0。把这些层面列出来是为了让你在写程序时主动去检查这些信号而不是被动地等到数据全错。三、如何识别自己已经被拦截不要只看状态码。下面这些信号出现任意两三条就该停下来复盘状态码异常403理解请求但拒绝、429请求过多、503服务不可用或者一路 200 却什么都抓不到。响应被重定向到验证页urlopen默认跟随重定向最终拿到的是一个和请求路径无关的页面。正文明显不对长度骤减、出现「请稍后重试」「访问验证」等字样、关键字段缺失。Cookie 被反复下发每次请求都收到新的会话 Cookie说明对方没有把你的请求认作同一个会话。延迟异常升高原本几十毫秒的响应变成几秒可能是被降级到慢速队列了。数据异常但不报错抓到的数字明显不合理全 0、全相同、超出正常范围。一个务实的做法是在采集流程里加一道「健全性检查」每次拿到数据先验证字段是否齐全、数值是否在合理范围、命中率是否突然从高位掉到零。这道检查不需要任何对抗技巧却能第一时间告诉你「对方不欢迎了」。# 适用于 Python 3.8def sanity_check(page, required_fields):对抓到的页面做最基础的健全性检查返回 (是否可信, 原因)if not page or len(page) 200:return False, 正文过短可能是验证页或空响应for field in required_fields:if field not in page:return False, f缺少关键字段: {field}blocked_markers (请稍后重试, 访问验证, 访问过于频繁)for marker in blocked_markers:if marker in page:return False, f命中拦截提示: {marker}return True, ok这段代码里没有任何「绕过」逻辑它做的唯一一件事是把「我可能被拦了」这件事变成程序能看见的信号。一旦ok变成False正确的动作是停止并人工判断而不是自动「换一套请求头再试」。四、正确的应对方式按顺序前面的能解决就不要跳到后面第一步降速并把速率交给对方来定。如果你的问题只是频率那就把并发降到 1、按 robots.txt 里的Crawl-delay或Request-rate设置间隔。用标准库就能读到这些参数。这是最常见、也最常被忽略的解——很多人被拦之后的第一反应是「换 IP」而实际上「把间隔从 0.1 秒改成 5 秒」就够了。# 适用于 Python 3.8import timeclass RateLimiter:最简单的令牌节流保证两次请求之间至少间隔 min_interval 秒def __init__(self, min_interval):self.min_interval float(min_interval)self._last 0.0def wait(self):now time.monotonic()delta now - self._lastif delta self.min_interval:time.sleep(self.min_interval - delta)self._last time.monotonic()用time.monotonic()而不是time.time()是刻意的前者是单调时钟不受系统时间被调整的影响适合测量时间间隔后者可能因为校时而回退用来测间隔会出现负数或跳变。第二步遵守 robots.txt。用urllib.robotparser.RobotFileParser读取can_fetch、crawl_delay、request_rate把「不允许」当成硬约束写进流程而不是当成一个建议。第三步走官方 API 或授权数据源。大部分站点对你想要的数据都提供了正规出口公开 API、数据下载页、合作方接口。走这些渠道不仅合规通常还更稳定——API 有明确的字段定义和配额说明比解析 HTML 靠谱得多。第四步联系站点。说明你的身份、用途和数据需求。研究用途、你自有数据的补充、公开数据集的整理这些都有机会拿到明确授权。第五步如果以上都不行就接受「拿不到」。这不是失败这是对一个明确答复的尊重。五、把合规写进程序结构一个容易被忽略的点合规不仅是行为也是结构。如果你把限速、robots 检查、健全性检查都写成可选的「装饰」它们迟早会被绕过或被忘记。更稳的做法是把它们放进请求的唯一入口# 适用于 Python 3.8class FetchPolicy:把合规约束集中到一处所有请求都必须经过它def __init__(self, limiter, robots_allowed, stop_on_blockTrue):self.limiter limiterself.robots_allowed robots_allowed # 一个 (url) - bool 的判定函数self.stop_on_block stop_on_blockself.stopped Falsedef before_request(self, url, user_agent):if self.stopped:raise RuntimeError(已因拦截信号停止等待人工判断)if not self.robots_allowed(url):raise PermissionError(frobots.txt 不允许抓取: {url})if not user_agent or user_agent.strip() :raise ValueError(必须设置能识别出自己身份的 User-Agent)self.limiter.wait()def on_blocked(self, reason):一旦判定被拦就整体停下self.stopped Trueraise RuntimeError(f检测到拦截信号停止采集: {reason})这个结构传递的信息是「停止」是一个正常且优先的分支而不是一个需要想办法绕开的异常。当stopped为真时整个采集流程停下来等人工判断这比「自动重试到成功为止」要负责任得多。顺带说一句 Python 2 的问题网上不少老爬虫代码还在用print ...语句、urllib2、dict.iteritems()这些写法。Python 2.7 已于 2020 年 1 月 1 日停止维护这些写法在 Python 3 里要么是语法错误print语句要么模块已被合并重组urllib2拆进了urllib.request和urllib.error要么方法已被移除iteritems要用items。照抄老代码不只是风格问题是根本跑不起来。常见坑点❌ 把「被反爬了」等同于「需要找一套绕过方案」第一反应是换 IP、换 UA。✅ 先判断成因是限速还是拒绝。限速就降速并按Crawl-delay设置间隔拒绝就停止并走授权渠道。❌ 只看状态码200 就当成功。✅ 200 也可能是验证页或空壳页要对正文长度、关键字段、异常数值做健全性检查。❌ 在程序里准备一个 User-Agent 池每次请求随机挑一个浏览器标识。✅ 用能识别出你自己的User-Agent这是自报家门轮换 UA 是隐藏身份方向相反。❌ 把验证码、滑块、JS 挑战当成「必须攻克的技术难题」。✅ 它们就是站点明确表达的「不欢迎自动化访问」正确应对是停止而不是识别或破解。❌ 用大量出口 IP 分摊请求把原本被限流的访问「绕开」。✅ 换 IP 规避基于 IP 的限流属于绕过代理只适合用在你有权发起的任务上如测试自己应用的地域表现。❌ 把限速写成「可选优化」忙起来就跳过。✅ 把限速、robots 检查、健全性检查放进请求的唯一入口让它们无法被绕过。❌ 用time.time()测请求间隔。✅ 用time.monotonic()它不受系统时间校时影响测间隔才可靠。❌ 拿网上老教程里的urllib2、print语句示例直接改。✅ Python 2.7 已于 2020 年 1 月 1 日停止维护用urllib.request/urllib.error、print()函数和.items()。总结反爬的「面」你观察到的信号正确应对网络层IP 频率变慢、429、连接重置降速、错峰不要换 IP 规避协议层请求头明确要求标识 UA用能识别自己的 UA不伪装会话层Cookie反复下发新会话理解会话语义不借用他人凭据应用层验证码 / JS正文变成验证页这是明确的拒绝停止内容层假 200正文异常、字段缺失健全性检查人工判断把「突破」换成「理解与尊重」这篇知识点总结才立得住反爬的成因是站点在保护自己的资源、服务和法律边界识别是否被拦要看的不只是状态码还有正文、Cookie 与延迟正确的应对只有降速、遵守 robots、走官方 API、取得授权这几条。任何试图让拦截「失效」的手法都不在本文的讨论范围内也不该出现在你的项目里。参考状态码语义以 MDN 的 HTTP 状态码文档为准robots.txt解析与请求头相关行为以 Python 官方文档urllib.robotparser、urllib.request章节为准本文代码未在本机运行仅作人工推演。
返回列表