news 2026/9/6 8:51:27

ScrapeGraphAI实战:用LLM语义抽取网页内容,告别XPath和CSS选择器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ScrapeGraphAI实战:用LLM语义抽取网页内容,告别XPath和CSS选择器

第一次把 ScrapeGraphAI 这个项目装进环境时,我其实没抱太大期望。这几年号称“AI 爬虫”的工具太多了,多数就是把模型接在一个 requests 循环后面,换个名字而已。但 Scrapegraph-ai 不太一样:它把整个抓取流程做成了图结构,不同节点各司其职,真正的抽取动作交给大语言模型去完成,而不是靠写死的 XPath 或 CSS 选择器。换句话说,以前写爬虫是在跟 HTML 标签斗智斗勇,现在只要用一句人话描述“我要什么”,模型会自己到页面里找。这篇文章我会从使用者的角度,完整拆解这个项目能做什么、怎么配置、有哪些坑,并附上可以直接照抄的代码。适合人群很明确:被页面结构和改版折磨的爬虫开发者,想做 LLM 工程化落地但不想从零搭框架的工程师,以及需要定期把网页内容整理成结构化数据的分析师。

1. ScrapeGraphAI 是什么:用“对话”代替“选择器”的爬虫新思路

1.1 传统爬虫让我最头疼的三件事

做爬虫时间久了,你会发现真正烦人的不是请求网站那一步,而是解析结构这件事。第一,很多页面的 HTML 标签没有语义,divdiv,class 名还经常是压缩过的随机字符串,你想定位一个商品价格,得先在开发者工具里点半天。第二,网站前端一改版,哪怕只是改了某个 class 名字,整套选择器就全部失效,维护成本比写爬虫本身还高。第三,不同类型页面的解析逻辑差异很大,今天抓的是博客列表,明天抓的是商品详情,每个都要单独写一套解析函数,代码量就这么膨胀起来。

这些问题不是靠加断言、加容错能根治的,因为根子在于“用固定规则去匹配不固定的页面”。ScrapeGraphAI 换了一个思路:解析规则不再由人写死,而是由大语言模型根据自然语言需求临时生成。它看到的是从页面提取出的文本块和上下文,而不是一堆标签,所以页面结构怎么变,只要信息的语义没变,它基本都能找出来。

我第一次用它的场景是抓一个项目展示页。传统做法我得打开控制台找到项目列表的父节点,再写一个for循环区提取标题和描述。用 ScrapeGraphAI 只需要写一句:“提取页面里所有项目的名称、简介和链接,返回 JSON 列表。” 在页面正常的情况下,它给出来的结果比我手写解析还干净。那一刻我意识到,这已经不是“自动生成选择器”那种旧概念,而是真正把“抽取”这件事交给了语言模型去理解。

1.2 从语义层面理解页面:核心是 LLM 而不是规则

ScrapeGraphAI 的核心理念可以概括成一句话:让模型读网页,而不是让程序翻网页。项目里定义的抓取流程不再是“发请求 -> 拿 HTML -> 用正则或 XPath 提取字段”,而是“获取页面内容 -> 切分并筛选相关片段 -> 把片段和用户需求一起交给大语言模型 -> 得到结构化结果”。

听起来简单,这背后其实有两个关键设计。第一,它不会把整页几千行 HTML 直接塞给模型,那样既浪费 token,也容易让模型被导航栏、页脚这些噪声干扰。它会在中间加一层嵌入模型做相关性筛选,只把和需求最相关的文本片段挑出来。第二,模型的输出不是随意的散文,而是通过提示词约束成 JSON 结构,方便后续直接入库或生成文件。这两点决定了它在真实业务里能不能落地:既控制成本,也保证输出格式可控。

我拿一个具体例子说明。假设要抓一个书籍列表页,希望拿到每本书的书名、作者、评分和价格。传统解析需要分别定位这四个字段的 DOM 节点,而 ScrapeGraphAI 只需要定义好目标的 JSON Schema 或者用自然语言说明字段含义,剩下的定位工作由模型完成。实测下来,即使页面里的评分是用 SVG 星星而不是文本表示的,它也能根据附近的信息推理出评分值。这种容错能力是任何固定规则都做不到的。

1.3 图结构不是噱头:它是整个框架的骨架

