ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

ScrapeGraphAI实战:用LLM语义抽取网页内容,告别XPath和CSS选择器 第一次把 ScrapeGraphAI 这个项目装进环境时我其实没抱太大期望。这几年号称“AI 爬虫”的工具太多了多数就是把模型接在一个 requests 循环后面换个名字而已。但 Scrapegraph-ai 不太一样它把整个抓取流程做成了图结构不同节点各司其职真正的抽取动作交给大语言模型去完成而不是靠写死的 XPath 或 CSS 选择器。换句话说以前写爬虫是在跟 HTML 标签斗智斗勇现在只要用一句人话描述“我要什么”模型会自己到页面里找。这篇文章我会从使用者的角度完整拆解这个项目能做什么、怎么配置、有哪些坑并附上可以直接照抄的代码。适合人群很明确被页面结构和改版折磨的爬虫开发者想做 LLM 工程化落地但不想从零搭框架的工程师以及需要定期把网页内容整理成结构化数据的分析师。1. ScrapeGraphAI 是什么用“对话”代替“选择器”的爬虫新思路1.1 传统爬虫让我最头疼的三件事做爬虫时间久了你会发现真正烦人的不是请求网站那一步而是解析结构这件事。第一很多页面的 HTML 标签没有语义div套divclass 名还经常是压缩过的随机字符串你想定位一个商品价格得先在开发者工具里点半天。第二网站前端一改版哪怕只是改了某个 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确认版本同时看一下它依赖的httpx、beautifulsoup4等关键包是否被自动装好。社区里常见的问题之一是用 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列出页面上所有的项目名称、描述和链接, sourcehttps://perinim.github.io/projects/, configconfig, ) result graph.run() print(result)运行之后结果是一个 JSON 字典里面通常是一个projects列表每个项目包含name、description、link三个字段。第一次跑通这个例子你就掌握了 90% 的基础用法SmartScraperGraph接收 prompt、source 和 config 三个参数run()执行整个抓取流程并返回结构化数据。几个参数值得细说。temperature我建议一律调低最好在 0.1 左右抽取任务需要的是稳定和准确不需要创造性发散。model里的前缀openai/是必要的它告诉框架走哪个后端如果换成本地模型这里就变成ollama/llama3.1这种写法。source既可以是 URL 字符串也可以是已经抓取好的 HTML 内容这一点在离线处理场景非常有用后面我会单独讲。2.3 输出格式转换从 JSON 到 CSV、XMLrun()返回的 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(sourceself.source, configself.config) parse_node ParseNode(configself.config) generate_node GenerateAnswerNode(output_typejson, configself.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 里headless和browser是否配置正确二是命令行执行playwright install chromium是否成功。需要注意框架在使用 Playwright 时会自己启动浏览器实例不要在业务代码里额外开一个浏览器两者会冲突。第三个问题是输出 JSON 格式不稳定。有些页面结构复杂时模型偶尔会在 JSON 外面包一层解释性文字或者字段名和 prompt 里要求的不一致。解决办法是在 prompt 里给一个完整的输出示例并且明确写“只输出 JSON不要任何额外说明”。相比在代码里写正则去清洗输出从源头约束模型要靠谱得多。5.2 问题速查表现象可能原因处理办法返回空结果相关文本片段被过滤掉调整嵌入模型或提高候选块数量动态页面内容缺失浏览器模式未开启配置headless和browser参数并安装 Playwright 内核输出 JSON 格式混乱提示词约束不够严格在 prompt 里增加输出示例并明确禁止解释性文字调用模型频繁超时批量并发过高用信号量限制并发数增加重试和延迟本地模型运行很慢模型规模太大或硬件不足换小参数模型或改用云 API 处理大批量任务整体 token 消耗过大页面文本块太多调小 chunk 数量优化 prompt 长度预先过滤无关区域5.3 给你的一些使用心得用 ScrapeGraphAI 做了几个真实项目之后我最大的体会是它适合做“理解型抽取”不适合做“海量搬运”。如果目标是每天抓百万级商品数据直接上 LLM 抽取根本不现实成本和时间都受不了。合理的架构是把传统规则爬虫和 ScrapeGraphAI 结合起来规则爬虫负责把任务量大的字段先抽一轮处理不了的复杂字段再交给 ScrapeGraphAI 做兜底提炼。这样成本可控也能发挥语义理解的长处。还有一个小技巧在重复抓取同一个页面时source参数可以直接传 HTML 字符串避免每次都从网络加载。先把页面下载到本地或数据库离线跑抽取相当于把“下载”和“理解”两个阶段解耦了。这么做之后即使页面在重跑时已经改版你手里还有旧内容可以做对比调试起来特别方便。最后想说的是这个项目迭代速度很快API 也常有调整。你在搜索引擎或社区看到的教程可能来自不同版本如果代码跑不通优先看官方文档对应版本的 release 说明而不是硬改代码。用 LLM 做爬虫是个新思路但它也不是万能钥匙理解它的边界才能在合适的场景里发挥最大的价值。
返回列表