Skip to main content

Command Palette

Search for a command to run...

网页转 JSON API 怎么接:从 Prompt 到可校验的结构化数据

Updated
5 min readView as Markdown

把网页内容变成 JSON,真正困难的部分通常不是“得到一段看起来像 JSON 的文本”,而是让字段含义、数据类型、缺失值、来源和更新规则都能被程序稳定处理。

语义化获取站点 JSON 结构内容 API 可以根据网页 URL 和自然语言 Prompt 提取自定义结构。它适合商品列表、文章索引、表格内容、站点研究和自动化数据准备等场景,但接口返回成功并不代表所有字段都已经过事实验证。生产接入仍需要本地 Schema 校验、来源证据和版本控制。

先选对接口:固定文章结构还是自定义字段

GuGuData 同时提供文章抽取和网页转 JSON 两类能力,它们解决的问题不同。

需求 更适合的接口 原因
提取标题、正文、作者、摘要和发布时间 article-extract 返回相对固定的文章结构
从商品页提取名称、价格、库存状态 url2json 字段由 Prompt 按业务需要定义
从列表页提取多条链接和标签 url2json 可以描述列表范围和每项字段
把正文送入知识库 article-extract 正文识别比任意字段抽取更明确
对文章进一步提取人物、机构或事件 两步组合 先得到正文,再按下游模型或规则处理

不要只因为两个接口都返回 JSON 就把它们当成同一种能力。article-extract 的结构主要由接口定义;url2json 的结构更多由 Prompt 和目标页面共同决定。

请求前先把 Prompt 写成数据契约

“提取这个网页的重要信息”很容易得到可读结果,却不适合直接入库。一个可执行的 Prompt 至少应说明以下内容:

契约项 应写清楚的内容 示例
提取范围 从页面哪个区域、提取多少项 只提取产品列表中的前 20 项
字段名称 使用稳定、唯一的键名 namepriceproductUrl
字段类型 字符串、数字、布尔值、数组或对象 price 为字符串,保留货币符号
缺失值 页面没有字段时如何表示 使用 null,不要猜测
文本规则 是否去除空白、是否保留原文 标题去除首尾空白,不翻译
链接规则 相对链接如何处理 返回页面中可见的原始链接
输出外壳 单对象还是数组 返回 { "products": [] }

例如,商品列表页可以使用这样的 Prompt:

从页面的产品列表中提取最多 20 项,返回 JSON 对象:
{
  "products": [
    {
      "name": "string",
      "price": "string|null",
      "availability": "string|null",
      "productUrl": "string|null"
    }
  ]
}

要求:
1. 只使用页面明确出现的信息;
2. 缺失字段返回 null,不推断;
3. 保持页面原始语言;
4. 不添加上述结构之外的字段。

这里的 JSON 示例是对输出的自然语言约束,不等同于服务端强制执行的 JSON Schema。调用方仍要在收到响应后自行验证。

从 Prompt 到可入库 JSON 的数据流

网页转 JSON 的契约校验数据流

这条链路把“抽取成功”和“可以入库”明确分开:URL2JSON API 先根据公开网页与版本化 Prompt 生成候选 JSON,本地 Schema 再检查字段类型、必填项、数量上限和业务规则。通过校验的结果进入版本化数据集,失败结果进入隔离队列,不覆盖旧版本。缺失字段保持 null,而不是为了通过校验自动补猜测值。

调用中文接口

中文接口为:

POST https://api.gugudata.com/websitetools/url2json

参数包括 appkeyurlprompt。当前中文文档使用查询参数传递这些值,因此要注意反向代理、APM 和访问日志可能记录完整查询字符串。真实 AppKey 应只由受控服务端持有,并在日志层进行脱敏。

下面使用 Python 标准库发送请求,同时校验 HTTP 状态、JSON 格式、业务状态和 Data 类型:

import json
import os
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen


ENDPOINT = "https://api.gugudata.com/websitetools/url2json"