“GraphAI”这个名字里的“Graph”指的既不是图表也不是社交图谱,而是一个有向无环图。项目把一次抓取任务拆成多个节点,比如获取页面内容的节点、解析 HTML 的节点、调用模型生成答案的节点,节点之间有依赖关系,串起来就组成一条流水线。

为什么用图而不是简单的顺序函数?因为真实抓取场景是有分支的。同一个页面,可能需要先判断是不是动态渲染的;搜索类的任务可能需要先抓搜索结果页,再逐个进入详情页;有些站点需要先模拟点击“加载更多”按钮才能拿到全部数据。如果只用线性代码写,这些分支会让主流程变得很臃肿。图的优势在于可以自由组装节点,需要条件判断就加一个条件节点,需要循环抓取子页面就加一个迭代节点,整个结构像搭积木一样清楚。

我最初以为这只是架构上的洁癖,后来自己试着扩了一个带“失败重试”的自定义链路,才体会到图的威力。改一条链路只需要调整节点之间的连接,不用动别的模块。对于爬虫这种逻辑分散、特别容易因地制宜的场景,这种模块化设计比一个大而全的函数要实用得多。

2. 环境准备与零基础跑通第一个抓取任务

2.1 安装导入:pip 与几个容易忽略的依赖

安装 ScrapeGraphAI 很简单,pip install scrapegraphai一行就能完成。但这里有几个容易忽略的依赖问题。如果只抓静态页面,基础安装就够用;如果目标网站是前端渲染的,需要额外装 Playwright,并执行playwright install chromium把浏览器内核下载下来。这个步骤经常被漏掉,不少人装上库之后跑动态页面报错,就是卡在这里。另外,库对 Python 版本有要求,建议用 Python 3.10 或更高版本,依赖冲突会少很多。

我自己的环境习惯是用虚拟环境单独装,避免和项目里已有的 requests、beautifulsoup 等包产生版本冲突。装完可以用pip show scrapegraphai确认版本,同时看一下它依赖的httpxbeautifulsoup4等关键包是否被自动装好。社区里常见的问题之一是用 pyenv 或 conda 装了低版本 Python,结果 import 阶段就报语法错误,这种情况优先升级解释器版本,而不是去改源码。

2.2 最小示例:一句“人话”抓取列表页

装好之后,第一个 Demo 我建议直接用官方文档里的经典示例,抓一个静态项目展示页。代码如下:

from scrapegraphai.graphs import SmartScraperGraph config = { "llm": { "model": "openai/gpt-4o-mini", "temperature": 0.1, "api_key": "你的API_KEY", }, } graph = SmartScraperGraph( prompt="列出页面上所有的项目名称、描述和链接", source="https://perinim.github.io/projects/", config=config, ) result = graph.run() print(result)

运行之后,结果是一个 JSON 字典,里面通常是一个projects列表,每个项目包含namedescriptionlink三个字段。第一次跑通这个例子,你就掌握了 90% 的基础用法:SmartScraperGraph接收 prompt、source 和 config 三个参数,run()执行整个抓取流程并返回结构化数据。

几个参数值得细说。temperature我建议一律调低,最好在 0.1 左右,抽取任务需要的是稳定和准确,不需要创造性发散。model里的前缀openai/是必要的,它告诉框架走哪个后端,如果换成本地模型,这里就变成ollama/llama3.1这种写法。source既可以是 URL 字符串,也可以是已经抓取好的 HTML 内容,这一点在离线处理场景非常有用,后面我会单独讲。

2.3 输出格式转换:从 JSON 到 CSV、XML

run()返回的 JSON 虽然方便程序处理,但很多时候业务方要的是 Excel 表格或者 XML 接口。ScrapeGraphAI 在scrapegraphai.utils里预置了几个转换函数,用起来很直接:

from scrapegraphai.utils import convert_to_csv, convert_to_json, convert_to_xml # 假设 result 是上一步抓取得到的 JSON 字典 convert_to_csv(result, "output.csv") convert_to_json(result, "output.json") convert_to_xml(result, "output.xml")

这几个函数内部会自动处理嵌套结构。比如 JSON 里的列表字段,转换成 CSV 时会自动做列展开,不需要自己写摊平逻辑。我实际用下来的感受是:小规模数据直接调这几个工具完全够用,但如果数据量上到几千行,建议还是在内存里先做字段裁剪,再落盘,因为转换函数对全量 dict 的递归处理在超大对象上会比较慢。

