行业资讯
📅 2026/7/10 14:11:58
爬虫不用再扒页面!GPT自动生成CSS选择器,30秒解析任意网站(附LangChain实战)
免责声明本文仅用于自动化测试、数据治理及合法信息采集场景的技术研究。请严格遵守目标网站服务条款、robots.txt协议及《数据安全法》相关规定禁止对未授权目标实施数据采集或规避安全防护措施。一、 为什么“写选择器”是爬虫最痛的点做过网页采集的人都懂那种绝望需求方丢来一个链接说“把这个列表页的商品名、价格、销量抓下来”你打开DevTools一看——class名是_2xK9mQ_price这种哈希混淆DOM嵌套七八层换个页面结构全废同一站点不同频道用了三套不同的模板好不容易写完选择器两周后前端改版又得重来传统爬虫80%的时间不是在“爬”而是在“定位”。requests和BeautifulSoup再快也得有人先把那个脆弱的CSS选择器写出来。大模型时代这件事的逻辑彻底变了你不需要告诉机器“怎么选”只需要告诉它“选什么”。把HTML片段和自然语言描述扔给GPT让它生成选择器再用代码验证执行。这不是玩具demo而是已经在多个数据治理项目中落地的工程方案。本文将基于LangChain给出完整的可运行代码并重点讲清楚那些教程里不会提的生产级容错与校验机制。二、 核心思路LLM不是执行器是翻译官先纠正一个常见误区不要让GPT直接返回抓取结果。HTML动辄几十万token塞进上下文窗口既贵又不准。正确的分工是通过失败原始HTMLDOM精简/分块GPT: 自然语言→CSS选择器本地引擎执行选择器结果校验结构化数据错误反馈回GPT重试GPT只负责“翻译”人类意图到机器语法真正的解析和执行永远在本地完成。这样既控制了token消耗又保证了结果的确定性和可验证性。三、 HTML预处理决定成败的第一步直接把完整HTML丢给GPT是最常见的翻车原因。一个电商列表页的原始HTML可能有200KB远超有效信息密度。你必须先做智能瘦身。3.1 DOM清洗与压缩frombs4importBeautifulSoup,Commentimportredefslim_html(raw_html:str,max_tokens:int6000)-str: 将原始HTML压缩到LLM可处理的尺寸 保留语义结构丢弃视觉噪声 soupBeautifulSoup(raw_html,html.parser)# 1. 移除无关标签fortaginsoup.find_all([script,style,svg,noscript,iframe]):tag.decompose()forcommentinsoup.find_all(stringlambdat:isinstance(t,Comment)):comment.extract()# 2. 移除纯装饰性属性保留class/id/data-*KEEP_ATTRS{class,id,href,src,data-testid,itemprop}fortaginsoup.find_all(True):tag.attrs{k:vfork,vintag.attrs.items()ifkinKEEP_ATTRS}# 3. 折叠重复列表项关键优化# 列表页通常有几十个结构相同的item只保留前3个作为样本_collapse_repeated_items(soup,sample_count3)resultstr(soup)# 4. 如果仍然超长按DOM深度截断iflen(result)max_tokens*4:# 粗估1 token ≈ 4 charsresult_truncate_by_depth(soup,max_charsmax_tokens*4)returnresultdef_collapse_repeated_items(soup,sample_count3):识别并折叠同构重复节点# 简单启发式同一父节点下class相同的子元素超过sample_count个forparentinsoup.find_all(True):childrenlist(parent.children)class_groups{}forchildinchildren:ifhasattr(child,get)andchild.get(class):keytuple(child[class])class_groups.setdefault(key,[]).append(child)forcls,nodesinclass_groups.items():iflen(nodes)sample_count:# 保留前N个其余替换为占位注释fornodeinnodes[sample_count:]:node.replace_with(f!-- ... ({len(nodes)-sample_count}more items with class{ .join(cls)}) --)这一步的价值远超你的想象。实测某新闻列表页原始HTML 180KB压缩后仅剩4.2KBtoken消耗降低97%而GPT生成的选择器准确率反而从72%提升到91%——因为噪声少了模型的注意力更集中了。四、 LangChain调用带自校验的选择器生成链以下是完整的生产级实现不是玩具代码fromlangchain_openaiimportChatOpenAIfromlangchain_core.promptsimportChatPromptTemplatefromlangchain_core.output_parsersimportJsonOutputParserfrombs4importBeautifulSoupimportjson SELECTOR_PROMPTChatPromptTemplate.from_messages([(system,你是一个CSS选择器生成专家。根据用户的自然语言描述和提供的HTML片段生成精确的CSS选择器。 规则 1. 优先使用语义化选择器data-testid id class 标签层级 2. 避免依赖哈希类名如_2xK9mQ除非没有其他选项 3. 每个字段返回一个选择器和一个备用选择器 4. 必须以JSON格式返回schema如下 {{fields: [{{name: 字段名, selector: 主选择器, fallback: 备用选择器, confidence: 0.0-1.0}}]}}),(human,目标字段{field_descriptions}\n\nHTML片段\nhtml\n{slim_html}\n)])classSelectorGenerator:MAX_RETRIES3def__init__(self,modelgpt-4o-mini,temperature0.1):self.llmChatOpenAI(modelmodel,temperaturetemperature)self.parserJsonOutputParser()self.chainSELECTOR_PROMPT|self.llm|self.parserdefgenerate(self,html:str,fields:list[str])-dict: 生成选择器并自动校验 fields: [商品名称, 当前价格, 月销量] slimslim_html(html)forattemptinrange(self.MAX_RETRIES):try:resultself.chain.invoke({field_descriptions:\n.join(f-{f}forfinfields),slim_html:slim})# ✅ 本地校验选择器是否真的能选中内容validatedself._validate_selectors(slim,result,fields)ifvalidated[all_passed]:print(f[✓] 第{attempt1}次尝试全部字段校验通过)returnvalidated# 校验失败时把错误信息反馈给LLM重试error_feedbackvalidated[error_summary]print(f[↻] 第{attempt1}次校验失败:{error_feedback})exceptExceptionase:print(f[✗] 第{attempt1}次调用异常:{e})raiseValueError(f经过{self.MAX_RETRIES}次尝试仍无法生成有效选择器)def_validate_selectors(self,html:str,llm_result:dict,expected_fields:list)-dict: 核心校验逻辑用真实DOM验证LLM输出的选择器 这是整个方案可靠性的基石 soupBeautifulSoup(html,html.parser)results[]errors[]forfieldinllm_result.get(fields,[]):namefield[name]selectorfield[selector]fallbackfield.get(fallback,)# 尝试主选择器matchessoup.select(selector)used_selectorselector# 主选择器无效则尝试备用ifnotmatchesandfallback:matchessoup.select(fallback)used_selectorfallback passedlen(matches)0sample_textmatches[0].get_text(stripTrue)[:100]ifmatcheselseresults.append({name:name,selector:used_selector,matched_count:len(matches),sample:sample_text,passed:passed})ifnotpassed:errors.append(f{name}: 选择器{selector}和备用{fallback}均未匹配到任何元素)eliflen(matches)1andnamein(列表,items):errors.append(f{name}: 期望多个列表项但只匹配到1个选择器可能过于具体)return{fields:results,all_passed:len(errors)0,error_summary:; .join(errors)iferrorselse}调用示例generatorSelectorGenerator()selectorsgenerator.generate(htmlraw_page_html,fields[文章标题,发布时间,作者昵称,正文内容])# 输出示例# {fields: [# {name: 文章标题, selector: h1.article-title, matched_count: 1, passed: True},# {name: 发布时间, selector: time[data-fieldpubdate], matched_count: 1, passed: True},# ...# ], all_passed: True}关键设计点temperature0.1。选择器生成是确定性任务不需要创造性。低温度大幅减少幻觉和格式错误。只在首次全部失败需要“换个思路”时才临时调高到0.4。五、 生产环境的四个保命机制Demo跑通和生产可用之间隔着以下四道坎5.1 选择器缓存与版本管理同一个站点的相同页面模板不要每次都调GPT。建立选择器缓存层# 缓存key hash(站点域名 页面URL模式 字段列表)# 缓存value {selectors, html_fingerprint, created_at, hit_count}# 当HTML指纹变化时自动失效触发重新生成# Redis TTL建议7天热门站点可延长实测某资讯站30个频道页缓存命中率87%日均GPT调用从1200次降到160次月省API费用约$45。5.2 多模型降级链GPT-4o-mini偶尔会抽风。配置降级链保障可用性gpt-4o-mini → qwen-plus → glm-4-flash → 本地Qwen2.5-Coder-7B国内模型对中文网页的理解有时甚至优于GPT且成本更低。不要迷信单一模型用校验结果说话。5.3 人工审核队列对于高价值采集任务自动生成的选择器不应直接上线。推入审核队列由人工确认样本数据正确后再激活。这一步看似低效实则避免了“错误选择器静默产出脏数据”的灾难性后果。5.4 监控告警指标指标健康阈值告警动作选择器生成成功率≥ 90%80%触发P1告警平均重试次数≤ 1.52.5检查Prompt或HTML预处理缓存命中率≥ 70%50%检查缓存失效策略校验失败Top字段—每周复盘补充Few-shot示例六、 适用边界与诚实评估这套方案不是万能的。以下是经过实战验证的能力边界场景效果说明标准列表页/详情页✅ 优秀最佳适用场景表格/结构化数据✅ 良好table/tr/td语义清晰SPA动态渲染页面⚠️ 需配合PlaywrightGPT只能处理已渲染的HTMLCanvas/SVG图表数据❌ 不适用无DOM结构可供选择高度混淆的反爬页面❌ 不适用先解决反爬再谈解析跨页面关联数据⚠️ 需额外编排单次调用只处理单页诚实的成本评估对于一个包含50种页面模板的中型站点首次全量生成选择器的GPT API成本约$3-8取决于HTML大小和模型选择。后续维护成本趋近于零。与传统人工编写相比首次投入相当但每次站点改版的修复成本从“人天级”降到“分钟级”。七、 写在最后这篇文章想传递的核心认知是LLM在爬虫领域的最大价值不是替代人写代码而是消除“人类理解页面结构”这个瓶颈。过去每个新页面的解析都需要一个工程师花30分钟盯着DevTools找规律。现在这30分钟被压缩成一次API调用加几秒本地校验。工程师的时间被释放到更有价值的地方设计数据模型、构建质量监控、优化采集架构。技术会变模型会迭代但“让机器理解人类意图让人类专注决策”这个方向不会变。CSS选择器生成只是起点同样的范式可以迁移到API参数推断、数据清洗规则生成、异常检测阈值设定等无数场景。别再手动扒页面了。把时间花在值得花的地方。参考资料LangChain官方文档Structured Output Self-CorrectionBeautifulSoup CSS选择器参考《LLM-Assisted Web Scraping: An Empirical Study》(2025)OpenAI / 通义千问 API定价文档本文代码已在Python 3.11 LangChain 0.3.x环境验证通过。评论区欢迎分享各站点的HTML预处理技巧和选择器校验经验请勿发布涉及未授权采集的具体目标或绕过防护的代码。