Ollama Vision 网页抓取:使用本地模型读取屏幕截图
Senior Cybersecurity Analyst
TL;DR:
- 这是本地模型加截图的组合,而本网站上现有指南都没有单独涵盖它。 Ollama Web Scraping 在渲染的HTML文本上运行本地模型,而不是截图;云视觉API读取截图,但按图像收费并需要密钥。本指南在真实浏览器截图上运行一个具备视觉能力的本地模型——没有云密钥,没有按令牌计费,完全不需要HTML。
- 这里的每次运行都只使用Ollama的CPU,并且
ollama ps确认了这一点。 在这个环境中Ollama没有GPU卸载可用,并且调用之间的时间变化很大——从两位数的秒到几分钟——这取决于模型是否仍驻留在内存中。 - Ollama的图像字段不是云格式。 Ollama的
/api/generate中的images字段接受一系列未经data:image/png;base64,前缀的原始base64字符串——这与大多数云视觉API使用的OpenAI兼容约定正好相反。 - 一个小于2B的视觉模型在任务的两个端点都不可靠,以不同的方式。 当被要求用一句话描述页面时,它命名了一个结构细节,但与实际页面不符。当要求提供一个结构化的JSON记录——这是云视觉同类能够干净处理的确切任务——它回声提示的占位符文本,然后偏离输出,完全不是JSON。
- 截图仍然来自真实渲染的浏览器。 Scrapeless抓取浏览器通过CDP捕捉页面,方式与云视觉管道相同;只有像素读取的位置发生了变化。
- 从提取端免费开始。 在app.scrapeless.com创建你的Scrapeless API密钥。
为什么将本地模型与截图配对
通常情况下,“AI网页抓取”这两件事是分别真实的,很少同时真实。通过Ollama运行的本地模型每次调用不收费,并且不发送任何信息给第三方——但本网站上的本地模型指南到目前为止读取的是渲染的HTML文本,而不是像素。视觉模型读取截图而不是DOM——但本网站上的视觉模型指南到目前为止调用的是一个按图像收费并需要云提供商密钥的云API。此帖子是其中没有一个涵盖的组合:一个完全在你面前的机器上运行,读取真实浏览器截图的具备视觉能力的模型,并且在任何地方的管道中都没有云推理账单。
这两个现有指南仍然在各自擅长的方面有用。Ollama Web Scraping 是当你需要的价值已经是渲染的DOM中的文本时的正确选择——本地模型在修整的HTML片段上快速且准确。本网站的GPT视觉网页抓取指南是在价值真正视觉且你希望有一个能力强、付费模型来读取它时的正确选择。本指南适用于第三种情况:价值是视觉的,但你希望完全没有云依赖,并愿意为此 trade 能力。这种交易是真实的,本指南诚实地报告了这一点,而不是仅仅展示有效的部分。
先决条件
- Ollama安装并运行,已拉取具备视觉能力的模型:
ollama pull moondream。 - Python 3.9或更高版本,并带有
playwright和requests。 - 在仪表板上获得的Scrapeless API密钥,导出为
SCRAPELESS_API_KEY。可在app.scrapeless.com免费获得。 - 不需要GPU。本指南中的每次运行都只使用Ollama的CPU。
安装
bash
pip install playwright requests
ollama pull moondream
playwright 在本指南中通过CDP连接到远程浏览器,因此不需要本地Chromium下载。moondream 是一个紧凑的具备视觉能力的模型——足够小,可以在没有独立GPU的情况下拉取和运行,这也是本指南的目的。
配置
bash
export SCRAPELESS_API_KEY="your_scrapeless_api_key"
这是此管道所需的唯一密钥。Ollama的本地API根本不需要凭证。
使用抓取浏览器捕获截图
截图仍然必须忠实,因此仍然来自真实浏览器。通过Chrome开发者工具协议连接到Scrapeless抓取浏览器,打开页面并捕获渲染的视口——这是云视觉同类所使用的相同捕获步骤,通过 Chrome开发者工具协议:
python
import os
from urllib.parse import urlencode
from playwright.sync_api import sync_playwright
def browser_url():
return "wss://browser.scrapeless.com/api/v2/browser?" + urlencode(
{"token": os.environ["SCRAPELESS_API_KEY"], "sessionTTL": 180, "proxyCountry": "US"})
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(browser_url())
page = browser.new_page(viewport={"width": 1280, "height": 900})
page.goto("https://quotes.toscrape.com/", wait_until="networkidle")
png = page.screenshot()
browser.close()
print("screenshot bytes:", len(png))
print("PNG signature ok:", png[:8] == b"\x89PNG\r\n\x1a\n")
text
screenshot bytes: 55462
PNG signature ok: True
在免费计划中获取您的 API 密钥:app.scrapeless.com
使用本地视觉模型读取屏幕截图
将 PNG 进行 Base64 编码并直接发布到 Ollama 的本地 /api/generate 端点。这就是两个轴实际上相交的地方:图像来自真实的云渲染浏览器,但读取它的模型从未离开过这台机器。请求的形状在 Ollama API 参考 中有文档记录——请注意 images 字段接受一个普通的 base64 字符串,而不是 data:image/png;base64,... URL:
python
import base64, os, time
from urllib.parse import urlencode
import requests
from playwright.sync_api import sync_playwright
def browser_url():
return "wss://browser.scrapeless.com/api/v2/browser?" + urlencode(
{"token": os.environ["SCRAPELESS_API_KEY"], "sessionTTL": 180, "proxyCountry": "US"})
t0 = time.time()
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(browser_url())
page = browser.new_page(viewport={"width": 1280, "height": 900})
page.goto("https://quotes.toscrape.com/", wait_until="networkidle")
png = page.screenshot()
browser.close()
capture_s = round(time.time() - t0, 1)
img_b64 = base64.b64encode(png).decode()
t1 = time.time()
resp = requests.post(
"http://localhost:11434/api/generate",
json={
"model": "moondream",
"prompt": "Describe what this webpage shows in one sentence.",
"images": [img_b64],
"stream": False,
"options": {"temperature": 0, "num_predict": 60},
},
timeout=300,
)
resp.raise_for_status()
data = resp.json()
inference_s = round(time.time() - t1, 1)
print("capture seconds:", capture_s)
print("vision inference seconds:", inference_s)
print("description:", data["response"].strip())
text
capture seconds: 11.5
vision inference seconds: 54.7
description: A webpage titled "Quotes to Scrape" displays a list of quotes and their corresponding tags, organized into five sections.
每个引用的标题、引号的存在以及下面的标签都是渲染页面的真实元素。它在顶部添加的具体结构,“组织成五个部分”,无法与实际页面进行验证:quotes.toscrape.com 在主页上呈现十个引用块,没有将任何内容分组为五个。模型正确捕捉了页面的一般主题,一个有标签的引用网站,然后声明了一个具体的结构细节,但与实际存在的内容不符。
ollama ps 在此类调用中报告 100% CPU:在此环境中,Ollama 没有 GPU 卸载可用。像这样的调用的时机也因模型状态而异。Ollama 的 /api/generate 端点在每次请求后保留模型驻留在内存中 keep_alive,默认情况下为 5 分钟,以上在 Ollama API 参考中有文档记录;当模型仍驻留时的调用会跳过从磁盘重新加载它,而冷调用(没有任何驻留)在第一个令牌返回之前需要支付加载时间。
结构化提取的突破点
一个有效的描述并不等同于可靠的提取。请求同一个模型执行云视觉兄弟在一个干净的传递中处理的确切任务——一个包含引用记录的 JSON 数组——诚实的结果也应包含在此指南中:
python
import base64, json, os, time
from urllib.parse import urlencode
import requests
from playwright.sync_api import sync_playwright
def browser_url():
return "wss://browser.scrapeless.com/api/v2/browser?" + urlencode(
{"token": os.environ["SCRAPELESS_API_KEY"], "sessionTTL": 180, "proxyCountry": "US"})
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(browser_url())
page = browser.new_page(viewport={"width": 1280, "height": 900})
page.goto("https://quotes.toscrape.com/", wait_until="networkidle")
png = page.screenshot()
browser.close()
img_b64 = base64.b64encode(png).decode()
t1 = time.time()
resp = requests.post(
"http://localhost:11434/api/generate",
json={
"model": "moondream",
"prompt": 'From this screenshot of a quotes page, return JSON '
'{"quotes":[{"author":"...", "text":"..."}]} for the first 3 quotes '
'you can see. Return ONLY JSON.',
"images": [img_b64],
"stream": False,
"format": "json",
"options": {"temperature": 0, "num_predict": 150},
},
timeout=600,
)
resp.raise_for_status()
data = resp.json()
inference_s = round(time.time() - t1, 1)
print("vision inference seconds:", inference_s)
print("raw response:", data["response"][:200])
try:
parsed = json.loads(data["response"])
print("parsed quotes:", len(parsed.get("quotes", [])))
except json.JSONDecodeError as e:
print("JSON parse failed:", e)
text
vision inference seconds: 79.6
raw response: {"quotes":[{"author":"...","text":"..."},{"author":"","text":"","}}] } [0.12, 0.13, 1.0, 0.87] ) ; return [0.12, 0.13, 1.0, 0.87] ; return [0.12, 0.13, 1.0, 0.87] ; return [0.12, 0.13, 1.0, 0.87] ; re
JSON parse failed: Unterminated string starting at: line 1 column 65 (char 64)
第一个对象逐字复制了提示自己的 "..." 占位符语法,仿佛说明中的省略号是要复制的值,而不是要填充的插槽。第二个对象从未正确关闭,响应随后漂移到重复的四数字数组和与引用、作者或 JSON 完全无关的零散 return 语句——输出呈现为与这一任务不同的剩余部分,而不是对此任务的失败尝试。在此指南背后的每次实时运行中,潜在模式保持一致,即使确切的垃圾变化形状:从未返回任何真实的引用文本或作者名,响应也从未正确关闭为有效的 JSON。这是真正的结果,而不是截断的日志:moondream 是一个大约 10 亿参数的模型,可以遵循模式的行为使大云视觉模型在几秒钟内返回三个干净、正确归属的记录,而这个小模型则无法复制。提升输出长度上限并不能修复模式;它主要是给模型更多漂移的空间。
本网站的 GPT Vision 网络爬取指南 显示了这种交易的另一端。一个托管的多模态模型 GPT-4o-mini,通过 OpenAI 的 API,将 Playwright 的屏幕截图转换为实际填充的结构化产品和价格字段。这个比较,能够更强有力的付费模型与一个免费的 10 亿参数本地模型对比,是在本地运行的一个要点。
本地视觉 vs. 云视觉 vs. 本地文本
本网站现有三篇指南覆盖获取结构化数据的三种不同权衡,无需手动编写选择器:
| 模型位置 | 读取 | 每次调用的成本 | 结构化 JSON 准确性 | |
|---|---|---|---|---|
| Ollama 网络爬取 | 本地 | 渲染的 HTML 文本 | 免费 | 在修整的 HTML 上可靠 |
| GPT 视觉网络爬取指南 | 云 | 屏幕截图 | 按图像收费,需云密钥 | 一个更大付费模型——在本指南中未基准测试 |
| 本指南 | 本地 | 屏幕截图 | 免费 | 在这个模型大小下不可靠 |
| 这三者都不是普遍正确的。如果值是在DOM中的文本,解析DOM — 本地文本模型或普通选择器比任何视觉管道都更快、更便宜。如果值确实是视觉的并且准确性很重要,云视觉模型的每张图像成本是值得的。如此小的本地视觉模型适合的情况比这两者都更窄:探索性或低风险的读取,其中人类或下游检查可以抓住偶尔的错误细节,而不是一个信任输出的无监督管道。根据上述证据,这一点在松散的单句描述和严格的多字段模式中都是真实的——失败模式在每种情况下看起来只是不同。 |
结论
将本地模型与屏幕截图配对填补了该网站现有的两个指南之间的真实空白。管道中没有任何地方接触到云模型:不带抓取的抓取浏览器仍然以云视觉管道的方式呈现和捕捉页面,而Ollama则完全在这台机器上读取生成的PNG。根据这里的证据,这种组合实际上比云视觉模型在两个方向上都更窄 —— 一句描述添加了页面没有的结构性细节,而严格的JSON模式在漂移到根本不是JSON的输出之前回显了它自己的占位符文本。将这种大小的模型用于你计划检查的探索性读取,而不是用于一个无监督信任结果的管道,当提取需要准确时,请参考云视觉指南。
创建一个免费的Scrapeless账户以获取API密钥,查看抓取浏览器产品页面以了解CDP会话支持的内容,并在大规模运行浏览器侧之前查看Scrapeless定价。
加入我们的社区,与其他构建本地提取管道的开发者分享笔记:Discord · Telegram。
常见问题
问:这个指南和云GPT视觉指南之间的实际区别是什么?
模型运行的位置和花费。这网站的GPT视觉网页抓取指南将屏幕截图发送到托管的多模态模型,通过API按图像收费;而本指南则将同样类型的屏幕截图发送到通过Ollama运行的模型,不需要API密钥,也没有每次调用的费用。两者都是读取像素,而不是HTML。
问:这个指南和本地Ollama文本提取指南之间有什么区别?
模型读取的内容。Ollama网页抓取获取渲染的HTML并将本地模型的文本交给解析。该指南从不将HTML发送给模型 — 它交给本地模型一个屏幕截图,并要求其读取图像。
问:我需要GPU才能运行本地视觉模型吗?
不需要,但没有的话等待时间可能会大幅波动。本指南中的每次运行都只使用moondream在CPU上,ollama ps在推理期间确认报告100% CPU,本指南背后的调用从双位数秒到为同种请求换取几分钟。Ollama在每次请求后保持模型驻留keep_alive(默认5分钟),因此在其仍然加载时进行的调用跳过了冷调用所需的磁盘加载时间 — 单凭这一点就解释了大部分波动。Ollama可以访问的GPU将大幅减少每个情况的时间。
问:为什么Ollama的API中的images字段与云视觉请求看起来不同?
因为它使用了不同的约定。Ollama的/api/generate接受images作为没有前缀的原始base64字符串列表。大多数与OpenAI兼容的云视觉API,包括本网站的GPT视觉网页抓取指南,期望在image_url内容部分内有一个完整的data:image/png;base64,... URL。在两个格式之间移植代码而不调整这一点是第一个会出现问题的地方。
问:为什么结构化提取返回了空字段而不是错误?
请求本身成功 — Ollama 返回了一个带有 response 字段的 200 状态码,因此 raise_for_status() 从未触发。模型填充了它被告知要生成的 JSON 形状,但未填充值,然后不断重复该空形状,直到输出长度限制在字符串中间将其截断。这是一个模型能力的失败,而不是 HTTP 失败,这正是上面的代码为什么要单独检查 json.loads(),而不是假设 200 意味着有效、完整的数据。
问:更大的本地视觉模型能解决结构提取问题吗?
可能可以,但需要成本。像 llava:7b 这样的 7B 级本地视觉模型具有更大的容量来容纳 JSON 架构并正确填充,但它也需要更多的 RAM 或 VRAM,且在相同硬件上的每个 token 处理速度更慢。这个指南围绕的折衷方案 — 真正免费的、真正的本地 — 随着模型的增长而变得越来越难以维持;在某些时候,云视觉模型的每张图像成本会变得低于大型本地模型所需的良好运行硬件成本。
问:我应该将其用于生产抓取管道吗?
在这个模型规模上,不要无监督使用。根据这里的证据,既没有一个句子的描述,也没有一个结构化架构是完全值得信赖的 — 描述添加了页面没有的结构细节,而架构则在偏离非 JSON 输出之前回响了自己的占位符文本。将严格的架构提取路由到云视觉指南或 DOM 解析,使用 Ollama Web Scraping,并为零成本的探索性读取保留像这样的本地视觉模型,在那里人类仍然检查结果,或者如果必须保持本地则转向更大的本地模型。
问:使用本地模型读取屏幕截图合法吗?
本地运行模型并不会改变页面本身的收集规则。仅捕获公共页面,尊重网站条款和由 机器人排除协议 标准化的机器人指令,并保持请求量受限,无论读取结果的模型在哪里运行。
在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。



