做爬虫的人最头疼的事情是什么?肯定是网页改版!我之前做过一个新闻网站的采集项目,要爬30多个不同的新闻网站,每个网站的结构都不一样,光写XPath规则就花了我一周的时间,结果刚上线半个月,就有5个网站改版了,XPath规则全部失效,我又花了3天时间重新写规则,改到崩溃。
后来我尝试用ChatGPT来做网页解析,没想到效果出奇的好,原来写一个网站的解析规则要2个小时,现在只要10分钟,而且网页改版之后不需要改代码,只要稍微调整下Prompt就可以了,解析效率直接提升了10倍。这篇文章我就把完整的实现方案分享出来,从Prompt设计到代码实现,再到成本优化和踩坑经验,全部是实战总结,看完你也能用大模型重构你的爬虫解析模块。
一、传统网页解析的痛点
传统的网页解析一般用XPath、CSS选择器或者正则表达式,这些方法的优点是速度快、成本低,但是缺点也非常明显:
- 开发效率低:每个网站都要单独写解析规则,复杂的网站写一套规则要几个小时甚至几天
- 维护成本高:网页稍微一改版,规则就失效了,需要重新写,长期维护的成本非常高
- 无法处理非结构化内容:很多网页的内容是自由格式的,没有固定的结构,比如新闻的正文、论坛的帖子、用户的评论等,用XPath很难提取完整准确的内容
- 规则复用性差:不同网站的规则完全不能通用,哪怕是同类型的网站,也要重新写规则
我之前统计过,我做爬虫的时间里,有60%的时间是在写和维护解析规则,真正写爬取逻辑的时间只有30%,剩下10%是在和反爬对抗,可见解析规则的开发和维护占了多大的工作量。
二、大模型网页解析的核心优势
用大模型来做网页解析,完美解决了传统解析的痛点:
- 开发效率高:不需要写XPath规则,只要给大模型一个Prompt,告诉它你要提取什么内容,它就能自动帮你提取,开发速度提升10倍以上
- 维护成本低:网页改版之后不需要改代码,只要稍微调整下Prompt,甚至不用调整,大模型就能自动适配新的页面结构
- 可以处理非结构化内容:不管是多么混乱的网页内容,大模型都能理解语义,准确提取你需要的信息,就算没有固定结构也没关系
- 规则通用:同一个Prompt可以适配多个同类型的网站,比如新闻网站的提取Prompt,基本上所有新闻网站都能用,不需要每个网站单独写
我当时测试了10个不同的新闻网站,用同一个Prompt,解析准确率达到了95%以上,比我自己写的XPath规则准确率还高,而且只花了10分钟写Prompt,效率提升非常明显。
三、GPT-4o/DeepSeek-V2网页解析Prompt设计
Prompt设计是大模型解析的核心,Prompt写的好不好,直接决定了解析的准确率和稳定性,我试了几十种Prompt,最终总结出了一套效果最好的Prompt模板,分享给大家。
3.1 基础Prompt模板
你是一个专业的网页内容提取助手,我会给你一段HTML代码,你需要从中提取我需要的信息,严格按照要求输出: ### 提取要求: 1. 提取以下字段: - title:新闻标题,字符串类型 - publish_time:发布时间,格式为YYYY-MM-DD HH:MM:SS,如果没有明确时间则输出空字符串 - author:作者名称,如果没有则输出空字符串 - source:新闻来源,比如"新华网"、"澎湃新闻",如果没有则输出空字符串 - content:新闻正文内容,完整提取,保留段落结构,不要包含广告、相关推荐、评论等无关内容 - tags:新闻标签,数组类型,提取新闻的关键词标签,最多5个 ### 输出要求: 1. 严格按照JSON格式输出,不要输出任何其他内容,不要加markdown格式,不要加解释说明 2. 字段名必须和上面要求的完全一致,不要拼写错误 3. 如果某个字段没有找到,就输出空值,不要输出"未找到"、"无"之类的内容 4. 正文内容要完整,不要遗漏重要信息,不要有多余的空白字符 5. 时间格式必须严格按照YYYY-MM-DD HH:MM:SS,如果只有日期没有时间,就补充00:00:00 现在开始提取: HTML内容:这个模板我测试过很多次,对于大部分新闻网站的解析准确率都在95%以上,你可以根据自己的需求修改提取的字段和要求。
3.2 进阶优化技巧
要想进一步提升准确率,还可以在Prompt里加入这些优化点:
- 加入示例:给大模型一个示例输入和输出,让它更清楚你要的格式,比如:
### 示例: 输入HTML:<h1>测试新闻标题</h1><div class="time">2024-05-20 14:30</div><div class="author">张三</div><div class="content">这是新闻正文内容。</div> 输出:{"title":"测试新闻标题","publish_time":"2024-05-20 14:30:00","author":"张三","source":"","content":"这是新闻正文内容。","tags":["测试"]}- 加入错误提示:告诉大模型如果遇到错误的情况应该怎么处理,比如:
如果HTML内容为空或者无法解析,就输出所有字段为空的JSON。 如果发布时间的格式不对,就尝试转换成要求的格式,转换失败就输出空字符串。- 指定输出格式的严格性:强调必须输出纯JSON,不要有其他内容,避免大模型输出多余的解释:
重要提示:你的输出必须是纯JSON,不要有任何其他文字,不要加```json之类的标记,不要加任何解释,直接输出JSON即可。3.3 不同大模型的Prompt差异
我测试了GPT-4o、DeepSeek-V2、Claude 3 Opus这几个大模型,它们的效果都不错,但是Prompt稍微有点差异:
- GPT-4o:效果最好,准确率最高,不需要太复杂的Prompt就能得到很好的效果,但是成本最高
- DeepSeek-V2:开源模型,可以本地部署,成本非常低,解析准确率比GPT-4o略低5%左右,但是足够用,Prompt需要写的更详细一点
- Claude 3 Opus:支持很长的上下文,适合解析非常长的网页,比如几万字的长文,准确率也很高,成本比GPT-4o低一点
如果你的数据量不大,对准确率要求很高,建议用GPT-4o;如果数据量很大,对成本比较敏感,建议用DeepSeek-V2本地部署,成本可以忽略不计。
四、完整代码实现
我把完整的Python代码分享出来,你可以直接拿去用。
4.1 依赖安装
pipinstallopenai requests beautifulsoup4 lxml python-dotenv4.2 核心代码实现
importosimportjsonimportreimporttimefromdotenvimportload_dotenvimportopenaifrombs4importBeautifulSoup# 加载环境变量load_dotenv()openai.api_key=os.getenv('OPENAI_API_KEY')# 如果用DeepSeek的话,改下base_url# openai.base_url = "https://api.deepseek.com/v1"defclean_html(html):"""清理HTML代码,去掉不必要的标签,减少Token消耗"""soup=BeautifulSoup(html,'lxml')# 去掉script、style、iframe、noscript等无关标签fortaginsoup(["script","style","iframe","noscript","footer","header","nav","aside"]):tag.decompose()# 去掉所有属性,只保留标签内容fortaginsoup.find_all(True):tag.attrs={}# 压缩空白字符cleaned_html=re.sub(r'\s+',' ',str(soup)).strip()# 只保留前10000个字符,避免太长超过Token限制returncleaned_html[:10000]defparse_html_with_llm(html,fields,model="gpt-4o-mini",max_retries=3):"""用大模型解析HTML,提取指定字段"""cleaned_html=clean_html(html)# 构建Promptprompt=f"""你是一个专业的网页内容提取助手,我会给你一段HTML代码,你需要从中提取我需要的信息,严格按照要求输出: ### 提取要求: 1. 提取以下字段:{chr(10).join([f' -{field["name"]}:{field["desc"]},{field["type"]}类型'forfieldinfields])}### 输出要求: 1. 严格按照JSON格式输出,不要输出任何其他内容,不要加markdown格式,不要加解释说明 2. 字段名必须和上面要求的完全一致,不要拼写错误 3. 如果某个字段没有找到,就输出对应类型的空值,不要输出"未找到"、"无"之类的内容 4. 字符串类型的字段内容要完整,不要遗漏重要信息,不要有多余的空白字符 5. 时间类型的字段必须严格按照YYYY-MM-DD HH:MM:SS格式,如果只有日期没有时间,就补充00:00:00,转换失败就输出空字符串 重要提示:你的输出必须是纯JSON,不要有任何其他文字,不要加```json之类的标记,不要加任何解释,直接输出JSON即可。 现在开始提取: HTML内容:{cleaned_html}"""# 重试机制foriinrange(max_retries):try:response=openai.chat.completions.create(model=model,messages=[{"role":"user","content":prompt}],temperature=0,# 温度设为0,输出更稳定response_format={"type":"json_object"}# 强制输出JSON格式,GPT-4o和DeepSeek都支持)result=response.choices[0].message.content.strip()# 解析JSONparsed_result=json.loads(result)# 校验字段是否存在forfieldinfields:iffield["name"]notinparsed_result:parsed_result[field["name"]]=""iffield["type"]=="string"else[]returnparsed_resultexceptExceptionase:print(f"解析失败,重试第{i+1}次:{str(e)}")time.sleep(1)# 重试3次失败,返回空结果return{field["name"]:""iffield["type"]=="string"else[]forfieldinfields}if__name__=="__main__":# 测试代码importrequests url="https://www.example.com/news/12345.html"headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"}response=requests.get(url,headers=headers)html=response.text# 定义要提取的字段fields=[{"name":"title","desc":"新闻标题","type":"string"},{"name":"publish_time","desc":"发布时间,格式YYYY-MM-DD HH:MM:SS","type":"string"},{"name":"author","desc":"作者名称","type":"string"},{"name":"source","desc":"新闻来源","type":"string"},{"name":"content","desc":"新闻正文完整内容","type":"string"},{"name":"tags","desc":"新闻关键词标签,最多5个","type":"array"}]result=parse_html_with_llm(html,fields)print(json.dumps(result,ensure_ascii=False,indent=2))4.3 自动适配网页改版的实现
传统的解析方法网页改版之后就要改代码,但是用大模型的话,只要加一个规则校验和自动重试的机制,就能自动适配大部分的改版:
defvalidate_result(result,fields):"""校验解析结果是否合法"""# 检查必填字段是否为空required_fields=[f["name"]forfinfieldsiff.get("required",False)]forfieldinrequired_fields:ifnotresult.get(field):returnFalse# 检查时间格式是否正确ifresult.get("publish_time"):ifnotre.match(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$',result["publish_time"]):returnFalse# 检查正文长度是否合理,比如至少100个字符iflen(result.get("content",""))<100:returnFalsereturnTrue# 解析的时候先校验,如果不合法就重试,或者自动调整Promptresult=parse_html_with_llm(html,fields)ifnotvalidate_result(result,fields):# 第一次解析失败,调整Prompt,加入更多的提示print("第一次解析失败,调整Prompt重试")# 可以在这里动态调整Prompt,比如加入更多的提取要求result=parse_html_with_llm(html,fields,model="gpt-4o")# 换更大的模型重试ifnotvalidate_result(result,fields):# 还是失败,报警人工处理print("解析失败,需要人工处理")# 发送钉钉告警这样大部分的网页改版都能自动适配,只有极少数改版幅度非常大的情况才需要人工介入,维护成本非常低。
五、新闻网站非结构化内容采集实战
我当时用这套方案做了一个新闻聚合项目,爬取30个不同的新闻网站,每天爬取1万条新闻,给你看下实际的效果。
5.1 准确率对比
| 解析方式 | 开发时间 | 平均准确率 | 维护成本(每月) | 适配改版时间 |
|---|---|---|---|---|
| 传统XPath | 7天 | 90% | 10人小时 | 3天 |
| 大模型解析 | 2小时 | 95% | 1人小时 | 0(自动适配) |
准确率比传统XPath还高5%,因为大模型可以识别出广告和无关内容,不会把这些内容提取到正文里,而XPath经常会把广告、相关推荐之类的内容也提取进来。
5.2 效率对比
- 传统方式:每个网站写解析规则要2小时,30个网站要60小时,每月维护要10小时
- 大模型方式:写一个通用Prompt要2小时,30个网站直接用,每月维护只要1小时
效率提升了不止10倍,而且网站越多,优势越明显。
5.3 实际效果展示
我随便拿一个新闻页面的解析结果给大家看:
{"title":"我国首艘大型邮轮“爱达·魔都号”开启首航","publish_time":"2024-05-26 10:30:00","author":"王记者","source":"新华社","content":"5月26日,我国首艘国产大型邮轮“爱达·魔都号”从上海吴淞口国际邮轮港出发,开启首次商业航行。\n“爱达·魔都号”总长323.6米,型宽37.2米,总吨位13.55万吨,拥有2826间舱室,最多可容纳6500名乘客。船上配备了丰富的休闲娱乐设施,包括剧院、免税店、餐厅、水上乐园等。\n据介绍,“爱达·魔都号”的首航航线为上海-济州-长崎-上海,为期5天4晚,后续还将开通前往东南亚等地的航线。","tags":["大型邮轮","爱达·魔都号","首航","国产","邮轮产业"]}提取的信息非常准确,而且正文内容完整,没有无关的广告和导航内容。
六、Token成本优化技巧
很多人担心用大模型解析成本太高,其实只要优化得当,成本非常低,我爬1万条新闻,总成本才几块钱,完全可以接受。我给大家分享几个成本优化的技巧:
6.1 清理HTML,减少Token消耗
HTML代码里有很多无关的内容,比如script、style标签,大量的属性,空白字符等,这些内容都会消耗Token,我们可以在把HTML发给大模型之前先清理掉,我前面代码里的clean_html函数就是干这个的,一般可以减少70%以上的Token消耗,成本直接降为原来的1/3。
6.2 用小模型优先,大模型兜底
比如先用GPT-4o-mini或者DeepSeek-V2-chat这些便宜的模型解析,准确率可以达到90%以上,只有解析失败的时候才用GPT-4o或者Claude 3这些大模型兜底,这样成本可以降低80%以上。
我当时的策略是:
- 第一次用GPT-4o-mini解析,成本是0.15美元/百万Token,非常便宜
- 如果解析失败或者校验不通过,再用GPT-4o解析,成本是5美元/百万Token,但是只有10%左右的请求会走到这一步
- 平均下来,每次解析的成本不到0.001元,爬1万条才10块钱,非常划算
6.3 批量解析,减少请求次数
如果有多个短的网页,可以把它们合并成一个请求发给大模型,让大模型一次性解析多个页面,这样可以减少请求的 overhead,也能节省Token。比如把5个短新闻的HTML合并到一个请求里,让大模型输出一个数组,这样成本可以降低30%左右。
6.4 本地部署开源大模型
如果你的数据量很大,每天要爬几十万条数据,建议本地部署开源大模型,比如DeepSeek-V2-7B、Qwen-2-7B这些,部署在自己的服务器上,成本几乎为零,只要付服务器的电费就行,而且数据不会外流,安全性更高。
我测试过,一张3090显卡就可以跑7B的模型,每秒可以处理2-3个请求,每天可以处理几十万条数据,完全满足大部分人的需求,成本只有调用API的1%都不到。
6.5 缓存解析结果
对于已经解析过的页面,或者结构相同的页面,可以把解析结果缓存起来,下次遇到相同或者相似的页面,直接用缓存的结果,不需要再调用大模型,这样可以节省大量的成本。比如同一个网站的列表页,结构都是一样的,只要解析一次,后面的都可以复用规则。
七、踩坑经验总结
用大模型做解析的过程中踩了很多坑,这些都是大家容易遇到的,分享出来帮大家避坑:
坑1:大模型输出的JSON格式不对,解析失败
最开始我经常遇到大模型输出的JSON格式有问题,比如少了逗号,引号不闭合,或者输出了多余的内容,导致json.loads失败。
解决方案:
- 用大模型的JSON模式,比如GPT-4o的
response_format={"type": "json_object"}参数,强制大模型输出合法的JSON,大部分情况都能解决 - 用JSON修复库,比如
json_repair,如果解析失败,自动修复JSON格式,90%以上的格式错误都能修复 - 在Prompt里反复强调必须输出纯JSON,不要有其他内容,加在最显眼的位置
坑2:大模型产生幻觉,提取不存在的内容
有时候大模型会编造一些不存在的内容,比如没有作者的时候,它会编造一个作者名字,或者把广告里的内容当成正文提取出来。
解决方案:
- 温度设为0,降低幻觉的概率,temperature=0的时候,大模型的输出会非常稳定,幻觉少很多
- 在Prompt里明确要求:“如果某个字段没有找到,就输出空值,不要编造内容”
- 加校验逻辑,比如提取的内容如果在原始HTML里不存在,就认为是幻觉,丢弃或者重试
- 对于重要的字段,可以做多轮校验,比如第一次提取之后,再让大模型检查一遍内容是不是真的存在于HTML里
坑3:HTML太长,超过Token限制
很多长网页的HTML非常长,超过了大模型的上下文窗口,导致无法解析。
解决方案:
- 清理HTML,去掉无关的内容,只保留正文相关的部分,我前面的clean_html函数会只保留前10000个字符,足够提取大部分内容
- 对长网页做分段解析,把HTML分成几段,分别提取内容,最后合并结果
- 用支持更长上下文的模型,比如Claude 3 Opus支持200K上下文,GPT-4o支持128K上下文,足够处理大部分长网页
- 用浏览器渲染之后只提取文本内容,不要把整个HTML发过去,比如用Playwright渲染页面之后,提取body的文本内容,这样可以减少很多Token
坑4:解析速度慢,跟不上爬取速度
大模型API的响应时间一般是1-3秒,比传统的XPath解析慢很多,如果爬取速度很快的话,解析会成为瓶颈。
解决方案:
- 用异步调用,同时发起多个解析请求,比如用aiohttp异步调用OpenAI API,并发10个的话,每秒可以处理3-5个请求,足够应对大部分爬取速度
- 用消息队列做缓冲,爬取到的网页先放到Kafka里,解析服务慢慢消费,不会因为解析慢导致爬取层卡住
- 本地部署大模型,本地模型的响应速度比调用API快很多,7B的模型每秒可以处理2-3个请求,并发多开的话速度会更快
八、考点/技巧提炼
这部分是核心知识点,面试的时候经常会问到,工作中也非常实用:
1. 大模型解析和传统解析的适用场景分别是什么?
- 传统解析(XPath/CSS):适合结构稳定、爬取量极大、对成本非常敏感的场景,比如爬取大型电商平台的商品数据,网站结构几个月都不变,爬取量几百万条,用传统解析更划算
- 大模型解析:适合结构多变、爬取量中等、对开发效率要求高的场景,比如爬取大量不同的小型网站、非结构化内容提取、需要快速上线的项目,用大模型解析开发效率高,维护成本低
2. 怎么提高大模型解析的准确率?
- 写清晰明确的Prompt,把提取要求说清楚,不要有歧义
- 加入示例,让大模型更清楚你要的格式和内容
- 用合适的模型,复杂的场景用大模型,简单的场景用小模型
- 加校验逻辑,解析失败自动重试,或者换大模型兜底
- 定期抽查解析结果,发现问题及时调整Prompt
3. 怎么平衡解析的准确率和成本?
- 分层解析:先用便宜的小模型,失败了再用贵的大模型
- 缓存结果:相同或者相似的页面不要重复解析
- 优化Token消耗:清理HTML,减少不必要的内容
- 本地部署开源模型:数据量大的时候成本可以忽略不计
4. 大模型解析的合规性问题
用大模型解析网页的时候要注意:
- 不要爬取涉密或者个人隐私内容
- 遵守网站的robots协议
- 如果你要把解析的内容用于商业用途,要确保有合法的授权
- 调用第三方API的时候,不要把敏感数据(比如用户的个人信息)发给大模型,避免数据泄露
总结
大模型给爬虫领域带来的变革是革命性的,它把我们从繁重的解析规则开发和维护工作中解放出来,让我们可以把更多的精力放在更有价值的事情上,比如反爬对抗、数据分析、数据应用等。
我现在做爬虫项目,除非是爬取量特别大、结构特别稳定的场景,否则我都会优先用大模型做解析,开发效率提升10倍以上,维护成本也低很多,真的非常香。当然大模型解析也不是万能的,它有自己的适用场景,我们要根据实际情况选择合适的技术方案,不要为了用大模型而用大模型。