空白的页面捕获本应导致提取失败。但在我们的测试中,它却产出了一套售价 $149、评分 4.5 星、拥有 120 条评论的 Mainstays 5-Piece Dining Set(五件套餐桌椅)。而那个 Walmart URL 实际销售的是一款 $59 的电动多功能锅。模型凭空生成的记录通过了我们的 JSON Schema,而内置的 Walmart 爬取器在三次运行中都返回了那款多功能锅。

这个例子揭示了结构化数据提取的一个问题:格式正确的 JSON 仍可能描述错误的商品。为了量化这种差异,我们在 25 个 Amazon 和 25 个 Walmart 商品页面上测试了三条路径:具名 Crawlbase 爬取器、autoparse=true,以及先获取 Markdown 再以严格 JSON Schema 调用 GPT-4o-mini。每条路径各运行三次。我们测量了字段覆盖率、可重复性、捕获文本中不存在的值、延迟,以及请求数和模型 token 数。

配套仓库 包含实现代码、九个演示脚本、离线测试数据和测试用例。其中的 URL 列表 定义了候选页面和备用页面,而 记录的结果 保留了 2026年11月2日那几次运行的测量数据。

要点速览
  • 两条解析器路径在 300 次尝试中有 297 次通过了应用模式校验,且不消耗任何模型 token。Markdown 加 GPT-4o-mini 路径在 150 次中有 130 次被接受。
  • 严格的结构化输出消除的是解析错误,而不是凭空编造。模型对收到的每一个机器人验证页和空白正文都返回了完整对象,其中一个对象还通过了模式校验。
  • 先尝试持续维护的解析器,归一化,按你自己的契约验证,只对未通过的记录调用模型。仓库中的路由器正是这样做的。

选择页面变成 JSON 的方式

Crawling API 同时支持页面获取和持续维护的提取。在 Amazon 爬取器 API 工作流中,发送 scraper=amazon-product-details 即可返回商品 JSON。Walmart 使用 scraper=walmart-product-details。使用 autoparse=true,API 会检测受支持的页面类型并应用对应的爬取器。

初步探测显示,通过 autoparse 和具名爬取器返回的是同一个商品对象。当没有受支持的爬取器匹配时,Crawlbase 会回退到 generic-extractor,它返回的是通用页面数据。这些仍然是彼此独立的请求:后面的基准测试包含了返回值和超时方面的差异。确定性提取描述的是解析方式;它并不能让实时的零售商页面或每一次响应保持不变。

两条路径都从同一个 Crawling API 端点出发。只有其中一条会在每个页面上都调用模型。

LLM 路径先用 format=md,然后将 Markdown、商品 URL、指令和一个八字段模式发送给 OpenAI。在未命中缓存的成功尝试中,这会在抓取之外增加一次模型调用。AI 数据提取概览 解释了这种先获取再提取的模式;在这里,我们将它与使用同一数据提取 API 进行获取的持续维护商品解析器进行比较。

我们还在 Amazon 上探测了同时发送 scraper= 和 format=md 的情况。响应中只包含爬取器 JSON。因此基准测试分别获取解析数据和 Markdown。这一点在解读其证据检查时很重要:模型是对照它自己收到的捕获输入进行检查,而每个解析器则是对照同一 URL 的另一次抓取进行检查。

先定义一种记录,再比较提取器

三条路径的输出需要有一个共同的表示形式才能相互比较。共享模式和映射 定义了八个字段:title、id、brand、price、currency、availability、rating,以及 reviews_count。

实际的爬取器响应使用了不同的键:

共享字段 Amazon 响应 Walmart 响应
title name title
id ASIN 取自 productInformation,映射器以 URL 作为回退 itemId
brand brand brand
price rawPrice,回退到 price price
currency currency currency
availability inStock 记录的响应中没有库存字段
rating customerReview ratings
reviews_count customerReviewCount reviewsCount

这些实际观察到的键与开发期间文档中的名称不同,因此映射遵循记录下来的载荷。在没有提供库存字段时,Walmart 的可用性保持为 null。如果响应中出现 outOfStock 或 availability,映射器也会处理。

LLM 模式提取使用单独的输出契约。每个键都必须存在,但每个值都可以是 JSON null。提示词要求只使用页面上印出的值,每个字段的描述都告诉模型在值缺失时返回 null:

python
MAX_MARKDOWN_CHARS = 60_000

SYSTEM_PROMPT = (
    "You extract one product record from a retail product page converted to Markdown. "
    "Use only values printed on the page. If a value is not on the page, return null. "
    "Do not guess, convert, or fill in values from general knowledge."
)

LLM 客户端 以温度 0 请求严格结构化输出,并发送 Markdown 的前 60,000 个字符:

python
response = self.client.chat.completions.create(
    model=self.model,
    temperature=0,
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": f"URL: {url}\n\n{page}"},
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {"name": "product_v1", "strict": True, "schema": LLM_SCHEMA},
    },
)

提取之后,应用会对结果进行归一化,并按 product.v1 进行验证。该接受模式要求标题和 ID 至少三个字符、价格和计数为非负数、评分在 0 到 5 之间、库存值为允许值或 null。数值型价格必须配有由三个大写字母组成的币种字符串:

python
PRODUCT_SCHEMA = {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": SCHEMA_VERSION,
    "type": "object",
    "required": FIELDS + ["_meta"],
    "additionalProperties": False,
    "properties": {
        "title": {"type": "string", "minLength": 3},
        "id": {"type": "string", "minLength": 3},
        "brand": {"type": ["string", "null"]},
        "price": {"type": ["number", "null"], "minimum": 0},
        "currency": {"type": ["string", "null"], "pattern": "^[A-Z]{3}$"},
        "availability": {"enum": AVAILABILITY + [None]},
        "rating": {"type": ["number", "null"], "minimum": 0, "maximum": 5},
        "reviews_count": {"type": ["integer", "null"], "minimum": 0},
        "_meta": {
            "type": "object",
            "required": ["url", "path", "extractor", "schema_version"],
            "properties": {
                "url": {"type": "string"},
                "path": {"enum": ["scraper", "autoparse", "markdown_llm"]},
                "extractor": {"type": "string"},
                "schema_version": {"const": SCHEMA_VERSION},
            },
        },
    },
    "allOf": [
        {"if": {"properties": {"price": {"type": "number"}}, "required": ["price"]},
         "then": {"properties": {"currency": {"type": "string"}}}},
    ],
}

代码会归一化常见的货币符号,但仅凭这个模式并不能验证其是否属于 ISO 4217 货币列表。这两个模式之间的区别解释了结果:模型可以返回一个完整、结构上有效但可为空的对象,而应用随后会因为标题缺失而拒绝它。

在三次运行中比较 50 个页面

基准测试框架 从 25 个 Amazon /dp/ URL、25 个 Walmart /ip/ URL 和备用页面开始。每个入选 URL 的首次运行 Markdown 必须超过 1,500 个字符并包含其商品 ID。这样在测量三条路径之前就应用了同样的准入规则,不过这也意味着数据集一开始就只包含产出了可用 Markdown 的页面。

选择过程替换了 28 个候选页面。Amazon 贡献了七个:六个 404 页面和一个页面上未显示 ASIN 的下架商品。Walmart 贡献了 21 个:六个机器人验证页、十个同意页面、两个空白正文、两个未找到页面,以及一个未通过准入规则的页面。五个 Walmart 候选页面和全部 Walmart 备用页面来自同一份 walmart-serp 对“kitchen essentials”的自然搜索结果列表。

三次运行于 2026年11月2日背靠背进行。发布的矩阵包含 450 条路径结果:50 个 URL 乘以三条路径再乘以三次运行。在这些保留的结果中,412 条使用了 Normal token,38 条在重试后使用了 JavaScript token。这些总数描述的是记录下来的结果;URL 选择请求和额外的重试尝试还会增加底层的网络工作量。

Python 依赖文件 将 Crawlbase SDK 固定在 1.1.0。记录的 LLM 调用请求的是 gpt-4o-mini,返回的是 gpt-4o-mini-2024-07-18。

抓取客户端 设置了 120 秒的客户端超时。配置了 JavaScript token 时,它允许在 cb_status 为 207 或 503、或成功响应中包含可识别的验证挑战、Markdown 正文几乎为空、或解析器结果没有标题的情况下切换一次。原始的 404 和 410 响应不参与该重试。传输异常会被记录为失败,而不是由这个客户端重试:

