反爬总原则
目标不是“完全防住”,而是“提高爬取成本”到对方放弃
- 监控:先埋点日志,识别异常流量,再针对性防御
- 用户体验优先:避免对正常用户造成干扰
- 渐进式防御:从低成本措施开始(如 Headers 校验),再叠加高阶方案(如行为分析)
主流的爬虫方式
效率、稳定性和开发成本:API → 静态 → 逆向 → RPA
| 方式 | 核心思想 |
|---|---|
| 1. 静态解析 | “URL 有规律,直接拼接抓” |
| 2. 调用 API | “绕过前端,直取数据源” |
| 3. RPA 自动化 | “像人一样操作浏览器” |
| 4. JS 逆向 | “破解前端加密,伪造合法请求” |
| 5. 无限滚动 | “要么抓 API,要么自动滚” |
| 6. 混合爬取 | “浏览器探路,HTTP 冲刺” |
方式 1:静态 URL 遍历 + HTML 解析
适用:传统服务端渲染网站,分页通过 ?page=1 或 /page/2/ 实现
核心思路
- 分页由 URL 控制,每一页是独立 HTML 页面
- 直接构造所有分页 URL → 下载 → 解析 → 提取数据
实现步骤
-
分析分页规律
- 观察 URL:
https://example.com/news?page=1→page=2,page=3... - 或路径:
/news/page/1/→/page/2/
- 观察 URL:
-
确定总页数(可选)
- 从第一页解析出“共 50 页”或“总条数 1000 条,每页 20 条 → 共 50 页”
-
循环请求每一页
-
用 HTML 解析器提取目标数据
方式 2:直接调用后端分页 API
适用:前端通过 AJAX 加载数据(如 React/Vue 应用)
核心思路
- 网页内容由 JavaScript 调用 API 获取
- 绕过前端,直接模拟浏览器向 API 发请求
实现步骤
- 打开浏览器开发者工具(F12)
- 点击“下一页”,观察 Network → XHR/Fetch 请求
- 找到分页 API 的 URL 和参数(如
page=2,limit=20) - 复制请求头(Headers),特别是
Cookie、Authorization、X-Requested-With - 用代码模拟该请求,循环翻页
方式 3:RPA / 浏览器自动化翻页
适用:无 API、JS 渲染复杂、需模拟点击的页面
核心思路
- 启动真实浏览器(无头模式)
- 自动点击“下一页”按钮或滚动加载
- 等待新内容出现后,从 DOM 中提取数据
实现步骤
-
启动 Playwright/Selenium 浏览器
-
打开目标页面
-
循环:
- 提取当前页数据
- 查找并点击“下一页”按钮(或滚动到底部)
- 等待新内容加载(用
wait_for_selector)
-
直到“下一页”按钮消失或不可点击
方式 4:JS 逆向 + 参数生成
适用:API 请求含加密参数(如 sign、X-Bogus、msToken)
核心思路
- 前端 JS 动态生成请求参数
- 通过调试还原算法,用代码复现参数生成逻辑
实现步骤
- 在 DevTools 中定位分页请求
- 查看 Initiator(发起者),找到 JS 文件和函数
- 设置断点,观察参数如何生成(如
sign = md5(url + timestamp + secret)) - “扣代码”:复制关键 JS 函数
- 用
execjs或重写为 Python 函数 - 每次请求前动态生成合法参数
方式 5:无限滚动模拟
适用:信息流页面(如微博、小红书),滚动自动加载
核心思路
- 优先方案:抓包找到滚动触发的 API,直接调用
- 备选方案:用浏览器自动滚动,监听 DOM 变化
实现步骤(推荐 API 方式)
- 滚动页面,观察 Network 中新增的 XHR 请求
- 找到类似
/api/feed?offset=20&count=20的接口 - 循环增加
offset值,直到返回空数据
方式 6:混合智能爬取
适用:复杂生产环境,需兼顾效率与鲁棒性
核心思路
- 前期用浏览器:登录、获取 Token、探测 API
- 后期用 HTTP 客户端:高效批量请求
- 失败时回退到浏览器重试
实现步骤
- 用 Playwright 登录,获取 Cookie 和 Token
- 从页面中提取 API 地址和参数模板
- 后续分页全部用
requests调用 API - 若某页失败(如 403),用浏览器重新获取 Token 并重试
防御方法
防御「静态 URL 遍历 + HTML 解析」
| 步骤 | 操作 | 技术实现 |
|---|---|---|
| 1.1 埋点监控 | 记录所有分页请求的 User-Agent、IP、Referer、访问频率 | Nginx 日志 + ELK 或自建日志系统 |
| 1.2 拒绝无 Referer 请求 | 要求分页请求必须来自本站 | Nginx 配置:if ($http_referer !~* "^https://yourdomain.com") { return 403; } |
| 1.3 检查 User-Agent | 拒绝常见爬虫 UA(如 python-requests) | 后端中间件(Python/Node.js/Java):python<br>if 'python' in request.headers.get('User-Agent', '').lower():<br> return 403<br> |
| 1.4 IP 频率限制 | 同一 IP 每分钟最多 20 次分页请求 | 使用 Redis 计数:bash<br>INCR user:ip:1.2.3.4<br>EXPIRE user:ip:1.2.3.4 60<br>超过阈值返回 429 |
| 1.5 动态分页 token | 每次渲染页面时生成一次性 token,下一页需携带 | 后端模板中嵌入:<a href="/page/2?token={{ next_token }}">下一页</a>服务端校验 token 有效性并单次使用 |
防御「直接调用后端分页 API」
| 步骤 | 操作 | 技术实现 |
|---|---|---|
| 2.1 强制登录态 | 分页 API 必须携带有效 Session 或 JWT | 前端登录后,API 请求自动带 Cookie 或 Authorization: Bearer <token> |
| 2.2 添加请求签名(Sign) | 对参数 + 时间戳 + 密钥生成签名 | 前端 JS 示例:js<br>const sign = md5(`${params}×tamp=${ts}&key=SECRET`);<br>后端用相同逻辑验证 |
| 2.3 校验关键 Headers | 要求包含 X-Requested-With: XMLHttpRequest 和 Origin | 后端中间件校验:python<br>if request.headers.get('X-Requested-With') != 'XMLHttpRequest':<br> abort(403)<br> |
| 2.4 签名密钥动态化 | 每 24 小时轮换 SECRET_KEY(通过配置中心下发) | 使用 Apollo/Nacos 管理密钥,前端通过安全通道获取(如登录后返回临时 key) |
| 2.5 接口路径混淆 | API 路径加入时间戳或随机字符串 | 如 /api/v1/list_20240615/,每日更新,旧路径 404 |
防御「RPA / 浏览器自动化翻页」
| 步骤 | 操作 | 技术实现 |
|---|---|---|
| 3.1 检测 WebDriver (W3C 标准的浏览器自动化控制协议)特征 | 检查浏览器是否被自动化控制 | 前端 JS 插入检测代码:```jsif (window.webdriver |
| 3.2 收集浏览器指纹 | 生成唯一设备 ID(Canvas + WebGL + 字体) | 使用开源库 FingerprintJS:js<br>const fp = await FingerprintJS.load();<br>const { visitorId } = await fp.get();<br>sendToServer(visitorId);<br> |
| 3.3 绑定 Token 与指纹 | 登录 Token 与设备指纹绑定,换环境失效 | 后端存储:token → {user_id, device_fingerprint, expire}每次 API 请求校验指纹一致性 |
| 3.4 行为埋点 | 上报鼠标移动、点击坐标、滚动速度 | 前端监听事件:js<br>window.addEventListener('mousemove', e => log(e.clientX, e.clientY));<br>后端分析是否为“直线匀速”等机器人特征 |
| 3.5 关键内容延迟加载 | 数据在用户停留 1.5 秒后才渲染 | 前端:js<br>setTimeout(() => renderData(), 1500);<br> |
防御「JS 逆向 + 参数生成」
| 步骤 | 操作 | 技术实现 |
|---|---|---|
| 4.1 JS 混淆压缩 | 使用 Webpack + JavaScript Obfuscator | webpack.config.js 配置:js<br>plugins: [new JavaScriptObfuscator({ rotateStringArray: true })]<br> |
| 4.2 关键函数拆分 | 将签名逻辑分散在多个文件,增加还原难度 | 如 part1.js + part2.js + runtime.js 动态组合 |
| 4.3 环境检测 | 检测是否在浏览器中执行 | 在签名函数开头加:```jsif (typeof window === 'undefined' |
| 4.4 使用 WebAssembly (WASM) | 将核心算法编译为 WASM | 用 Rust/C++ 编写签名逻辑 → 编译为 .wasm → 前端调用(极难逆向) |
| 4.5 动态更新算法 | 每周自动更换签名逻辑(通过 AB 测试灰度) | 构建多套签名方案,通过配置开关切换 |
防御「无限滚动模拟」
| 步骤 | 操作 | 技术实现 |
|---|---|---|
| 5.1 改用 Cursor 分页 | 返回 next_cursor 而非 offset | API 响应:json<br>{ "items": [...], "next_cursor": "abc123xyz" }<br>下一页请求:?cursor=abc123xyz |
| 5.2 限制最大数据量 | 未登录用户最多返回 500 条 | 后端逻辑:python<br>if not user.is_logged_in and offset > 500:<br> return []<br> |
| 5.3 滚动行为验证 | 要求前端上报滚动事件 | 前端:js<br>let lastScroll = 0;<br>window.addEventListener('scroll', () => {<br> if (window.scrollY > lastScroll + 100) {<br> sendScrollEvent();<br> lastScroll = window.scrollY;<br> }<br>});<br>后端校验是否有真实滚动 |
| 5.4 数据脱敏 | 对未授权用户隐藏关键字段 | 如返回 { title: "xxx", content: "***需登录查看***" } |
防御「混合智能爬取」
| 步骤 | 操作 | 技术实现 |
|---|---|---|
| 6.1 多维风控引擎 | 综合 IP、设备、行为、频率打分 | 自建规则引擎或接入第三方(如阿里云风险识别):- 高频请求:+30 分- 无鼠标轨迹:+20 分- 新设备:+10 分总分 > 50 → 弹验证码 |
| 6.2 Token 与设备强绑定 | 登录后 Token 仅对该设备有效 | 后端存储结构:{ token: "abc", device_hash: "fp123", user_id: 1001 }每次请求校验 device_hash |
| 6.3 接口灰度发布 | 对 1% 用户启用新 API 结构 | 通过网关(如 Kong、Nginx)路由:if (user.id % 100 == 0) use_new_api()使通用爬虫大面积失效 |
| 6.4 设置蜜罐数据 | 对可疑 IP 返回特殊“陷阱数据” | 如商品价格设为 999999,若该数据出现在竞品网站,即可取证 |