class UrlToJsonError(RuntimeError):
    def __init__(self, message: str, http_status: int | None = None):
        super().__init__(message)
        self.http_status = http_status


def extract_webpage(url: str, prompt: str) -> object:
    query = urlencode(
        {
            "appkey": os.environ["GUGUDATA_APPKEY"],
            "url": url,
            "prompt": prompt,
        }
    )
    request = Request(f"{ENDPOINT}?{query}", method="POST")

    try:
        with urlopen(request, timeout=60) as response:
            http_status = response.status
            payload = json.load(response)
    except HTTPError as exc:
        detail = exc.read().decode("utf-8", errors="replace")
        raise UrlToJsonError(detail or str(exc), exc.code) from exc
    except (URLError, TimeoutError, json.JSONDecodeError) as exc:
        raise UrlToJsonError(str(exc)) from exc

    if http_status != 200:
        raise UrlToJsonError("Unexpected HTTP status", http_status)

    data_status = payload.get("DataStatus", {})
    if int(data_status.get("StatusCode", 0)) != 100:
        description = data_status.get("StatusDescription", "Extraction failed")
        raise UrlToJsonError(description)

    data = payload.get("Data")
    if not isinstance(data, (dict, list)):
        raise UrlToJsonError("Data is not a JSON object or array")
    return data

调用时只传公开且有权处理的网页:

prompt = """
Extract at most 20 products as
{"products":[{"name":"string","price":"string|null"}]}.
Use null for missing fields and do not infer values.
""".strip()

data = extract_webpage("https://example.com/products", prompt)

不要在示例或日志中打印完整请求 URL,因为它包含 AppKey、目标 URL 和 Prompt。生产环境可以记录请求 ID、目标域名哈希、Prompt 版本、HTTP 状态和业务状态,但应避免记录凭证和完整采集内容。

英文接口的请求结构不同

英文接口为:

POST https://api.gugudata.io/v1/websitetools/url2json

当前 OpenAPI 契约要求 appkey 位于查询参数,urlprompt 位于 JSON 请求体:

curl -X POST "https://api.gugudata.io/v1/websitetools/url2json?appkey=YOUR_APPKEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/products",
    "prompt": "Extract product names and prices. Use null for missing values."
  }'

英文接口的响应包装字段使用小驼峰,例如 dataStatus.statusCodedata;中文接口使用 DataStatus.StatusCodeData。两套接口的成功码也不同,不能共用一条写死字段大小写和成功码的校验逻辑。

返回 JSON 后必须再做本地 Schema 校验

假设业务期望的数据结构是:

{
  "products": [
    {
      "name": "Example Product",
      "price": "¥99",
      "availability": null,
      "productUrl": "https://example.com/products/1"
    }
  ]
}

最低限度的校验应覆盖:

  1. 顶层必须是对象;
  2. products 必须是数组;
  3. 每一项必须是对象;
  4. name 必须是非空字符串;
  5. 可空字段只能是指定类型或 null
  6. 数量不能超过 Prompt 约定的上限;
  7. URL 字段只能接受业务允许的协议和域名范围;
  8. 未声明字段是拒绝、保留还是隔离,必须有明确策略。

不依赖第三方库时,可以先做一层窄校验:

from urllib.parse import urlparse


def validate_products(data: object, max_items: int = 20) -> list[dict]:
    if not isinstance(data, dict):
        raise ValueError("Expected a JSON object")

    products = data.get("products")
    if not isinstance(products, list):
        raise ValueError("products must be an array")
    if len(products) > max_items:
        raise ValueError("products exceeds the configured limit")

    validated = []
    for index, item in enumerate(products):
        if not isinstance(item, dict):
            raise ValueError(f"products[{index}] must be an object")

        name = item.get("name")
        if not isinstance(name, str) or not name.strip():
            raise ValueError(f"products[{index}].name is invalid")

        product_url = item.get("productUrl")
        if product_url is not None:
            if not isinstance(product_url, str):
                raise ValueError(f"products[{index}].productUrl is invalid")
            parsed = urlparse(product_url)
            if parsed.scheme not in {"http", "https"} or not parsed.hostname:
                raise ValueError(f"products[{index}].productUrl is invalid")

        validated.append(item)
    return validated