3. 核心配置与关键参数:真正发挥效果的点

3.1 LLM 接入方式选择:云 API 和本地模型怎么权衡

ScrapeGraphAI 目前支持的后端非常多,OpenAI、Azure OpenAI、Google Gemini、Groq、Mistral、Anthropic、Hugging Face、Ollama 都能接。选哪个取决于三个因素:预算、数据隐私、任务量。

对于企业内部的低频抓取,云端模型理解能力强,配置也最简单,在 config 里填一个api_key就行。但调用是计费的,如果每天要抓几千个页面,成本会很快涨上去。对于这种情况,我更推荐本地模型方案。Ollama 在本地跑一个中等规模的模型,比如 llama3.1 8B 或者 qwen2.5 7B,虽然推理速度不如云 API,但胜在零调用费和部署简单。配置示例:

config = { "llm": { "model": "ollama/llama3.1", "temperature": 0.1, }, "embeddings": { "model": "ollama/nomic-embed-text", }, }

这里同样要关注嵌入模型。框架默认 OpenAI 系列的嵌入模型,如果改用 Ollama,最好也把embeddings配成本地模型,否则会出现后端的不匹配。我自己踩过的坑是,只改了llm忘了改embeddings,结果每次运行都在嵌入调用阶段报 401 或超时,排查了半天才发现是两个后端混着用了。

3.2 嵌入模型和 chunk 处理:决定结果质量的关键环节

很多人用 ScrapeGraphAI 只盯着大模型选型,忽略了嵌入模型,其实嵌入模型对最终结果的影响甚至更大。网页抓回来后,框架会做内容切分,把正文按块拆开,然后通过嵌入模型计算每个块和用户需求的相似度,只把相似度高的块传给大模型。如果嵌入模型选得不好,相关段落被过滤掉,后面模型再聪明也巧妇难为无米之炊。

针对不同的内容类型,嵌入模型的选择也有讲究。技术文档、新闻这类语义清晰的内容,通用嵌入模型就够用。但如果是垂直领域的专业术语,比如医疗或法律页面,通用嵌入模型的向量空间可能表达不准,这种情况可以换用领域微调过的嵌入模型,或者调高阈值参数让更多文本块进入候选集。框架里的关键参数是chunk_size,控制文本切块大小,默认值并不总是最优,遇到内容很长的页面,适当调大 chunk 反而能帮助模型看到完整上下文。

3.3 动态网页抓取配置:Playwright 与 headless 模式

现在很多网站是前端渲染,HTML 里根本没有真实数据,这时候必须让爬虫像真正的浏览器一样执行 JavaScript。ScrapeGraphAI 对这种情况的处理是借助 Playwright 驱动无头浏览器抓取页面,然后在浏览器渲染完的结果上再跑解析流程。开启方式是在 config 里加一段:

config = { "llm": {"model": "openai/gpt-4o-mini", "temperature": 0.1, "api_key": "..."}, "headless": True, "browser": "playwright", }

这里有两个注意点。第一,首次使用前要在终端执行playwright install chromium,不然框架能调起 Playwright,但浏览器内核没下载,会直接报错。第二,开启浏览器抓取后,一次请求的耗时从几百毫秒涨到几秒甚至十几秒,这是正常现象,不要急着调超时。批量抓动态页面时一定要算好耗时预算,别按静态页面的速度去预估总量。

我实际遇到过一个典型的动态页面问题:页面商品列表是点击“加载更多”才出现的,即使开启了 Playwright,默认也只抓了首页加载后的第一屏。这种情况需要在 prompt 里明确站点交互逻辑,或者自己扩展节点处理点击动作。简单项目可以在 prompt 里写“页面内容可能需要点击加载更多才能完整显示”,复杂的交互就只能用自定义节点了,这部分后面展开。

4. 进阶实操:批量抓取、自定义流水线与工程化改造

4.1 批量任务:多 URL 循环与限速策略

单个页面抓通之后,大家通常都会想跑批量任务。最直接的办法是把上面的图包在一个循环里,遍历 URL 列表。但这里我强烈建议从第一个脚本就开始考虑限速,原因不是怕目标网站封你,而是本地模型和云 API 都有并发限制,同时跑太多请求很快会触顶报错,反而影响整体效率。

