Ollama 网络爬虫:在无需 API 密钥的情况下运行本地 LLM 爬虫
Senior Web Scraping Engineer
TL;DR:
- Ollama在您自己的机器上运行提取模型,因此抓取程序的LLM步骤不需要API密钥,也不需要按令牌计费。
- Ollama不负责获取数据。 本地模型没有浏览器,也无法访问网页网络;它仅对您提供的文本进行结构化处理。
- 这个差距在一个脚本中显示出来。 对一个JavaScript渲染的演示页进行简单的GET请求会得到0个引用块;通过使用
js_render的Scrapeless通用抓取API访问同样的URL则可以得到全部10个——而这个渲染的HTML就是模型读取的内容。 - 本指南中的提取是真实的。 一次实时运行将三个渲染的引用块传给本地的
qwen2.5:0.5b模型,得到每个引用的干净JSON返回,里面包含文本和作者——没有任何云调用。 - 小型模型更希望减少噪音。 在模型查看内容之前,先将HTML裁剪到内容区域;在目标片段上使用0.5B模型速度快且准确,而完整页面则无法适应。
- 抓取方面,免费开始。 在 app.scrapeless.com 创建您的Scrapeless API密钥。
为什么在本地运行抓取器的模型
一个LLM抓取程序将页面内容转化为结构化记录,而该模型不必驻留在他人的云端。Ollama 在您的硬件上通过一个小型HTTP API运行开放模型,因此提取步骤不需要API密钥、没有速率限制,也不需要按令牌计费——在处理许多页面或处理您宁愿不发送给第三方的数据时非常有用。
本地模型无法执行的操作是获取数据。它没有浏览器,不持有会话,在JavaScript渲染的页面上,普通HTTP请求返回的只是标记,没有任何内容。因此,Ollama网页抓取程序由两层组成:获取层返回忠实渲染的HTML,而本地模型则作为提取层,负责将其转化为记录。本指南使用Scrapeless通用抓取API作为第一层。如果您想让本地模型进行实时网络搜索——模型调用搜索工具并推理结果——那是另一项工作,涵盖在本地LLM网络搜索指南中;本文的主题是从特定页面提取结构化数据。
先决条件
- 安装并运行Ollama,并下载一个小型工具友好的模型:
ollama pull qwen2.5:0.5b。 - 安装Python 3.9或更高版本,并确保有
requests库。 - 从仪表盘获取Scrapeless API密钥,并将其导出为
SCRAPELESS_API_KEY。
安装
下载一个小型模型并安装Python的依赖项。Ollama服务器在运行后监听localhost:11434。
bash
ollama pull qwen2.5:0.5b
pip install requests
将您的密钥保存在环境中,而不是源代码中:
bash
export SCRAPELESS_API_KEY="sk_your_scrapeless_key"
获取模型可以实际读取的页面
提取质量受获取精度的限制,因此首先要从这里开始。在JavaScript渲染的页面上,普通HTTP客户端接收的文档与阅读器所见的文档不同——内容仅在脚本构建DOM后到达,这一生命周期定义于HTML脚本规范。一个脚本可以计算出差异:
python
# fetch_rendered.py — 普通GET与服务器端渲染,同一URL
import os
import requests
URL = "https://quotes.toscrape.com/js/"
MARKER = '<div class="quote">'
plain = requests.get(URL, timeout=60).text
print("plain GET chars:", len(plain), "| quote blocks:", plain.count(MARKER))
resp = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
headers={"Content-Type": "application/json", "x-api-token": os.environ["SCRAPELESS_API_KEY"]},
json={"actor": "unlocker.webunlocker", "input": {"url": URL, "js_render": True}},
timeout=120,
)
resp.raise_for_status()
rendered = resp.json().get("data", "")
print("rendered chars:", len(rendered), "| quote blocks:", rendered.count(MARKER))
运行将为普通获取输出0个引用块,而对渲染获取的结果则为10:
text
plain GET chars: 5806 | quote blocks: 0
rendered chars: 8940 | quote blocks: 10
渲染、解锁和代理路由都在那一次POST的服务器端完成——通用抓取API是获取层,而渲染的HTML就是本地模型提取的内容。
使用本地模型进行提取
现在将内容交给Ollama。保持小模型准确性的两个关键因素是:将HTML修剪到内容区域,以便模型不读取页面外观,并请求format: "json"以便输出可解析。Ollama的/api/generate端点接受模型名称和提示,并返回完成,具体记录在Ollama API参考中:
python
# extract_local.py — 使用本地Ollama模型获取、修剪和提取
import json
import os
import re
import requests
resp = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
headers={"Content-Type": "application/json", "x-api-token": os.environ["SCRAPELESS_API_KEY"]},
json={"actor": "unlocker.webunlocker", "input": {"url": "https://quotes.toscrape.com/js/", "js_render": True}},
timeout=120,
)
resp.raise_for_status()
html = resp.json().get("data", "")
# 修剪到前面三个引用块 —— 小型本地模型在噪音更少的情况下效果更好。
blocks = re.findall(r'<div class="quote">.*?</small>', html, re.S)[:3]
snippet = "\n".join(blocks)
completion = requests.post(
"http://localhost:11434/api/generate",
json={
"model": "qwen2.5:0.5b",
"prompt": '从此HTML中提取每个引用到JSON,格式为完全相同的'
'{"quotes":[{"text":"...","author":"..."}]}。只回复JSON。\n\nHTML:\n' + snippet,
"stream": False,
"format": "json",
"options": {"temperature": 0, "num_predict": 400},
},
timeout=600,
)
data = json.loads(completion.json()["response"])
records = data["quotes"]
print("记录数量:", len(records))
print("第一个作者:", records[0]["author"])
print("第一个文本:", records[0]["text"])
本地运行返回了三个记录,第一条的文本和作者完整:
text
记录数量: 3
第一个作者: 阿尔伯特·爱因斯坦
第一个文本: 我们创造的世界是我们思维过程的结果。没有改变我们的思维,就无法改变它。
这就是整个爬虫,模型部分没有触及网络:一次POST请求到Scrapeless进行获取和渲染,一次调用localhost进行提取。根据硬件处理能力调整每次调用的块数——在CPU上,0.5B模型比云端端点慢,因此尽量批量处理小块并保持输入紧凑。
在免费计划上获取您的API密钥:app.scrapeless.com
高级模式
- 根据硬件匹配模型。
qwen2.5:0.5b在CPU上足够快;如果您有内存和耐心,3B或7B模型在更复杂的页面上提取更可靠。请求的格式不变——只有model字符串改变。 - 在提示之前修剪。 隔离内容容器是小模型质量的最大杠杆。发送重复的块,而不是整个文档,这样token成本和错误率都会降低。
- 保持
format: "json"和temperature: 0。 JSON模式限制输出为可解析的JSON,而零温度保持提取过程在多次运行中稳定;这两个对爬虫来说都是不可选的。 - 在Python中循环,而不是在提示中。 每个请求一个内容区域可以防止记录混合在一起,并使坏提取可归因于特定页面。
故障排除
Connection refused在localhost:11434。 Ollama服务器未运行。启动它(ollama serve),并确认使用ollama list拉取了模型。- 没有块可提取。 您的获取返回了预渲染的HTML。像第一个脚本那样计算已知元素;几乎空的获取是渲染问题,通过
js_render解决,而不是模型问题。 - 模型返回文本而不是JSON。 您丢失了
format: "json"。有了它,Ollama可以限制输出;没有它,您是在解析善意。 - 提取缓慢或掉记录。 模型对于输入大小太小。每次调用修剪到更少的块,或切换到更大模型——小型本地模型在速度上牺牲了准确性,而更短、更清晰的提示是您恢复这两者的方法。
结论
Ollama赢得了作为提取层的地位,它在您所处的地方运行:在您自己的硬件上开启模型,无需密钥和每个token账单,将页面内容转换为JSON。决定这一切是否可能的层是获取——第一个脚本的0与10的计数决定了这一点——而一次服务器渲染的POST填补了空白。将两者连接起来,一个渲染的页面变成了结构化记录,而模型部分完全保持在localhost上。
创建一个免费的Scrapeless账户以获取API密钥, 开发者文档 介绍了unlocker.webunlocker参数。当您计划一个定期作业时,请查看 Scrapeless定价。
常见问题解答
问:Ollama可以自己抓取网站吗?
不可以。Ollama在本地运行语言模型;它没有针对任意页面的HTTP客户端,也不渲染任何JavaScript。它结构化您提供的文本。将其与抓取层配对——这里是Scrapeless通用抓取API,它在服务器端渲染页面——然后本地模型负责提取。
问:这与给本地LLM网页搜索有何不同?
方向。网页搜索设置允许模型调用搜索工具并通过结果推理以回答问题;此设置获取您选择的特定页面并让模型从中提取结构化字段。一个是增强搜索的回答,另一个是抓取管道——上述链接的本地LLM网页搜索指南涵盖了前者。
问:我应该使用哪个本地模型进行提取?
最小的模型,它能容纳您的架构。使用严格JSON提示和temperature: 0的提取不需要大量推理,因此0.5B模型在修剪的片段上运行良好,正如上面的运行所示。仅当页面混乱到小模型无法提取字段时,才切换到3B或更大的模型。
问:我需要GPU吗?
不需要。本指南中的运行使用了0.5B模型在CPU上。GPU使较大模型更实用且更快,但在CPU上运行的小模型足以构建和测试管道。
问:使用本地模型抓取是否合法?
在本地运行模型并不会改变收集规则。仅获取公共页面,尊重网站条款和机器人排除协议标准化的robots指令,保持数量有限,并在适用的法律下处理任何个人数据。
在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。