正式项目可以使用 JSON Schema、Pydantic、Zod 或团队现有的契约框架,但不要为了“提高成功率”而把必填字段、类型和数量上限全部放宽。验证失败的数据应进入隔离队列,而不是直接覆盖线上记录。

为 Prompt 和结果建立版本

网页结构、Prompt 和模型行为都会变化。只保存最终 JSON,会让后续很难回答“这个字段为什么变了”。建议至少记录:

字段 用途
sourceUrl 原始来源
canonicalUrl 规范化后的来源标识
promptVersion 生成该结果的 Prompt 版本
schemaVersion 本地校验规则版本
fetchedAt 本次采集时间
requestId 对应服务端请求,便于排查
contentHash 规范化结果哈希,用于变化检测
validationStatus passedrejectedneeds_review
rawPayloadRef 受控原始响应引用,而不是公开日志内容

更新时不要仅按 URL 执行覆盖。更稳妥的流程是:

  1. 获取新结果;
  2. 验证响应包装和业务状态;
  3. 执行本地 Schema 校验;
  4. 规范化字段并计算哈希;
  5. 与上一版本比较;
  6. 无变化时只更新检查时间;
  7. 有变化时创建新版本,并保留差异和来源;
  8. 关键字段变化进入人工复核或业务规则判断。

列表页需要稳定主键和去重策略

从网页提取多条记录时,标题通常不是稳定主键。商品可能改名,文章标题可能修订,同名记录也可能同时存在。

主键优先级可以设计为:

  1. 页面明确提供的业务 ID;
  2. 规范化后的详情页 URL;
  3. 多个稳定字段组成的复合键;
  4. 最后才考虑标题等易变文本。

去重还要区分三种情况:

情况 建议处理
主键相同、内容哈希相同 记录本次检查,不创建内容版本
主键相同、内容哈希不同 创建新版本并记录字段差异
没有稳定主键 标记 needs_review,不要自动合并

错误处理要分清 HTTP 与业务状态

中文接口在 JSON 包装中使用 DataStatus.StatusCode。当前本地事实文档列出的状态包括参数错误、频率限制、账号或 AppKey 问题、配额限制和提取失败。英文接口则通过 HTTP 400401403429500503 表达请求级状态,并在成功响应中提供 dataStatus

处理策略可以分为三类:

类型 示例 是否重试
请求不可恢复 参数缺失、URL 格式错误、Schema 不匹配 修正输入,不自动重试
权限或配额 AppKey 错误、无权限、配额耗尽 停止并核查账号状态
暂时性失败 频率限制、上游不可用、明确的服务端暂时错误 指数退避并设置总次数上限

网页提取失败不一定是 AI 服务故障,也可能是目标网页无法访问、只返回脚本壳、加载超时、页面要求登录,或者 Prompt 与页面内容不匹配。重试前应先分类原因,避免对同一不可恢复目标持续请求。

安全边界不能只依赖接口

调用方仍需负责自己的采集合规与数据安全:

  • 只处理公开且有权使用的页面;
  • 不绕过登录、验证码、付费墙或目标站点访问控制;
  • 不把 Cookie、Authorization、浏览器存储或客户会话交给提取链路;
  • 对目标域名、允许协议、重定向和结果中的 URL 再做业务侧校验;
  • 对 Prompt 做长度和模板限制,避免把不受控用户文本直接变成系统级指令;
  • 对结果中的 HTML、链接和富文本执行输出编码或净化;
  • 对个人信息、联系方式和敏感字段设置最小化采集与保留策略。

Prompt 注入也需要单独考虑。目标网页中的文字可能包含“忽略之前要求”等内容。调用方不应把网页输出直接当成下一阶段 Agent 的可信指令;提取结果只能作为数据,进入工具调用或自动决策前必须通过固定 Schema、权限和业务规则。