一个比较稳妥的批量策略是信号量限流。抓取是 IO 密集型任务,用asyncio.Semaphore控制并发数,比如同时最多跑 5 个图实例。另外要设置合理的重试机制,捕获异常后等待几秒再重试。我通常会在批次之间加一个随机延迟,避免请求太规律。还有一个容易忽略的点:ScrapeGraphAI 的SmartScraperGraph实例不能安全地跨线程共享,批量场景务必每个任务单独创建图实例,跑完再释放。

批量任务跑出来之后,一定要对结果做一次完整性检查。LLM 抽取不像规则解析那么确定,偶尔会有字段缺失或格式跳变,脚本里加一段字段校验逻辑,把不合格的结果单独标记出来,重跑一遍只处理失败项,比整体重跑省时间得多。

4.2 自定义图节点:按业务场景搭流水线

当你需要处理“先搜索,再进详情页”这类多步任务时,框架自带的标准图就有点不够用了。好在它提供了比较自由的扩展机制,核心思路是继承AbstractGraph基类,重写图的构建方法,在里面组装官方预设好的节点。下面是一个简化思路,实际开发时可以参考这个结构:

from scrapegraphai.graphs import AbstractGraph from scrapegraphai.nodes import FetchNode, ParseNode, GenerateAnswerNode class MyPipelineGraph(AbstractGraph): def _create_graph(self): fetch_node = FetchNode(source=self.source, config=self.config) parse_node = ParseNode(config=self.config) generate_node = GenerateAnswerNode(output_type="json", config=self.config) self.graph = (fetch_node >> parse_node >> generate_node) return self.graph

这段代码里的>>表示节点之间的顺序连接,是库内部封装好的操作符。如果业务需要条件分支,可以在链路里插入ConditionalNode,根据前一步的输出决定走哪条子链路。这个机制的灵活性最好体现在“重试类”任务上:第一次用低质量模型快速试跑,如果结果字段为空,就通过条件节点切到更强的模型再次抽取。

自定义节点的学习曲线比直接调库稍微陡一点,但收益是实实在在的。之前我接了一个需求,要抓一个带分页的资讯列表,每页结构一样,但 URL 不连续。我用一个迭代节点配合分页参数生成子任务,把“遍历分页”也变成了图的一部分,主流程代码反而比原来少了一半。

4.3 工程化部署:容器化与外部调用

开发环境的脚本跑通后,多数人会希望把抓取能力暴露成内部服务,给其他团队调用。ScrapeGraphAI 官方提供了 Docker 镜像,把项目打包成了一个带 Web API 的服务。部署方式大致是:拉取官方镜像,映射端口,然后通过 API 提交 URL 和提示词,服务返回结构化结果。

这种部署模式有几个实际好处。第一,其他团队不需要安装 Python 环境和模型密钥,直接发 HTTP 请求就能完成抓取。第二,可以把模型密钥集中在服务端,调用方不需要碰任何密钥。第三,服务端可以做统一限流和日志,方便排查问题。我自己比较推荐把抓取服务设计成无状态任务,每次请求独立成图实例,这样水平扩容时只需要加容器副本,不需要考虑集群状态同步。

不过要提醒一点,容器化之后的模型配置需要提前固化。如果直接在环境变量里写死模型名和密钥,后续想切换模型需要重新构建,比较麻烦。我建议把模型名也做成 API 的可选参数,服务端白名单校验一下允许使用的模型列表,这样既灵活也不至于被人乱调用资源。

5. 常见问题与排查技巧实录

5.1 我实际遇到的问题与排查过程

先说一个最有代表性的:抓取结果返回空字典。这种情况通常是嵌入模型过滤把关太严了。页面切块后,没有任何一个块和目标 prompt 的相似度超过阈值,导致传给大模型的内容是空的。排查思路是先打印一下过滤后的上下文片段,看看是不是空的。如果是,就调高文本块候选数量,或者换一个语义更强的嵌入模型。

还有一个高频问题:动态页面抓出来只有框架没有数据。这多半是浏览器模式没有正确开启,或者 Playwright 内核缺失。检查点有两个,一是 config 里headlessbrowser是否配置正确,二是命令行执行playwright install chromium是否成功。需要注意,框架在使用 Playwright 时会自己启动浏览器实例,不要在业务代码里额外开一个浏览器,两者会冲突。

