如何构建一个通用的 AI Web 爬虫
最近,我一直在研究网页抓取技术。鉴于人工智能领域的快速发展,我尝试构建一个 “通用” 的网页抓取工具,它可以在网页上迭代遍历,直到找到需要抓取的信息。这个项目目前还在开发中,这篇文章我将分享一下该项目目前的进展。
目标愿景
给定一个初始网址和一个高层次目标,该网页抓取工具需能够:
- 分析给定网页的内容;
- 从相关部分提取文本信息;
- 进行必要的页面交互;
- 重复上述步骤,直至达成目标。
使用的工具
尽管这是一个纯后端工程,但我使用了 NextJs 作为开发框架,便于未来扩展前端。网页抓取部分选择了 Crawlee 库,这是一个基于 Playwright 的浏览器自动化库。Crawlee 对浏览器自动化进行了优化,使爬虫能更好地模仿人类用户。Crawlee 还提供了请求队列系统,便于按顺序管理大量请求,这对于未来部署服务很有帮助。
AI 部分主要使用了 OpenAI 的 API 接口和 Microsoft Azure 的 OpenAI 服务,总共使用了三个模型:
- GPT-4-32k (‘gpt-4-32k’)
- GPT-4-Turbo (‘gpt-4-1106-preview’)
- GPT-4-Turbo-Vision (‘gpt-4-vision-preview’)
相比原版 GPT-4,GPT-4-Turbo 模型上下文窗口更大 (128k 令牌),速度更快 (最高提速 10 倍),但智能程度略低。在一些复杂情况下就显得欠灵活,这时我会使用 GPT-4-32K 获取更高的智能。
GPT-4-32K 是 GPT-4 的改良变体,上下文窗口为 32k,远远超过 4k。由于 OpenAI 当前限制对该模型的访问,我最终选择通过 Azure 的 OpenAI 服务来访问该模型。
起步
我从需求约束出发,反向设计。由于底层使用 Playwright 爬虫,我知道如果要与页面交互,最终必须要从页面中获取元素的选择器。
元素选择器是一个字符串,用于唯一标识页面上的某个元素。例如,如果我想选取页面上的第四个段落,我可以使用 p:nth-of-type(4)
作为选择器。如果我要选择一个写着 ‘Click Me’ 的按钮,我可以用 button:has-text('Click Me')
这个选择器。Playwright 通过选择器先锁定目标元素,然后对其执行特定的动作,比如点击 'click()'
或填充 'fill()'
。
因此,我的首要任务是理解如何从给定的网页中识别出 “目标元素”。从现在起,我会将这一过程称为 ‘GET_ELEMENT’。
获取 “目标元素” 的方法
方法 1:截图 + 视觉模型
HTML 数据通常都很复杂和冗长。大部分内容用于定义样式、布局和交互逻辑,而非文本内容本身。我担心文本模型处理这种情况效果欠佳,所以我的想法是使用 GPT-4-Turbo-Vision 模型直接 “查看” 渲染后的页面,抄录出最相关的文本,然后在源 HTML 中搜索包含该文本的元素。
但这个方法很快就失败了:
GPT-4-Turbo-Vision 有时会拒绝我的抄录文本请求,说 “对不起,我无法帮助你完成这项任务” 等。有一次,它甚至声称 “不能从有版权图片中抄录文本”。看来 OpenAI 在努力限制它帮助执行这类任务 (不过,如果我告诉它自己是盲人似乎可以绕过这个限制)。
随后出现了更严峻的问题:大页面的截图高度往往很夸张 (>8000 像素)。这是个问题,因为 GPT-4-Turbo-Vision 会将所有图像预处理调整为固定尺寸。我发现超高图像在预处理后可能会严重变形,无法辨认。
一种可能的解决方案是分段扫描页面,逐段总结后再拼接。但鉴于 OpenAI 对 GPT-4-Turbo-Vision 的速率限制,我不得不建立一个队列系统来进行流程管理,听起来就很麻烦。
此外,仅从文本反推出有效的元素选择器也非常困难,因为你不知道底层 HTML 的结构。基于以上原因我决定放弃这种方法。
方法 2:HTML + 文本模型
纯文本的 GPT-4-Turbo 速率限制较宽松,上下文窗口有 128k,所以我试着直接输入整个页面 HTML,要它识别相关元素。
尽管 HTML 数据基本符合 (大多数情况下),但我发现 GPT-4-Turbo 模型的智能程度仍不足以正确无误地完成这项工作。它们经常识别错误的元素,或者给出范围过广的选择器。
所以我试着进一步简化 HTML 代码,只保留 body 部分并移除脚本和样式标签,隔离主体 HTML 以缩小范围,这有一定帮助,但问题依旧存在。对语言模型来说,从整个页面准确识别 “相关” HTML 元素是一个过于复杂和不确定的任务,我需要某种方法将候选元素范围缩减到仅剩几个,然后再手动提交给文本模型。
接下来