Demo 能证明什么,不能证明什么

2026 年 8 月 25 日(Asia/Shanghai)核验时:

  • 中文 Demo 返回 HTTP 200、DataStatus.StatusCode=100,并按固定 Prompt 返回 Data.products
  • 英文 Demo 返回 HTTP 200、dataStatus.statusCode=200status=SUCCESS,响应头明确标记为缓存 Demo;
  • 两个 Demo 都返回了非空结构化数据。

这些证据可以证明当前固定样例链路、包装字段和结果形状可读,但不能证明:

  • 任意第三方网页都能访问或成功渲染;
  • 任意 Prompt 都严格遵守字段与类型要求;
  • 生产 AppKey、配额、并发和长时间稳定性已经验收;
  • 提取字段与来源页面在事实层面完全一致;
  • 缓存 Demo 代表实时抓取结果。

正式上线前,应使用自有 AppKey、已授权测试网页和固定 Prompt 完成小范围冒烟,并人工对照来源页面核验关键字段。

上线前检查清单

  • 明确选择 url2json,而不是更适合固定文章结构的 article-extract
  • Prompt 写明范围、字段、类型、缺失值、数量上限和输出外壳
  • AppKey 只保存在服务端,不进入代码、前端或普通访问日志
  • 目标 URL 属于公开且有权处理的网页
  • 同时检查 HTTP 状态和对应域名的业务状态字段
  • Data 执行本地 Schema 和数量限制校验
  • Schema 失败结果进入隔离,不覆盖旧数据
  • 保存 Prompt 版本、Schema 版本、请求 ID、来源和采集时间
  • 使用稳定主键和内容哈希区分重复与更新
  • 对结果中的 URL、HTML 和敏感字段再次执行安全策略
  • 为 429 或暂时性错误设置有上限的退避,不重试参数和权限错误
  • 用真实 AppKey 和自有测试页完成生产冒烟,不把 Demo 当成生产验收

网页转 JSON 的价值,是让同一套采集链路适配不同页面和字段需求;它的工程边界,是输出结构仍然需要调用方验证。把 Prompt 当成可版本化的数据契约,把 Schema 校验、来源证据和失败隔离放在入库之前,才能让“能提取”变成“可长期维护”。

More from this blog

地址逆编码接口 API

此文章对开放数据接口 API 之「地址逆编码接口 API」进行了功能介绍、使用场景介绍以及调用方法的说明,供用户在使用数据接口时参考之用,并且在目前更新的微信小程序实战开发项目中的使用场景。 1. 产品功能 此次开放了精准的地址坐标逆编码在线接口,用于对提供的 GPS 坐标转换为文字地址信息。 提供精准、高效的地理坐标逆编码接口; 返回的地址包含详细的位置信息; 一次可返回坐标周边的 10

Aug 3, 20261 min read

中英文排版规范化 API

此文章对开放数据接口 API 之「中英文排版规范化 API」进行了功能介绍、使用场景介绍以及调用方法的说明,供用户在使用数据接口时参考之用。 1. 产品功能 此次开放了中英文排版规范化在线接口,用于自动中英文排版、标点符号格式化,中英混排格式化 / 标点修正。 支持中英文混排格式化; 自动在汉字与英文字符、英文标点、数字间添加空格; 中文标点符号自动规范化,遵从 [标点符号用法 GB/T

Aug 3, 20261 min read

为阿里云站点部署免费 HTTPS

本文记录了部署在阿里云的站点,在申请了免费的 SSL 证书后如何正确的部署到站点上,让站点支持 HTTPS 访问。 阿里云引入了沃通作为 CA 证书供应商,并开放了免费 SSL 申请的页面,之前一直想给 咕咕监控 部署上全站 HTTPS,所以就申请了一个,但是部署的过程中遇到了些问题,所以记录下来备忘。 1. 证书申请 在阿里云后台的 CA 管理页面,填写相关的信息后就可以申请到一张免费的 CA

Aug 3, 20261 min read

GuGuData

178 posts