第三个问题是输出 JSON 格式不稳定。有些页面结构复杂时,模型偶尔会在 JSON 外面包一层解释性文字,或者字段名和 prompt 里要求的不一致。解决办法是在 prompt 里给一个完整的输出示例,并且明确写“只输出 JSON,不要任何额外说明”。相比在代码里写正则去清洗输出,从源头约束模型要靠谱得多。

5.2 问题速查表

现象可能原因处理办法
返回空结果相关文本片段被过滤掉调整嵌入模型或提高候选块数量
动态页面内容缺失浏览器模式未开启配置headlessbrowser参数并安装 Playwright 内核
输出 JSON 格式混乱提示词约束不够严格在 prompt 里增加输出示例并明确禁止解释性文字
调用模型频繁超时批量并发过高用信号量限制并发数,增加重试和延迟
本地模型运行很慢模型规模太大或硬件不足换小参数模型,或改用云 API 处理大批量任务
整体 token 消耗过大页面文本块太多调小 chunk 数量,优化 prompt 长度,预先过滤无关区域

5.3 给你的一些使用心得

用 ScrapeGraphAI 做了几个真实项目之后,我最大的体会是:它适合做“理解型抽取”,不适合做“海量搬运”。如果目标是每天抓百万级商品数据,直接上 LLM 抽取根本不现实,成本和时间都受不了。合理的架构是把传统规则爬虫和 ScrapeGraphAI 结合起来:规则爬虫负责把任务量大的字段先抽一轮,处理不了的复杂字段再交给 ScrapeGraphAI 做兜底提炼。这样成本可控,也能发挥语义理解的长处。

还有一个小技巧,在重复抓取同一个页面时,source参数可以直接传 HTML 字符串,避免每次都从网络加载。先把页面下载到本地或数据库,离线跑抽取,相当于把“下载”和“理解”两个阶段解耦了。这么做之后,即使页面在重跑时已经改版,你手里还有旧内容可以做对比,调试起来特别方便。

最后想说的是,这个项目迭代速度很快,API 也常有调整。你在搜索引擎或社区看到的教程可能来自不同版本,如果代码跑不通,优先看官方文档对应版本的 release 说明,而不是硬改代码。用 LLM 做爬虫是个新思路,但它也不是万能钥匙,理解它的边界,才能在合适的场景里发挥最大的价值。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 8:49:44

Agent空转?从常驻VM到按需算力的实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 8:48:40

两轮车电机换相检测方案:从霍尔到编码器的6种对比与选型

换相检测这件事,玩两轮车电机的朋友迟早都要面对。不管你是做电动自行车、平衡车、电动滑板车,还是改装的电摩,只要是BLDC或者PMSM电机,都有一个绕不开的问题:怎么知道转子转到哪个位置了?或者说&#xff0…

作者头像 李华
网站建设 2026/9/6 8:43:31

STM32驱动TT马达:从PWM调速到PID闭环的完整实战指南

不用管原理有多玄,先记住一句话:TT马达几乎是小车类项目的默认选项。我第一次接触这玩意儿,是在给一块STM32F103C8T6做两轮小车的时候,拆开快递盒发现里面躺着两个带齿轮箱的直流小电机,手上连驱动芯片都没有&#xff…

作者头像 李华
网站建设 2026/9/6 8:42:36

32位MCU最小封装竞赛:WLCSP与低功耗设计的工程实践

1. 这颗“最小”的32位MCU,凭什么大家都在抢着做这几年芯片圈有一个特别有意思的现象:8位MCU还没彻底退场,32位MCU已经把战火烧到了“封装面积”上。各家的新品发布,PPT上不再是单纯标主频、标Flash,而是放一张芯片实拍…

作者头像 李华
网站建设 2026/9/6 8:42:34

图书馆局域网系统规划与设计:从VLAN划分到安全加固的实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 8:41:36

AI 新闻周报 | 2026年8月31日 — 9月5日

AI 新闻周报 | 2026年8月31日 — 9月5日 本周 TL;DR:本周 AI 产业进入"旗舰模型密集对决 IPO 冲刺 安全合规收紧"三重高潮。国际方面,Anthropic 发布 Claude Fable 5.1 / Mythos 5.1 并计划美国劳动节(9月7日)后公开 …

作者头像 李华