python
TIMEOUT_S = 120
RETRY_ON_JS = {"207", "503"}
# A 200 from the API can still carry an interstitial instead of the product page.
CHALLENGE_MARKERS = ("robot or human", "health information consent", "enter the characters you see below")
MIN_MARKDOWN_CHARS = 40


def unusable(result: "CrawlResult") -> bool:
    """Same rule for every path: no product content in an otherwise successful response."""
    if result.cb_status != "200" or result.error:
        return False
    if result.path == "markdown":
        text = (result.markdown or "").lower()
        return len(text) < MIN_MARKDOWN_CHARS or any(m in text for m in CHALLENGE_MARKERS)
    body = result.data.get("body") if isinstance((result.data or {}).get("body"), dict) else result.data
    return not (isinstance(body, dict) and (body.get("name") or body.get("title")))

指标模块 测量几种不同的属性。填充率是八个字段的平均非空覆盖率。未产出记录的尝试贡献零个已填充字段;被应用模式拒绝的记录仍然贡献其非空字段。一致性要求归一化后的八字段记录在三次运行中完全相同;元数据不计入。模式接受数统计通过 product.v1 的记录,延迟涵盖抓取耗时(发生 JavaScript 重试时为两次尝试之和),LLM 路径还要加上模型耗时。

在页面文本检查中,LLM 的值与它收到的 Markdown 前缀进行比对,URL 也可作为商品 ID 的证据。解析器的值与该次运行单独抓取的 Markdown 进行比对;当该次捕获不符合商品页面规则时跳过检查。基准测试刻意禁用了路由器的 Markdown 内容门槛,以便测量模型面对机器人验证页和空白页面时的表现。

解读测量结果

测量项 内置爬取器 autoparse Markdown + gpt-4o-mini
平均填充率,Amazon 88.3% 89.0% 90.5%
平均填充率,Walmart 83.0% 86.5% 74.2%
3 次运行均一致,Amazon 12/25 13/25 12/25
3 次运行均一致,Walmart 19/25 17/25 2/25
页面文本中不存在的值,Amazon 36/522 26/526 10/543
页面文本中不存在的值,Walmart 6/353 10/374 21/445
通过模式校验的记录 147/150 150/150 130/150
每页中位延迟 13.9 秒 15.6 秒 13.4 秒
模型 token 数 0 0 1,803,338
按域名统计的记录结果。Walmart 的一致性柱是三条路径拉开差距的地方。

两条解析器路径在 300 次尝试中有 297 次通过了模式校验,且没有消耗模型 token。具名爬取器的三次失败全部是触发客户端超时的 Walmart 调用。LLM 在每次尝试中都返回了对象,但归一化和应用验证只接受了 150 条记录中的 130 条。

Walmart 显示出最大的差距。在其 75 次 Markdown 捕获中,有 21 次包含机器人验证页、同意页面或空白正文。这 21 次全部出现在第二和第三次运行的 50 次捕获中,也就是在首轮选择已经接纳这些 URL 之后。模型仍然为每一次都生成了对象。其中十五个标题是字符串 "null",另有 16 个响应在没有库存文本支持的情况下给出了 in_stock。引言中那条凭空生成的餐桌椅套装记录,是唯一一次在归一化后仍然通过应用模式校验的 Walmart 非商品捕获。

Amazon 需要换一种解读。LLM 的字段覆盖率略高,其页面文本检查标记的值也比两个解析器都少。在具名爬取器被标记的 36 个值中,有 27 个在模型读取同一次运行的 Markdown 时为该字段返回了 null。记录的捕获中包含没有购买框的列表式布局,因此部分差异反映的是对同一 URL 的不同抓取返回了不同内容。

对每条解析器路径而言,价格、评分和评论数在 11 或 12 个 Amazon URL 上跨运行发生了变化;模型路径则是 7 到 11 个 URL。模型标题在五个 Amazon URL 上发生了变化;解析器标题则没有变化。这些观察说明了为什么一致性必须结合捕获到的输入来解读。没有任何 URL 的完整 Markdown 在三次运行中逐字节相同,因此这次实验没能把模型的随机性与页面本身的变化区分开。在每次运行都拿到商品页面 Markdown 的九个 Walmart URL 中,LLM 记录有两个完全一致,具名爬取器记录有七个。

