最小可运行示例:一个 GET 请求查询 ICP 备案信息
2026/8/7 16:51:16 网站建设 项目流程

适用场景

ICP 备案查询是一个非常高频的开发诉求。常见的落地场景包括:

  • 域名准入检查:在内容发布、广告投放或用户提交外链之前,先判断目标域名是否完成备案,从源头规避因未备案域名导致的业务风险。
  • 运营数据清洗:批量筛选已备案域名用于活动报名或开发者认证,避免人工逐一核对。
  • 企业内部系统集成:在 CMS 或工单系统中增加备案信息自动回填,减少运营人员在工信部站点手动检索的时间。
  • 安全巡检与资产管理:定期扫描公司域名列表中备案主体的变更,及时发现备案被注销或主体不一致的问题。

以上场景都有一个共同点:调用方只关心「这个域名有没有备案」「备案主体是谁」,并不需要理解工信部备案系统内部复杂的查询逻辑。这正好是 ICP 备案查询 API 的设计边界所在。

接口能力边界

在写第一行代码之前,先明确接口能做什么、不能做什么,能避免很多认知偏差。

接口本质上是「域名 → 备案信息」的映射查询,它具备三个值得注意的能力:

  • 自动域名清洗:接口接收的不一定是纯域名。传入https://www.baidu.com/abcm.baidu.com:8080/foobaidu.com,接口都会自动剥离协议、路径、端口和www.前缀,统一识别为baidu.com。这省去了调用方自行做 URL 解析的代码。
  • 已备案与未备案的语义区分:已备案域名返回is_filed=true以及完整的 6 个字段;未备案、境外域名或备案已注销的域名返回is_filed=false且字段为空。注意:未备案不是错误,而是正常的业务响应,因此不需要用 try/catch 包裹业务判断。
  • 缓存策略:已备案数据缓存 24 小时,未备案数据缓存 1 小时。原因是备案状态本身变更频率低,而新备案通过审核后有尽快被查到的需求。这个缓存设计意味着你查询到的结果不是绝对的实时状态,但用于业务判断已经足够。

请求参数与鉴权

本次调用的信息如下:

项目
请求方法GET
请求地址https://v1.apizero.cn/api/icp
Query 参数domain(必填)
Header 参数Authorization(可选)
分类开发工具
推荐 QPS5 / s

Query 参数

domain是唯一必填参数,类型为字符串。它的宽容度很高,支持完整 URL 输入,接口会自动清洗。换句话说,下面三种写法在语义上是等价的:

baidu.com https://www.baidu.com/abc m.baidu.com:8080/foo

Header 鉴权参数

Authorization是可选的鉴权头,格式为Bearer sk_live_xxx。匿名调用时可以省略该参数,但会受每日调用额度的限制;当业务量较大或对稳定性有要求时,建议配置 API Key 后再调用。

最小可运行示例:curl 一行接入

最小可运行示例的核心目标只有一个:用最少的代码拿到有效响应。curl 是这个目标最直接的体现。

把下面的命令复制到终端,将$APIZERO_API_KEY替换成你的真实 Key,或者直接去掉-H行做匿名调用:

curl -sS \ -X GET \ -H "X-API-Key: $APIZERO_API_KEY" \ "https://v1.apizero.cn/api/icp?domain=baidu.com"

注意:上述命令中的X-API-Key是素材中 curl 示例使用的鉴权头。如果你使用文档最新推荐的Authorization: Bearer方式,则改成:

curl -sS \ -X GET \ -H "Authorization: Bearer $APIZERO_API_KEY" \ "https://v1.apizero.cn/api/icp?domain=baidu.com"

两者具体以官方文档的鉴权说明为准。建议先跑通第一个 curl,再进入代码封装阶段。

从命令行走向代码:Python 与 JavaScript 示例

curl 用来验证连通性很高效,但业务系统最终还是要落到代码里。这里给出 Python 和 Node.js 两个最小可运行版本。

Python 示例

使用标准库urllib.request,不依赖任何第三方库:

