新闻详情

新闻详情

首页 / 资讯中心 / 详情

Python爬虫反爬虫策略分层解析与Selenium自动化抗检测实战

发布时间:2026/10/2 10:08:16来源:尧图网络
Python爬虫反爬虫策略分层解析与Selenium自动化抗检测实战
先说一个我最近接到的真实需求。对方要爬一个中等规模的电商站点页面结构看似平淡无奇用requests两三行就能拿到首屏HTML。可等我真正写下去却发现翻到第二页时接口返回的是一段被加密过的JSON再往下请求直接出现403响应头里还多了一个标记着风控命中的参数。那一刻我意识到现在稍微有点规模的网站反爬虫策略早就不再是单一手段了而是从网关到应用层再到浏览器环境的一整套体系。Python爬虫真正的分水岭不是你会不会写循环和正则而是你懂不懂反爬虫策略的分层逻辑以及能不能在必要时把Selenium自动化这套工具真正用好。这篇文章没有教科书式的长篇大论只讲我实际踩过、也实际解决过的问题反爬虫策略通常分哪几层每一层用什么思路去应对requests与Selenium各自的边界在哪里以及Selenium自动化进阶时那些文档里不会写的抗检测细节。适合刚越过入门阶段、准备认真玩爬虫的开发者也适合接了爬虫任务但被各种验证码和风控卡住的人。1. 先搞明白目标站点的反爬梯度从网关到浏览器指纹我见过太多人一上来就问怎么绕过验证码但验证码往往是最后一道防线前面还有好几层关卡。如果连前面的基础层都没过根本不会触发验证码。理解反爬虫策略的梯度比急着写代码重要得多。1.1 传输层IP频率与地域限制这是最底层也最无脑的一层。站点会统计每个IP在一定时间窗口内的请求次数超过阈值就临时封禁或要求验证码。比如某个接口限制是每分钟60次你单线程跑没问题一上多线程就立刻触发限制。应对思路也最直接控制请求频率、使用代理池分散请求来源。但要提醒一句免费代理质量非常差很多本来就是肉鸡或数据中心IP站点侧记录到这类IP的访问特征甚至会直接拉黑。自建代理池或者购买付费代理的时候优先选住宅代理数据中心的IP段在风控系统里往往有更高的风险权重。地域限制稍微特殊一些。有些站点的内容针对特定地区开放纯海外IP访问会被打回。这时候只关注IP类型还不够还得看IP的归属地区是否匹配站点预期。1.2 请求头与指纹一致性UA、Referer和Sec-CH-UA传输层过了之后就是请求头校验。很多站点并不只看User-Agent还会看请求头组合是不是活的浏览器发出的。我知道一个判断技巧一个正常的Chrome浏览器发出的Headers是高度配套的——UA版本和Sec-CH-UA里的版本号一致Accept-Language、Referer、Origin、Cookie之间也有关联逻辑。如果只换UA不换别的头风控系统很快就能通过特征交叉验证判定你是脚本。举个例子某个站点要求Sec-CH-UA里的Chromium版本和Sec-CH-UA-Full-Version-List完全匹配而单纯手工构造的UA池根本不会关注到这些细节。这时候光靠requests硬怼效率很低。1.3 参数层签名、加密字段与返回内容处理再往上一层是参数签名。服务端下发一段脚本或前端逻辑把某些参数通过加密算法算出一个动态sign值并加到请求里。你只改headers没用因为每次请求的sign都不一样。这一层的应对没有银弹。常见做法是逆向前端JS找到加密入口把算法搬运到Python里执行。但很多站点会做JS混淆、动态变量名、字符串拼接甚至把关键逻辑放到WebAssembly里逆向成本直线上升。还有一个容易被忽略的点动态渲染。数据根本不是出现在HTML源码里的而是页面加载完后通过XHR请求获取再用JS填充到DOM上。你用requests拿到的是空壳页面啥也分析不出来。遇到这种情况就该考虑换工具了。1.4 行为层浏览器环境与用户行为模拟最高级的反爬虫策略已经走到了行为分析这一步检测访问者是不是真人。判断依据包括鼠标轨迹、点击间隔、键盘输入速度、滚动节奏、浏览的停留时长甚至是CPU并发能力、Canvas指纹、WebGL参数。这些指标加起来就让看起来像浏览器和真的是浏览器之间产生了区别。requests达不到这个层次Selenium也有天然的暴露风险这就是为什么很多爬虫资深玩家最终都会去研究浏览器自动化。2. 请求层对抗UA池、代理与频率控制的正确打开方式其实很多被反爬的案例问题并不在于目标站点做了多高深的防护而是爬虫代码本身的指纹太明显。有人拿requests默认UA去请求数据如果我是网站方一秒钟全站都是同一个浏览器标识不封你封谁。2.1 UA池不是随便拼字符串UA池的设计原则是从真实浏览器复制而不是手写。我一般在本地开一个真实Chrome在开发者工具里复制一份完整的UA字符串然后再从UserAgentHistory之类的公开列表里补充一批不同系统、不同版本、不同内核的组合。只换UA还不够要配套替换Sec-CH-UA、Sec-CH-UA-Platform、Sec-CH-UA-Mobile等字段。这组字段在近几年的HTTP头里几乎是Chrome系浏览器的标配缺了就有脚本特征。import requests import random from fake_useragent import UserAgent ua UserAgent() headers { User-Agent: ua.random, Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Referer: https://example.com/list, Sec-CH-UA: Google Chrome;v120, Chromium;v120, Not-A.Brand;v99, Sec-CH-UA-Mobile: ?0, Sec-CH-UA-Platform: Windows, }当你的UA和其余请求头保持高度一致时基础过滤层基本就不会抓你了。2.2 代理池的使用逻辑以及为什么免费代理会拖垮你代理池的核心在于提取-校验-使用三步循环。不能拿到一个代理就用而是要先校验它当前的连通性、响应延迟、匿名等级。我通常维护一个小脚本定时从代理源拉数据再用一个低风险的目标URL做连通性测试把延迟在3秒以内的存进Redis队列供爬虫主程序消费。使用上要注意两点。第一代理切换频率不要过高。一个代理发20个请求不到就换反而可能触发短时间多地登录的风控标记。第二失效代理要自动剔除否则重试逻辑会一直卡在同一个坏代理上。session requests.Session() session.proxies { http: http://user:passproxy_ip:port, https: http://user:passproxy_ip:port, }一个成熟的爬虫架构代理是动态获取的最好封装成一个get_proxy函数请求前从队列里弹一个失败自动还回队列或丢弃。2.3 请求频率的随机化艺术固定间隔3秒、固定间隔5秒的写法比不间断请求更容易被识别。因为真实用户访问是有波动的不会像节拍器一样精确。我习惯用随机区间import time import random time.sleep(random.uniform(1.5, 4.0))但随机化只是基础。更高级的做法是根据目标网站的响应时间做动态调整如果响应慢了说明服务器压力大或风控正在收紧就主动增大间隔如果响应快可以适当提速。这个反馈式的节奏控制既保护自己的IP也大大降低触发风控的概率。3. 到了动态渲染这层requests的边界在哪里我经常被问到一个问题什么时候必须上Selenium答案取决于你要的数据是怎么出现在页面上的。3.1 数据藏在XHR里而不是HTML里如果打开页面的HTML发现内容区是空的正文数据是在页面加载完成后通过异步请求拿到的那requests直接请求页面URL毫无意义。这时候有两条路一是抓XHR接口。浏览器里按下F12切到Network面板刷新页面找到那条返回JSON数据的请求把URL、请求头、参数全部抠出来。这条路效率极高但前提是接口没有做签名校验。一旦接口参数里有个加密的sign这条路就很难走通。二是上Selenium。用真实浏览器加载页面等JavaScript执行完直接从DOM里取值。这一招不依赖接口是否加密因为浏览器自己就完成了全套解密流程。3.2 数据在事件循环之后才生成另一个常见场景是滚动加载。页面首屏只有20条数据滚动到底部再触发下一次拉取。如果你需要的是当天全量数据就必须不断地模拟滚动等着新数据渲染出来。这种场景用requests写会比较别扭因为你得自己去复刻分页接口的参数还要处理页码耗尽的条件。而Selenium天生擅长这种等一等、滚一滚、再取值的操作模式。3.3 Selenium的性能代价与选择方法论Selenium最大的毛病是慢。一个无头浏览器加载一个中等页面通常需要2到5秒同样的时间requests可能已经发完几十个请求了。所以选择工具之前先做评估数据是否直接存在于初始HTML用requests。数据是否在XHR中且无签名优先用requests抓接口。数据是否经过JS动态渲染、接口加密、或有行为验证踏踏实实上Selenium。这个判断流程我写在了工作笔记的首页每次接爬虫需求先回答这三个问题能省掉一半试错时间。4. Selenium自动化从能用走向抗检测很多人的Selenium停留在能跑阶段一放到有风控的网站上就被秒识别。原因很简单默认的Selenium打开的浏览器在JavaScript环境里有一堆明显的暴露特征。4.1 默认Selenium为什么容易被识别先看几个核心检测点navigator.webdriver属性。正常浏览器里这个值是undefinedSelenium打开的窗口里值是true。这是最常见的暴露点。window.chrome对象。Chrome浏览器里window.chrome是存在的但Selenium新开窗口里这个对象可能存在也可能缺失检测方会结合其他特征综合判断。navigator.plugins和navigator.languages。无头模式下这些数组或列表经常是空的或者不全。浏览器自动化特征。比如navigator.permissions里某些权限接口的返回值异常。4.2 基础配置三段式在代码层面至少要做三件事关闭自动化控制标志、注入脚本覆盖webdriver属性、设置合理的加载策略。from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() # 1. 关闭自动化控制 options.add_argument(--disable-blink-featuresAutomationControlled) # 2. 加载策略等页面主框架加载完即可 options.page_load_strategy eager # 3. 常规隐藏 options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) driver webdriver.Chrome(optionsoptions) # 4. 注入脚本覆盖webdriver标志 driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, { get: () undefined }); })execute_cdp_cmd是DevTools Protocol的调用入口能在页面任何脚本执行之前先注入我们的自定义脚本。这个时机的把握非常关键——如果你等页面加载完再去修改navigator.webdriver检测脚本早就读完了。4.3 拿到老版本Chrome与环境的兼容性问题还有一个容易被忽略的坑Selenium Manager虽然会帮你自动匹配chromedriver但如果你用的是旧项目chromedriver版本与浏览器版本差距太大就会报启动失败。解决办法不是手动下一个驱动就完事而是让Selenium自动管理driver版本或者显式指定driver路径。我踩过一次比较深的版本坑服务器环境里Chrome自动升级到120本地还是118的chromedriver整个调度脚本全部瘫痪。后来我在启动脚本里加了版本检测逻辑先读浏览器主版本再判断驱动是否匹配不匹配就直接静默更新。这套逻辑让我少熬了好几个夜。4.4 selenium-stealth和undetected-chromedriver我到底该选谁说到抗检测绕不开这两个工具。selenium-stealth本质是一批JS注入补丁它会修补navigator.webdriver、window.chrome、navigator.plugins等常见检测点。优点是轻量配合普通Selenium就能用。undetected-chromedriver则是在驱动层面做patch启动的浏览器更加接近真实环境。它会把webdriver特征隐藏在Driver后端同时自动匹配chromedriver版本。我的经验是普通数据采集场景selenium-stealth加手动配置基本足够遇到强风控场景直接上undetected-chromedriver更省心。但不管用哪个都不能只依赖库本身行为层面的模拟才是最难解决的。行为模拟方面我比较朴素不用ActionChains做机械的直线滑动而是按一定曲线移动鼠标并在目标区域短暂停留滚动页面时用driver.execute_script(window.scrollBy(0, random_height))模拟随机滚动位置。这些细节没法通过一个库全自动补齐只能靠脚本逻辑自己控制。5. 综合实操一个登录动态数据行为验证场景的完整链路理论讲了这么多不跑一个完整场景说不过去。我以一个典型的内部系统页面为例需要先登录登录后页面通过XHR动态渲染一份工单表格且在查询时偶尔会弹出滑块验证。需求是抓取全部工单的流水号与状态。5.1 第一步登录态获取与复用能不走登录流程就别走登录流程。我一般是手动操作浏览器完成一次登录然后把Cookies序列化保存下来下次启动直接加载。import pickle # 保存cookies with open(cookies.pkl, wb) as f: pickle.dump(driver.get_cookies(), f) # 下次启动加载 with open(cookies.pkl, rb) as f: cookies pickle.load(f) for cookie in cookies: driver.add_cookie(cookie)Cookie复用能大幅降低登录流程触发的风险也能减少账号被风控盯上的概率。如果必须走登录我建议输入账号密码时用真实的时间间隔去模拟键盘而不是send_keys一股脑灌进去from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time driver.get(https://example.com/login) WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, username)) ) # 模拟输入字母间随机停顿 username_input driver.find_element(By.ID, username) for char in your_account: username_input.send_keys(char) time.sleep(random.uniform(0.05, 0.2))5.2 第二步动态数据等待与抓取登录完成之后页面会发出XHR加载表格。此时如果立刻去取元素大概率取不到因为数据还没渲染完。我习惯用显式等待条件设置为页面中预期的数据行出现from selenium.webdriver.support import expected_conditions as EC WebDriverWait(driver, 15).until( EC.presence_of_element_located((By.CSS_SELECTOR, table tbody tr)) ) rows driver.find_elements(By.CSS_SELECTOR, table tbody tr)这里有个坑我必须强调不要在一个循环里去反复创建WebDriverWait那会白白增加等待开销。更好的思路是首次加载时等待一次后续操作里用条件判断决定是否继续等待。5.3 第三步遇到滑块验证时的处理思路滑块验证一旦出现纯模拟是比较费力的。我的处理顺序是这样的先判断滑块是否真的阻断后续操作。有些站点的滑块只挡一次通过后一段时间内不再出现。如果只出现一次就用ActionChains结合随机轨迹去滑动成功概率在中低强度风控场景下尚可。如果频繁触发就要考虑降低操作频率避免一次会话里执行太多请求。从源头减少触发的概率永远比解决触发结果要轻松。需要说明的是滑块拖拽理论上就是一次鼠标位移行为。不要用固定坐标直接拖要分段移动并加入抖动和停顿from selenium.webdriver import ActionChains slider driver.find_element(By.CLASS_NAME, slider-btn) ActionChains(driver) \ .click_and_hold(slider) \ .pause(0.3) \ .move_by_offset(50, random.uniform(-2, 2)) \ .pause(0.2) \ .move_by_offset(90, random.uniform(-1, 1)) \ .pause(0.3) \ .release() \ .perform()5.4 第四步数据清洗与落盘DOM里取出来的数据经常带着换行、空格和标签残留。清洗逻辑建议直接在解析阶段处理别等入库后再折腾import csv data [] for row in rows: cells row.find_elements(By.TAG_NAME, td) record [] for cell in cells: text cell.text.replace(\n, ).strip() record.append(text) data.append(record) with open(data.csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerows([流水号, 状态, 时间]) writer.writerows(data)整个过程最关键的地方在于等待-判断-兜底三步。等不到元素就判断是不是页面结构变了判断完之后再决定是重试还是截图排查。6. 爬虫稳定性的血泪经验等待、重试与风控指纹管理代码能跑只是最基础的一步真正决定爬虫项目生命周期的是稳定性。我能连续跑几天的脚本和跑半小时就挂的脚本差别都在这些细节里。6.1 等待机制显式等待优于隐式等待优于无脑sleep无脑sleep最大的问题是时间不可控。页面快的时候浪费了时间页面慢的时候又不够用。隐式等待的问题在于它是全局设置的对每个find_element都生效一旦某个条件始终不满足反而拉长了整个会话的耗时。显式等待最语义化也最精准WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, submit-btn)) )我一般来说不会用EC.visibility_of_element_located代替EC.presence_of_element_located来获取存在于DOM但不可见的元素。你要看你需要的是元素存在、可见还是可点击这三个条件对应的EC函数完全不同。6.2 定位元素的坑动态ID、同名Class和CSS伪类现在很多前端框架会生成动态class或id每次刷新都不一样。如果你把元素定位写死脚本很快会失效。我自己的写法是优先用相对稳定的属性组合比如部分匹配driver.find_element(By.CSS_SELECTOR, [class*product-item])多个元素共用一个class时我会先定位整个列表容器再在容器内部按索引取值而不是全页面乱找container driver.find_element(By.CLASS_NAME, list-wrapper) items container.find_elements(By.CSS_SELECTOR, div[data-id])6.3 异常重试与断点续抓一个长跑脚本必须有重试机制。我的封装习惯是写一个retry装饰器把每次操作包进去import functools import time def retry(max_tries3, delay2): def decorator(func): functools.wraps(func) def wrapper(*args, **kwargs): for i in range(max_tries): try: return func(*args, **kwargs) except Exception as e: print(f第{i1}次执行失败: {e}) time.sleep(delay) raise return wrapper return decorator断点续抓的意思是把已经成功抓取的数据记录在本地每次启动时加载已经完成的集合跳过它们。避免跑到一半断了又要从头再来。6.4 风控指纹管理的思维框架把上面所有内容串起来能看到一个完整的风控指纹管理框架网络层IP的质量与稳定性。请求层UA、Headers、Sec-CH-UA的配套一致。会话层Cookie的持久化与温度。行为层输入速度、滚动节奏、鼠标轨迹。环境层webdriver属性、插件列表、Canvas指纹。反爬虫策略不是某个单一机制而是一整套基于指纹相似度的概率评分。爬虫能做的就是尽可能让所有维度的特征都趋近于一个真实用户同时保持合规合理的使用场景。我一直坚持一个原则爬取公开数据、只用于学习和研究、尊重目标网站的robots声明和服务条款。爬虫这个技能边界感越清晰路才走得越远。最后说一个我自己的切身体会如果你刚接触Selenium别一上来就追求复杂的反检测配置先老老实实把定位、等待、取值这三个基本功打扎实。因为再强的隐藏手段也救不了一个连元素都定位不准的脚本。等基础稳住之后再去研究那些高级玩法你会发现所谓反爬虫策略本质上就是在和对方比谁更能理解浏览器。而理解浏览器这件事做多了之后看整个Web前端都会有一种更通透的感觉。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

NestJS生产级限流实战:从装饰器到滑动窗口与可观测性 2026/10/2 11:00:03

NestJS生产级限流实战:从装饰器到滑动窗口与可观测性

1. 为什么 Nest 的限流不是加个装饰器就完事了?在 NestJS 生态里,“限流”这个词常被新手误读成一个“开箱即用”的功能开关——看到Throttle()装饰器,以为贴上就能防爆;看到nestjs/throttler包名,以为装完 npm instal…

阅读更多 →
DTFT核心性质全推导:从定义到卷积定理与Parseval定理 2026/10/2 11:00:03

DTFT核心性质全推导:从定义到卷积定理与Parseval定理

干了这么多年信号处理教学和工程实践,我一直有个很深的体会:很多人在考试或者面试前,会把“离散系统傅里叶变换”(DTFT)的常用结论背得滚瓜烂熟,但真被问到“这个结论是怎么来的”时,往往说不出…

阅读更多 →
基于低频FRF矩阵的刚性体惯性参数反演方法 2026/10/2 10:59:57

基于低频FRF矩阵的刚性体惯性参数反演方法

简介:本资源是一份面向机械工程领域研究人员与工程师的刚性体惯性参数识别技术实践指南,聚焦频响函数(FRF)驱动的参数辨识方法,解决复杂结构(如发动机、航天器部件)在多体动力学仿真中惯性参数难…

阅读更多 →
叠纸闪耀暖暖的3D换装工业化实践 2026/10/2 10:59:56

叠纸闪耀暖暖的3D换装工业化实践

1. 项目概述:当换装游戏不再只是“贴图”,而是一场三维建模的工业革命“叠纸”“闪耀暖暖”“2D到3D”——这三个词在2019年前后几乎同时引爆国内二次元与游戏美术圈。不是因为某张新皮肤海报,也不是因为某个联动活动,而是叠纸在一…

阅读更多 →
开源三天 9900 star!把 CodeX 塞进 Claude Code 后,TaoToken 统一 Key 怎么配? 2026/10/2 10:59:44

开源三天 9900 star!把 CodeX 塞进 Claude Code 后,TaoToken 统一 Key 怎么配?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Kimi Claw 实战:在 Kimi 里用 OpenClaw 的完整指南与 TaoToken 配置 2026/10/2 10:59:43

Kimi Claw 实战:在 Kimi 里用 OpenClaw 的完整指南与 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