LLM 在 Walmart 上较低的延迟也包含了短小的中间页和空白输入。失败输入处理得更快,并不能证明成功的商品提取更快。同样,报告的中位数是按尝试计算的,而不是每条被接受记录的耗时或整批的吞吐量。

核实返回值的真实含义

页面文本检查器 为每个值寻找归一化后的证据。它既能捕捉模型的凭空编造,也能捕捉解析器的偏差。在某个 Walmart 页面上,具名爬取器把 reviewsCount 返回为 "122",而页面显示的是 12,292;autoparse 返回的是 "12292"。在另一个页面上,两条路径的结果正好对调。仅看字段覆盖率,这个错误的计数会被算作已填充。

python
def is_supported(field: str, value, text: str) -> bool:
    if value is None:
        return True
    if field in ("title", "brand", "id"):
        return _has_text(text, str(value))
    if field == "price":
        return _has_number(text, float(value))
    if field == "reviews_count":
        return _has_number(text, float(value)) or _has_abbreviated(text, float(value))
    if field == "rating":
        return _has_rating(text, float(value))
    if field == "currency":
        return any(cue in text for cue in CURRENCY_CUES.get(value, (value.lower(),)))
    if field == "availability":
        cues = IN_STOCK_CUES if value == "in_stock" else OUT_OF_STOCK_CUES
        return any(cue in text for cue in cues)
    return True


def invented_fields(record: dict, markdown: str, fields: list[str], url: str = "") -> list[str]:
    """The URL counts as input: an id taken from it is not invented."""
    text = page_text(markdown) + " " + url.lower()
    return [f for f in fields if record.get(f) is not None and not is_supported(f, record[f], text)]

对三个 Amazon 和两个 Walmart 页面的人工核查带来了四项改进。缩写计数按其显示精度接受:27,406 匹配“27.4K”,14,084 匹配“14K”。文本匹配忽略空格,使“HeroCosmetics”可以匹配“Hero Cosmetics”。Walmart 的“Pickup as soon as 3pm”算作有货提示。所有路径都不对 Walmart 评分做证据检查,因为捕获的 Markdown 显示的是其他商品的评分,而不是该商品自己的评分。

这一排除并没有把评分从填充率计算中去掉,也没有从用作证据率分母的非空总数中去掉。因此,报告中的计数和图表百分比应被理解为这套测试框架的文本支持度测量,而不是对一组完全相同检查字段的准确率评分。

两处人工核查出的分歧说明了仍然存在的局限。检查器在只有开箱商品缺货时接受了 out_of_stock,还因为页面其他地方出现了一个无关的“30”而接受了 30 条评论的计数。文本存在只能说明某个值出现在输入的某处;要把它与正确的商品、报价和字段关联起来,还需要上下文。

严格输出解决的是另一个问题。150 次模型调用没有产生任何 JSON 解析错误或不完整的对象,但严格的结构并不能阻止没有依据的库存值或凭空生成的商品。要求每个键都存在,仍然允许诚实的 JSON null。失败发生在模型在没有证据的情况下填入了字段,而不是因为模式要求非空答案。

对于相关的模式驱动工作流,Firecrawl Extract 提供了一个提取接口,其文档现在把 /agent 作为它的继任者来介绍。其 JSON 模式指南 建议显式处理 null、提示词聚焦、字段数达到约 30 个或更多时拆小模式,并移除数组的 minItems 和 maxItems 约束,因为它们并不能控制采集多少内容。本文测量的 LLM 路径是在 Crawlbase Markdown 之上直接调用 OpenAI。

这些问题报告也说明了为什么输出失败需要单独诊断。Firecrawl 的问题 #1836 和 #3300 涉及 Zod 到 JSON Schema 的转换处理,导致数据为空或缺失。问题 #2491 涉及一个未生成任何对象的自托管模型集成。这些例子描述的是模式和集成失败;它们既没有测量幻觉频率,也不能证明严格模式会修复这些问题。

先归一化并验证,再回退