import json import urllib.parse import urllib.request API_URL = "https://v1.apizero.cn/api/icp" DOMAIN = "baidu.com" params = urllib.parse.urlencode({"domain": DOMAIN}) url = f"{API_URL}?{params}" req = urllib.request.Request( url, headers={ # 匿名调用时移除这一行 "Authorization": "Bearer sk_live_xxxxxxxxxxxxxx", "Accept": "application/json", }, ) with urllib.request.urlopen(req, timeout=5) as resp: payload = json.load(resp) if payload.get("code") == 0: data = payload.get("data", {}) if data.get("is_filed"): print(f"{data['domain']} 已备案") print(f"备案号: {data['icp_code']}") print(f"主办单位: {data['company_name']}") print(f"单位性质: {data['company_type']}") print(f"网站名称: {data['site_name']}") print(f"审核时间: {data['audit_time']}") else: print(f"{DOMAIN} 未备案、已注销或为境外域名") else: print(f"业务异常: code={payload.get('code')}, msg={payload.get('msg')}")

判断逻辑非常直观:先检查业务码code,再检查is_filed。这种两级判断避免了把「未备案」当成「接口异常」。

JavaScript 示例

在 Node.js 18+ 环境中,可以直接使用全局fetch

const apiUrl = "https://v1.apizero.cn/api/icp"; const domain = "baidu.com"; const url = new URL(apiUrl); url.searchParams.set("domain", domain); const res = await fetch(url, { headers: { // 匿名调用时移除这一行 Authorization: "Bearer sk_live_xxxxxxxxxxxxxx", Accept: "application/json", }, }); const payload = await res.json(); if (payload.code === 0) { const data = payload.data; if (data.is_filed) { console.log(`${data.domain} 已备案`); console.log(`备案号: ${data.icp_code}`); console.log(`主办单位: ${data.company_name}`); console.log(`单位性质: ${data.company_type}`); console.log(`网站名称: ${data.site_name}`); console.log(`审核时间: ${data.audit_time}`); } else { console.log(`${domain} 未备案、已注销或为境外域名`); } } else { console.error(`业务异常: code=${payload.code}, msg=${payload.msg}`); }

两个示例都遵循同一个处理框架:拆解 URL → 发请求 → 先看业务码 → 再看业务数据。这比直接访问data.icp_code要稳健,因为未备案时data是空字段结构,直接取属性会拿到undefined

返回字段逐个拆解

domain=baidu.com为例,成功响应如下:

{ "code": 0, "data": { "audit_time": "2019-05-16 16:06:21", "company_name": "北京百度网讯科技有限公司", "company_type": "企业", "domain": "baidu.com", "icp_code": "京ICP证030173号-1", "is_filed": true, "site_name": "百度一下,你就知道" }, "msg": "成功", "request_id": "abc123def456" }

各字段含义如下:

字段类型说明
codenumber业务状态码,0表示成功
msgstring响应描述,例如「成功」
request_idstring请求唯一标识,排查问题时可以提供给服务方
data.domainstring清洗后的域名
data.is_filedboolean是否已备案。true为已备案,false为未备案
data.icp_codestring备案号,如京ICP证030173号-1
data.site_namestring网站名称,来自备案信息
data.company_namestring主办单位名称,可能是企业、个人或事业单位
data.company_typestring单位性质,如「企业」
data.audit_timestring备案审核通过时间,格式为YYYY-MM-DD HH:mm:ss

一个容易忽略的细节:返回的domain是接口清洗后的值,不一定是请求时传的原始字符串。如果业务系统里需要回写数据库,建议以响应中的data.domain为准,避免不同格式造成的数据冗余。

未备案与异常情况的语义区分

这是本文重点强调的边界。很多开发者在第一次接入时会有疑惑:未备案是不是抛错误?

不是。未备案、境外域名、备案已注销时,接口返回的code仍然是0,但data.is_filedfalse,并且data中除domain外的业务字段为空。

这种设计有一个明显的好处:业务代码可以写出非常干净的 if 分支:

if (payload.code !== 0) { // 只有这里才是真的异常,比如参数错误、鉴权失败、请求频率超限 } if (data.is_filed) { // 已备案逻辑 } else { // 未备案逻辑 }

不要用「icp_code是否存在」来判断备案状态,因为字段是否为空并不是该接口承诺的契约;is_filed才是判断备案状态的唯一依据。

常见错误与排查思路

初次接入时最可能遇到以下几类问题,按排查优先级排序:

  1. 鉴权方式不对。先确认你使用的是Authorization: Bearer还是X-API-Key,两者混用可能被识别为无效鉴权。其次是确认 Key 前缀是否完整,例如sk_live_开头。
  2. 域名格式异常。虽然接口有自动清洗能力,但如果你传入的字符串包含空格或换行,清洗逻辑可能无法正确识别。建议在请求前做一次trim()
  3. 把未备案当成失败is_filed=false不是错误,先检查你的代码是否在code != 0时把未备案的数据也拦截掉了。
  4. 忽略缓存导致的数据延迟。一个刚刚通过审核的新备案域名,在 1 小时缓存窗口内可能仍然返回未备案。设计业务逻辑时要预留这个时间窗口,不要基于一次查询结果做永久性标记。
  5. 频率超限。接口 QPS 为 5 / s。如果业务需要在短时间内批量查询,必须在客户端做限速,否则会收到限流响应。
  6. 超时时间设置过短。网络抖动时,一个跨地域请求可能超过 3 秒。建议把超时时间设为 5 秒,并在超时后做一次重试,但重试次数不建议超过 2 次,避免对服务端造成额外压力。

工程化注意事项

把接口从「能跑」提升到「可靠运行」,还需要关注以下工程细节:

缓存与时效性

接口侧已经有 24 小时 / 1 小时的缓存,业务侧不需要再做长时间缓存。但如果你的场景是每日批量巡检,建议把查询结果落库,并记录查询时间,便于追踪备案状态变化的时间点。

批量场景的限速设计

假设需要批量查询 10000 个域名,按 5 QPS 计算,理论耗时约 33 分钟。建议用量:

  • 使用令牌桶或简单的间隔循环,把请求速率控制在 4 QPS 左右留出余量。
  • 增加本地增量缓存:已备案域名 24 小时内不重复请求,未备案域名 1 小时内不重复请求。

日志与可观测性

建议把以下信息写入日志:

  • 传入的原始域名和接口返回的清洗后域名
  • request_id
  • 响应耗时
  • codeis_filed的组合结果

request_id是排查问题时的关键凭证。一旦出现批量异常,可以依据request_id快速证实或排除接口侧故障。

使用场景的资料留存

如果是合规审查或内容安全场景,建议把接口返回完整 JSON 存档,而不仅仅是提取某一个字段。一旦后续出现争议,原始响应就是最直接的证据。

参考文档

  • 接口文档页:https://apizero.cn/aidocs/icp
  • 原始文档(Markdown):https://apizero.cn/aidocs/icp/raw.md

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询