
做数据采集的人几乎都逃不过“反爬”这两个字。就在上个月我给自己那个只会聊天的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最后渲染出来的都是标准HTMLAI可以直接从中提取信息。缺点是速度和资源消耗都远大于纯请求所以它更适合中小规模的定向采集不适合大规模爬取。配合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。对于“小龙虾”来说它只需要加载页面、等待关键元素出现、导出HTMLPlaywright用起来最省心。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/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Connection: keep-alive, } resp requests.get(url, headersheaders, timeout10) 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_fornetworkidle, timeout30000): with sync_playwright() as p: browser p.chromium.launch(headlessTrue) 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}, localezh-CN, ) page context.new_page() page.goto(url, wait_untilwait_for, timeouttimeout) 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_retries3): for attempt in range(max_retries): resp session.get(url, timeout10) 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-typeproduct] .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, errorsreplace)字段错位则通常是选择器匹配到了错误元素。比如想抓价格结果把“原价”和“折扣价”混在一起。这个时候不要只给AI一段HTML而是把目标字段的上下文也喂给它让它理解哪些是有效数据。5.3 请求被风控后的恢复步骤一旦确认被风控很多人会焦虑地不停重试结果越试越黑。正确的恢复步骤是先完全停止任务让IP“冷静”一段时间然后检查抓取日志确认触发风控的原因。在我的项目里“小龙虾”被风控时会自动切断该站点任务转去抓其他数据源然后等待至少30分钟再试探性请求一次。如果试探请求返回正常才恢复抓取但会把原来的并发数减半抓取频率也降到原来的一半。如果你同时抓取了多个站点建议给不同站点设置独立的限速配置。有的站点比较宽容每秒10次都没事有的站点比较敏感每秒2次都会被警告。把这些配置做成可调节参数比写死在代码里要灵活得多。5.4 快速排查速查表下面这张表是我在实际排障过程中整理出来的按“现象-原因-解法”的顺序罗列希望对你有帮助。现象可能原因合规解法返回403UA被校验拦截设置真实浏览器UA补全请求头页面无数据动态渲染未执行换用浏览器自动化工具渲染第N次请求被拦触发频率限制降低频率启用指数退避重试出现验证码行为或频率异常立即停止等待窗口期提取字段为空当前选择器失效让AI重新分析HTML生成新规则中文乱码编码识别错误使用chardet检测编码后手动解码登录墙数据非公开放弃抓取寻找API或授权最后再分享一个小技巧爬虫技能不是越大越好而是要能进能退。你给AI加的能力越多它跑偏的概率也越大。我在“小龙虾”上线第二周的时候发现它会自作主张去抓一些明显不该碰的节点后来加上“合规规则引擎”才把问题控制住。现在回头看真正的“数据高手”不是能绕过多少限制而是知道该在哪儿停下。