一旦失败模式清晰可见,路由规则就很直接:先尝试受支持的解析器,归一化其输出,并且只有在满足应用契约时才接受。归一化器 会把 "null"、"none",以及 "n/a" 之类的占位字符串转换为 null,解析价格和缩写计数,清理品牌标签,并将库存文本映射为 in_stock、out_of_stock,或 null。

把 "null" 标题转换为真正的 null 值,使 15 个虚假标题变成了应用模式拒绝。归一化器还会在价格缺失时清除币种。它支持的数字格式覆盖了本实验中的例子,包括 $1,299.99 和 €39,90;其他地区的格式需要显式的解析规则。

第一条被接受的记录胜出。被拒绝的记录会落到下一条路径,只有当没有任何解析器通过时才会运行模型。

打包好的路由器 返回第一个被接受的结果:

python
def route(url: str, crawl: CrawlClient, llm: LLMExtractor, run: int = 1) -> Extraction:
    """First accepted record wins. A rejected record falls through to the next path."""
    attempts = []
    if scraper_for(url):
        attempts.append(lambda: via_scraper(url, crawl, run))
    attempts.append(lambda: via_autoparse(url, crawl, run))
    attempts.append(lambda: via_markdown_llm(url, crawl, llm, run))
    result = None
    for attempt in attempts:
        result = attempt()
        if result.accepted:
            return result
    return result

每个 via_* 函数都返回一个 Extraction 对象。提取成功后依次进行归一化、元数据标记和 JSON Schema 验证。在这里,accepted 表示记录存在且没有验证错误。如果所有路径都失败,路由器会返回最后一个被拒绝的结果,而 write_record() 不会写入任何内容。

在调用模型之前,via_markdown_llm 会拒绝短于 40 个字符或包含三种可识别验证挑战短语之一的 Markdown。对这些情况它返回 not_a_product_page:

python
def via_markdown_llm(url: str, crawl: CrawlClient, llm: LLMExtractor, run: int = 1, gate: bool = True) -> Extraction:
    res = crawl.fetch(url, "markdown", run)
    extractor = f"openai:{llm.model}"
    if not res.ok:
        return Extraction(url, "markdown_llm", extractor, None, [res.error or f"cb_status {res.cb_status}"], res.to_dict())
    if gate and unusable(res):
        # A 200 that carries a challenge or an empty body never reaches the model.
        return Extraction(url, "markdown_llm", extractor, None, ["not_a_product_page"], res.to_dict())
    out = llm.extract(url, res.markdown, run)
    if not out.ok:
        return Extraction(url, "markdown_llm", extractor, None, [out.error], res.to_dict(), out.to_dict())
    return _finish(url, "markdown_llm", f"openai:{out.model or llm.model}", out.data, res.to_dict(), out.to_dict())

应用到记录下来的 Walmart 捕获上,这个门槛本可以把全部 21 个不可用页面挡在模型之外。它是一个有针对性的内容检查,而不是能识别每一种中间页或错误页面响应的完整检测器。

路由器的接受决策不会运行基准测试的页面文本检查器。当一个模式有效但实际错误的价格或库存值会影响下游决策时,请在你自己的接受策略中加入基于上下文的证据检查。这样可以让结构验证和事实核查都成为明确的实现步骤。

被接受的记录包含 _meta.url、_meta.path、_meta.extractor,以及 _meta.schema_version。示例包括 crawlbase:amazon-product-details、openai:gpt-4o-mini-2024-07-18,以及 product.v1。这些标识了路径以及模型或爬取器名称;仅凭爬取器名称并不能确定具体的解析器版本。

演示脚本分别展示了单独的 内置爬取器调用, autoparse 调用, LLM 提取,以及 三次运行比较.

Crawlbase Crawling API

一个端点覆盖整条路径:持续维护的商品爬取器、autoparse,或供你自己的模型使用的干净 Markdown,页面需要浏览器时还可使用 JavaScript token。失败的请求不计费。最多 5,000 次免费请求起步,无需信用卡。

Token 用量与缓存

磁盘缓存 使得无需重复获取就能反复分析。抓取条目以路径、运行编号和 URL 为键。模型条目包含请求的模型、运行编号、URL 和完整 Markdown 的哈希。两种缓存都不会自动过期,模型键也不包含提示词或模式。在测量修改后的提取契约或获取新页面之前,请先清除相关条目。

