做数据采集的人,几乎都逃不过“反爬”这两个字。就在上个月,我给自己那个只会聊天的AI助手——代号“小龙虾”——加了一套爬虫技能,让它能自己看网页、提取数据、清洗入库。整个过程踩了不少坑,但也整理出了一套相对完整的思路。这篇内容,我会把这套方案的架构、工具选择和实战代码都拆开讲清楚,尤其会聊一聊反爬机制到底在防什么,以及哪些“突破”手段是合规的、哪些绝对不能碰。如果你也在做数据采集,或者想给自己的AI应用加上联网抓取能力,这篇文章应该能帮你避开我踩过的那些坑。
1. 项目全景:为什么给AI装爬虫技能
1.1 “小龙虾”的初始能力边界
我一开始做“小龙虾”这个项目,其实只是想让它能回答一些需要实时数据的问题。比如用户问“某款产品最近的价格趋势”“某个论坛的热帖标题”,它不能凭空编,需要有真实数据来源。原本的方案很简单:人工写几个脚本,定时抓数据喂给它。但问题很快暴露出来——脚本只针对固定的页面结构,一旦页面改版,选择器全部失效;而且碰到有反爬策略的站点,脚本跑不了几次就会被限制访问。
后来我换了个思路:既然“小龙虾”是AI,为什么不让它自己学会“看页面”和“提取信息”?于是我给这个AI助手设计了一套爬虫技能层。它不再是单纯的聊天模型,而是可以调用工具、分析页面结构、动态调整抓取策略的“数据小助手”。这个改造听起来简单,真正做起来却涉及三个层面的问题:反爬机制识别、页面解析策略、数据清洗流程。每一个层面都有不少隐藏的坑。
1.2 反爬机制真正想拦住的是谁
先说反爬。很多人一听到“反爬”就觉得是猫鼠游戏,非要绕过对方的防护才算赢。但实际动手后你会发现,大部分反爬机制的目标不是阻止所有爬虫,而是阻止“不守规矩”的爬虫。常见的几类反爬手段,本质都在做同一件事:判断访问者是不是真人,以及访问行为是否合理。
- User-Agent校验:服务器检查HTTP请求头里的UA字段,非浏览器UA直接拒绝。
- 访问频率限制:同一IP在短时间内请求次数过多,触发限流甚至封禁。
- 动态渲染:数据由JavaScript在浏览器里异步生成,直接请求HTML拿不到内容。
- 行为校验:检测鼠标轨迹、点击间隔、页面停留时间等,判断是否像真人。
- 验证码:当风控系统判断可疑时,弹出验证码要求人机验证。
我做的第一版爬虫,一上来就模拟请求去抓一个排行榜页面,结果连返回的响应都变成了“403 Forbidden”。后来我仔细看了对方返回的HTML,发现里面有一行提示,大概意思是“请求频率超限,请稍后再试”。这才意识到,反爬不是要把所有爬虫赶尽杀绝,而是在逼你用更尊重站点资源的方式去抓数据。
合规的应对策略其实也简单:模拟真实浏览器的基本请求头、控制抓取频率、尽量用浏览器自动化处理动态渲染页面,遇到验证码就停下来检查自己的节奏。这套思路放进去之后,“小龙虾”的抓取成功率立刻高了很多。
1.3 方案选型:为什么用“浏览器自动化+AI解析”而不是纯请求
最开始我倾向于用纯请求库加正则表达式,因为性能好、资源占用低。但实际遇到动态渲染页面后,这个方案直接失效。我试着用接口逆向的方式去找页面背后的数据接口,确实能找到一部分,但有些站点会对接口参数做签名校验,逆向成本非常高,而且一旦对方更新签名算法,又要重新分析,维护成本完全不可控。
后来我转向浏览器自动化方案。让“小龙虾”像人一样打开一个无头浏览器,等页面加载完,再读取渲染后的DOM。这个方案的优点是不太依赖页面的具体实现方式,不管是Vue还是React,最后渲染出来的都是标准HTML,AI可以直接从中提取信息。缺点是速度和资源消耗都远大于纯请求,所以它更适合中小规模的定向采集,不适合大规模爬取。
配合AI解析之后,方案变得更灵活了。我不需要预先写死提取规则,而是把渲染后的HTML片段丢给大语言模型,让它分析出目标数据的CSS选择器或者XPath。这样即使页面改版,只要把新的HTML片段再给模型看一遍,规则就会自动更新,不需要人肉维护一堆正则表达式。
2. 技能架构与工具选型
2.1 整体架构:请求层、解析层、AI决策层
整个“小龙虾”的爬虫技能,我拆成了三层。
第一层是请求层,负责获取页面内容。这个层级要考虑请求头、超时时间、重试机制、频率控制。对于静态页面,用HTTP请求库就够了;对于动态页面,则要启动浏览器自动化框架。
第二层是解析层,负责从HTML里提取结构化数据。以前我习惯直接用XPath或CSS选择器,但选择器写起来麻烦,页面一改就崩。现在我会把页面文本和结构交给AI,让它生成提取规则,然后我用一个通用的提取器去执行这些规则。
第三层是AI决策层,也是“小龙虾”的灵魂。这一层接收用户的自然语言指令,比如“抓取这个页面里的商品名称和价格”,然后自动判断:该用什么请求方式、是否需要渲染JS、提取完数据后怎么清洗、要不要保存到数据库。AI决策层的输出是一份可执行的任务计划,而不是一段写死的代码。
这三层各司其职,好处是任何一层出问题都可以单独替换。比如请求层发现某个站点需要登录才能看数据,那就不用硬闯,直接在AI决策层判断“这个数据源不适合自动化抓取”,转而搜索公开API。
2.2 工具选型:每个环节的合适选择
工具选型我做了不少对比,重点考虑三个维度:社区活跃度、文档质量、和AI模型的配合难度。
请求层我用了两个方案:一个是老牌的requests库,适合简单页面;另一个是httpx,支持异步,并发性能更好。requests的优点是生态成熟、代码简单,但遇到连接复用和异步请求时有点力不从心。httpx的API和requests很像,但原生支持异步,我用它来处理需要同时抓取多个页面的场景。
解析层我选过BeautifulSoup、lxml和parsel。BeautifulSoup最直观,适合新手,但性能一般。lxml速度最快,XPath支持完善,可语法太硬。parsel是Scrapy提炼出来的解析库,封装得干净,既有CSS选择器也支持XPath,内存占用也可控。我最后留了parsel,因为它和AI生成的选择器兼容性最好。
浏览器自动化工具我在Selenium和Playwright之间犹豫了很久。Selenium名气大、资料多,但安装驱动麻烦,运行也不够稳定。Playwright的API设计更现代,自带浏览器下载和管理,还可以通过CDP协议和AI模型交互,所以最终选了Playwright。对于“小龙虾”来说,它只需要加载页面、等待关键元素出现、导出HTML,Playwright用起来最省心。
2.3 给“小龙虾”的AI能力接口
AI决策层我设计了一套简易的函数调用机制。“小龙虾”本身是一个大语言模型接口,但我给它注册了几个工具函数,包括“fetch_page”“render_page”“extract_data”“save_dataset”。模型根据用户的问题生成JSON格式的工具调用指令,系统去执行并把结果返回给模型,模型再组织成回答。
这套机制的核心是让AI学会“看情况办事”。比如用户说“帮我看看这个页面里有哪些新闻标题”,模型会先选择“render_page”而不是“fetch_page”,因为它知道新闻站点的内容很可能由JS渲染。如果用户要求“每5分钟抓一次”,模型会自动在请求层插入随机延时,降低被封风险。
工具接口设计有个很关键的细节:要给模型提供足够清晰的参数说明。比如“fetch_page”需要传入url、headers、timeout;“extract_data”需要传入html_snippet和schema。模型看到这些说明后才知道怎么调用。没有详细的工具说明,AI再强也会瞎猜。
3. 实战:三类典型反爬场景的合规应对
3.1 场景一:UA校验与基础请求头
先看最简单的场景。很多网站老旧的防护机制就是检查User-Agent,如果是Python默认的UA,直接拒绝。我给“小龙虾”设计请求层时,把常用浏览器的UA维护成一份配置,发起请求前随机取一个,并补上常见的Accept、Accept-Language、Connection头。
下面是一个简化版的请求函数,用requests实现:
import requests import random BROWSER_UAS = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 " "(KHTML, like Gecko) Version/16.1 Safari/605.1.15", ] def fetch_page(url): headers = { "User-Agent": random.choice(BROWSER_UAS), "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Connection": "keep-alive", } resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() return resp.text这样改完后,原本被UA校验挡掉的页面大多能正常返回。但我提醒一句:这只是最基本的一步,现在很多站点的反爬是组合拳,UA校验往往和频率限制一起出现。
实操心得:不要伪造一个根本不存在的UA。有的开发者随手写了“Googlebot”的UA去抓页面,这样做既不符合规范,也容易触发更严格的反爬。更稳妥的做法是模拟普通用户的真实环境,比如Windows Chrome或者macOS Safari的UA。
3.2 场景二:动态渲染页面拿不到数据
第二个场景非常常见。用requests请求某论坛的帖子列表,返回的HTML里只有空壳,数据全是通过JavaScript异步加载的。这时候就需要用Playwright渲染完整页面再提取。
我给“小龙虾”封装了一个“render_page”工具,代码如下:
from playwright.sync_api import sync_playwright def render_page(url, wait_for="networkidle", timeout=30000): with sync_playwright() as p: browser = p.chromium.launch(headless=True) context = browser.new_context( user_agent=( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36" ), viewport={"width": 1280, "height": 800}, locale="zh-CN", ) page = context.new_page() page.goto(url, wait_until=wait_for, timeout=timeout) page.wait_for_timeout(2000) html = page.content() browser.close() return html我知道很多人在这一步喜欢加“随机休眠”,让页面更像真人操作。但更重要的其实是等待策略。Wait_until设置为“networkidle”可以等网络空闲后再返回,如果数据加载特别慢,还可以显式等待某个选择器出现,比如“page.wait_for_selector('.list-item')”。
这个场景的难点不是代码,而是判断一个页面到底需不需要渲染。我的经验是:先用普通请求抓一遍,如果HTML长度正常但目标字段是空的,或者页面结构里有明显的script标签包含大量数据,再切换成渲染模式。“小龙虾”的AI决策层会先做一次预判,减少不必要的浏览器启动。
3.3 场景三:访问频率限制与风控提示
第三个场景是高频访问触发限流。我最早测试某个商品比价站时,用单线程连续抓了100个页面,结果第80个请求开始,页面返回里出现了一段“请完成安全验证”的提示。这说明触发风控了。
合规的应对措施不是去破解验证码,而是让AI自动降速并暂停。我在请求层加了一个“抓取状态检查器”,专门分析返回内容里是否有验证码、滑块、异常跳转等关键词。一旦发现异常,立刻停止当前任务,并进入指数退避模式。
import time import random def smart_request(url, session, max_retries=3): for attempt in range(max_retries): resp = session.get(url, timeout=10) content = resp.text if "安全验证" in content or "滑块" in content: wait_time = (2 ** attempt) + random.uniform(0, 1) time.sleep(wait_time) continue if resp.status_code == 200: return content raise RuntimeError("请求被风控拦截,已自动停止")指数退避的意思是每次重试的等待时间翻倍,第一次等2秒,第二次等4秒,第三次等8秒。这样既不会立刻激怒风控系统,也给对方留出恢复时间。如果重试三次仍然被拦,就直接放弃这个数据源,不硬刚。
实操心得:在“小龙虾”里,我还会让AI记录每个站点的“健康分”。抓取成功一次加一分,触发风控一次扣十分。分数低于阈值后,AI会主动向用户报告“该站点暂时不适合抓取,建议改用官方API”。这个机制让我避免了很多无谓的封禁。
4. 反爬对抗的边界:技术可行不等于可以乱来
4.1 Robots协议与法律底线
标题里写了“突破反爬”,但这个“突破”必须是合规前提下的技术突破,而不是钻法律空子。每个正规站点都会在robots.txt里声明哪些路径允许抓取、哪些不允许。开发者在写爬虫之前,第一件事应该是查看目标站点的robots.txt。
我遇到过有开发者直接抓取某平台需要登录才能看到的数据,还沾沾自喜地觉得“绕过了权限校验”。这种做法非常危险,已经不只是反爬问题,而是越权访问。即使技术上能做到,一旦被追责,个人很难承担后果。
不要试图绕过验证码、不要破解登录态、不要对非公开数据强行抓取。这是我在团队里反复强调的三条红线。“小龙虾”的AI决策层里也内置了这条规则:当页面出现登录墙或验证码时,默认判定为“不可抓”,并提示用户寻找授权数据源。
4.2 优先用官方API和公开数据集
很多时候我们想抓的数据,其实对方已经提供了更稳定、更合法的获取途径。比如很多社交平台、天气服务、地图服务都有官方API,虽然可能限频或者收费,但数据质量和稳定性远高于爬虫。
我在设计“小龙虾”时,把“寻找官方API”作为一个优先动作。AI会先根据用户描述判断数据所属领域,然后自动搜索有没有官方接口。只有确认没有API,或者API权限不能满足需求时,才考虑页面抓取。
这样做还有一个额外好处:维护成本低。页面结构随时会变,但API一般相对稳定。如果你做的是长期数据采集项目,用API几乎是唯一的可持续方案。
4.3 把“合规判断”写进AI脚本
既然AI已经参与到爬虫决策里,那合规判断也应该交给它。我做了一个简单的规则引擎,放在AI决策层旁边。规则包括:检查robots.txt、检查页面是否需要登录、检查抓取频率是否合理、检查数据是否涉及个人隐私。
这套规则引擎会在每次抓取任务开始前执行,只要有一项不符合,就中止任务并向用户说明原因。比如用户要求抓取某论坛的用户私人主页,AI会直接拒绝,因为那属于个人隐私数据。“小龙虾”不再只是一个能抓数据的工具,它更像一个有边界意识的数据助手。
偷懒的办法是只把规则写进系统提示词里,让大模型“自觉”。但我个人建议做成独立的规则模块,因为提示词可以被用户改写,规则却始终独立存在,能够更可靠地守住底线。
5. 常见问题与排查技巧实录
5.1 AI生成的选择器为什么老失效
我在实际使用中遇到最频繁的问题,就是AI生成的CSS选择器明明在某一时刻有效,过几天再运行就失效了。原因很简单:页面结构变了。
排查时,我先登录浏览器开发者工具,手动查看目标元素的当前class和id。然后对比“小龙虾”上次生成的选择器,看看差异在哪。通常有两种情况:一种是class名带上了随机后缀,比如“list-item-2398”变成“list-item-7812”;另一种是整块DOM结构移动了层级。
解决办法是让AI生成“语义更稳”的选择器。不要用纯class名,而是优先用文本内容或者结构化属性。比如:
// 不稳:.list-item-2398 // 更稳:[data-type="product"] .title另外我还会在提取规则里加一个“自检步骤”。抓取结果为空时,AI会自动对比页面里是否有相似结构,并尝试重新生成选择器。这样即使页面改版,系统也能在无人干预的情况下自我修复。
5.2 抓下来的数据乱码或字段错位
乱码问题多出在编码识别上。很多网页没有声明charset,或者声明与实际编码不一致。我用requests时,直接使用resp.text可能会得到乱码,因为requests会猜编码。
我的习惯是用resp.content拿到原始字节,再通过代码或页面meta标签判断编码,然后手动解码。
import chardet def decode_response(resp): raw = resp.content detected = chardet.detect(raw[:5000]) encoding = detected.get("encoding", "utf-8") return raw.decode(encoding, errors="replace")字段错位则通常是选择器匹配到了错误元素。比如想抓价格,结果把“原价”和“折扣价”混在一起。这个时候不要只给AI一段HTML,而是把目标字段的上下文也喂给它,让它理解哪些是有效数据。
5.3 请求被风控后的恢复步骤
一旦确认被风控,很多人会焦虑地不停重试,结果越试越黑。正确的恢复步骤是:先完全停止任务,让IP“冷静”一段时间;然后检查抓取日志,确认触发风控的原因。
在我的项目里,“小龙虾”被风控时会自动切断该站点任务,转去抓其他数据源,然后等待至少30分钟再试探性请求一次。如果试探请求返回正常,才恢复抓取,但会把原来的并发数减半,抓取频率也降到原来的一半。
如果你同时抓取了多个站点,建议给不同站点设置独立的限速配置。有的站点比较宽容,每秒10次都没事;有的站点比较敏感,每秒2次都会被警告。把这些配置做成可调节参数,比写死在代码里要灵活得多。
5.4 快速排查速查表
下面这张表是我在实际排障过程中整理出来的,按“现象-原因-解法”的顺序罗列,希望对你有帮助。
| 现象 | 可能原因 | 合规解法 |
|---|---|---|
| 返回403 | UA被校验拦截 | 设置真实浏览器UA,补全请求头 |
| 页面无数据 | 动态渲染未执行 | 换用浏览器自动化工具渲染 |
| 第N次请求被拦 | 触发频率限制 | 降低频率,启用指数退避重试 |
| 出现验证码 | 行为或频率异常 | 立即停止,等待窗口期 |
| 提取字段为空 | 当前选择器失效 | 让AI重新分析HTML生成新规则 |
| 中文乱码 | 编码识别错误 | 使用chardet检测编码后手动解码 |
| 登录墙 | 数据非公开 | 放弃抓取,寻找API或授权 |
最后再分享一个小技巧:爬虫技能不是越大越好,而是要能进能退。你给AI加的能力越多,它跑偏的概率也越大。我在“小龙虾”上线第二周的时候,发现它会自作主张去抓一些明显不该碰的节点,后来加上“合规规则引擎”才把问题控制住。现在回头看,真正的“数据高手”不是能绕过多少限制,而是知道该在哪儿停下。