记录的结果是从保存的响应重新计算得出的。该次调用元数据中的零次 API 调用描述的是回放,而不是原始实验。重新计算这些测量值需要保存的响应缓存;仅凭发布的指标文件并不包含原始捕获。

输入大小仍然是一项独立的扩展成本。150 次模型调用消耗了 1,793,923 个提示 token 和 9,415 个补全 token,合计 1,803,338 个。Amazon 平均每次调用 22,131 个 token;Walmart 平均 1,914 个。75 次 Amazon 调用中有四十九次超过了 60,000 字符的 Markdown 上限并被截断。提高上限会发送更多上下文;降低上限则有可能丢掉页面末尾附近的字段。

仅示意形状。两条线都随页面数增长;模型那条线还随每页 token 数增长。

该图仅为示意。在平均输入大小固定的情况下,两种方式都随页面数线性增长;模型路径额外增加了逐页的 token 处理。它更陡的斜线并不代表测得的价格或速度比。基准测试统计的是逻辑抓取尝试和模型 token,而实际获取成本还取决于重试、域名复杂度、渲染和存储。

Firecrawl 的 v2.11.0 版本发布说明 描述了另一种减少重复模型工作的方式:deterministicJson 会生成一个可复用的提取器并按站点缓存。这一机制与跨重复页面摊销提取工作的工程目标相同,但它不是本实验测量的路径。

排队采集并按 RID 取回结果

对于更大的批次,获取步骤可以独立于提交 URL 的进程完成。异步演示脚本 发送 async=true&store=true,立即收到一个请求 ID,稍后再从 Cloud Storage:

python
def submit(api, url: str) -> str | None:
    from urllib.parse import urlparse

    options = {"async": "true", "store": "true"}
    name = SCRAPERS.get(urlparse(url).hostname or "")
    options.update({"scraper": name} if name else {"autoparse": "true"})
    res = api.get(url, options)
    payload = res.get("json")
    if not isinstance(payload, dict):
        try:
            payload = json.loads(res.get("body") or b"{}")
        except ValueError:
            payload = {}
    return payload.get("rid")  # the SDK puts the async rid in res["json"], not res["rid"]


def fetch_stored(storage, rid: str, attempts: int = 12, wait_s: float = 10.0) -> dict | None:
    for _ in range(attempts):
        try:
            res = storage.get(rid, {"format": "json"})
        except KeyError:  # crawlbase 1.1.0 raises this while the rid is not stored yet
            res = {}
        if res.get("status_code") == 200:
            stored = res.get("json_body")
            if isinstance(stored, dict) and stored:
                return stored
            body = res.get("body") or b""
            if body:
                return {"body": body.decode("utf-8", "replace") if isinstance(body, bytes) else body}
        time.sleep(wait_s)
    return None

在 SDK 1.1.0 中,异步 ID 位于 res["json"]["rid"]。演示脚本按 RID 轮询,并把 SDK 对尚未就绪条目抛出的 KeyError 视为待处理结果。在两个 URL 的测试中,Amazon 的结果在两分钟内即可读取。Walmart 在前两次检查时不存在,在之后的一次检查中出现。这些是记录下来的完成情况观察,而不是有保证的交付时间间隔。

批处理辅助函数 也接受回调 URL。Crawlbase 会把完成的结果 POST 到该端点,同时 store=true 保留一份副本供取回。Zapier 回调演示 展示了这种交付模式。异步示例会映射并归一化取回的载荷;在把它们作为被接受的商品记录持久化之前,请应用同样的应用验证策略。

存储一个页面会在获取它的那次抓取之上增加 0.5 积分,如 定价页 所列,页面保留 30 天。取回免费。请把存储连同抓取和模型工作一起纳入预算。

计费还取决于响应状态。Crawling API 参考文档 列出的计费请求是 cb_status 为 200 或 207 且 original_status 为 200、201、301、302、303、307、308、404 或 410 的请求。可识别的 503 拦截不计费。本实验中的机器人验证响应是以成功的 200 到达并照此计费的,这就是为什么即使检查了状态码,仍然需要检查正文。

API 文档建议对超过几百个 URL 的批次使用异步交付,对数百万 URL 这样的大规模任务使用 Crawler。对于定制的吞吐量和服务要求,企业版方案 提供了下一步的运营选项。

在解读能带来价值的地方使用模型

对于受支持的商品页面,持续维护的解析器是一个实用的起点。它省去了逐页的模型调用,并暴露一组已知的字段。实验也显示了权衡:通过所测试的解析器无法获得 Walmart 的库存,错误的评论数仍然需要核查。请根据你的应用真正需要的字段来选择提取路径。

模型适用于没有匹配解析器的商店、论坛帖子和新闻稿之类的不规则文档,或者构建专用提取器的成本高于模型调用的一次性研究。这些文档需要自己的模式和映射;本路由器实现的是一个商品专用契约。ChatGPT, Gemini,以及 Perplexity 教程提供了相关的先获取再解读的实现,而 Amazon 价格提取教程 把这一模式应用于零售数据。

模型也可以在确定性提取之后发挥作用。一条商品记录可以支持按自定义分类法进行分类,或在 Walmart 与 Amazon 之间做候选匹配。投诉标记需要评论文本,因此也请从合适的爬取器或捕获中提供这些内容。每个任务都可以只拿到它需要的字段,而不是整个页面。

对于周期性的 价格情报,先采集并归一化价格,再在需要的地方加入模型解读。Amazon 定时爬取指南 介绍了周期性获取。让获取、字段提取、验证和解读各自保持明确,可以在某个值发生变化时更容易检查网页抓取到 JSON 的整个过程。

结论

在这 50 个商品页面上,持续维护的爬取器路径在 300 次尝试中产出了 297 条通过模式校验的记录,且不消耗模型 token。Markdown 加 GPT-4o-mini 在 Amazon 上提供了更高的字段覆盖率,但 Walmart 的中间页暴露了从不可用输入中接受看似合理记录的风险。记录的结果支持这样的做法:先从可用的解析器开始,核查返回的字段,把模型提取留给你的应用需要填补的空缺。

完整实现 提供了映射、路由器、基准测试和离线示例。要在你自己的商品目录上试用,请 创建一个 Crawlbase 账户,配置令牌,并在扩大运行规模之前先检查一小批被接受的记录。Amazon 爬取器, Walmart 爬取器,以及更全面的 电商爬取概览 可以帮助你为目标站点确定获取路径。

常见问题(FAQ)

哪些网站有 Crawlbase 内置爬取器?

爬取器索引 列出了 Amazon、Walmart、eBay、AliExpress、Best Buy、Google、Bing、Facebook、Instagram、TikTok、LinkedIn、Reddit、Booking.com、Airbnb 等站点的受支持提取器,以及通用提取器。按页面类型选择爬取器,并通过 scraper= 发送其名称。示例中的域名路由器只映射 Amazon 和 Walmart 商品页面;请为其他目标进行扩展。

autoparse 对任何页面都有效吗?

使用 autoparse=true 时,Crawlbase 会检测页面类型并应用匹配的受支持爬取器。当没有受支持的爬取器匹配时,它会回退到 generic-extractor,后者返回的是通用页面数据,而不是零售商专属的商品记录。在本仓库中,应用会把返回的 JSON 映射到其商品模式并进行验证。如果这一过程没有产出可接受的商品记录,应用会单独发起一次 Markdown 请求并尝试 LLM 提取。这一额外回退属于应用自身的路由逻辑。

内置爬取器多久会失效一次,由谁维护?

Crawlbase 负责维护其内置爬取器,并在网站变更影响提取时进行更新。失效会被自动上报,通常在数小时内修复。如果你发现字段缺失、值不正确或问题持续存在,请联系 Crawlbase 支持团队,并附上受影响的 URL 和示例响应,以便团队调查。

JSON Schema 验证能保证提取数据准确吗?

不能。JSON Schema 检查的是记录的结构、必填字段和值约束。它不验证这些值是否描述了正确的商品。请先确认捕获的页面包含可用的商品内容,再对照与正确商品和报价相关联的证据核实重要的值。

开始构建

大规模爬取任何站点,无需与基础设施对抗。

Crawlbase 负责处理代理、指纹和验证码,让您的团队专注于构建数据管道,而不是维护爬取基础设施。免费提供最多 5,000 次请求,无需绑定信用卡。

自助开通 · 无需销售通话 · 提供企业级爬取量