ICP 备案查询接口接入实战:域名备案信息查询的后端封装与工程化设计
很多业务在处理域名时,表面需求看起来都不复杂:
- 用户提交一个网址
- 系统判断这个站点是否已备案
- 如果已备案,补全备案号、网站名称、主体名称
- 如果未备案,给出提示或进入风控流程
看起来像是一个很轻的查询能力,但真正落到项目里,问题通常比“发一个 GET 请求”复杂一些:
- 用户传进来的不一定是纯域名,往往是完整 URL
- 可能带协议、路径、端口、
www.前缀,甚至是移动端子域名 - 查询结果要不要缓存
- 未备案是不是就等于非法站点
- 域名录入、内容审核、投放风控,不同业务怎么消费这个结果
- 是否要把接口返回直接透给前端
所以,从开发接入角度看,ICP 备案查询接口的价值,不只是“查一下备案号”,而是把域名信息校验和备案信息补全做成一个稳定可复用的基础能力。
一、这个接口真正适合解决什么问题
如果只把 ICP 查询理解成“站长工具能力”,文章会写得太窄。
放到真实业务里,它更常见的用途其实是这些:
-
域名录入校验
平台录入官网、落地页、推广站点时,先补一层备案信息。 -
广告投放或渠道审核
提交投放链接时,快速判断国内站点的合规基础信息。 -
内容平台外链风控
用户发外链、站点跳转、资源分发时,增加一层站点背景判断。 -
企业信息补全
对站点做备案主体识别,用于商家资料、公司信息辅助校验。 -
内部运维或资产盘点
对维护中的域名批量查询备案情况,辅助管理站点资产。
也就是说,这类接口更像一个域名信息查询组件,而不是一个孤立的备案号查询工具。
二、为什么“自动域名清洗”比表面看起来更重要
很多人第一次接这种接口时,只会看返回字段:
- 是否备案
- 备案号
- 网站名称
- 主办单位
- 审核时间
但在真实项目里,开发者更容易栽在输入上。
2.1 用户传进来的常常不是标准域名
实际场景里,常见输入可能是:
baidu.comwww.baidu.comhttps://www.baidu.com/abcm.baidu.com:8080/foo- 一整段文案里的某个 URL
如果系统把“域名校验”和“备案查询”混在一起,前端和后端就会反复出现同类清洗逻辑。
2.2 更合理的做法:把“域名清洗”当成接口入口能力的一部分
这类接口的实际工程价值之一,就是把这些处理统一掉:
- 剥离
http://、https:// - 去掉路径
/path - 去掉端口
:8080 - 去掉
www.前缀 - 收敛成主域名口径
从系统设计上看,这一步能明显减少前端和后端重复判断。
三、接入这类接口前,建议先想清楚 3 个问题
3.1 你要查的是“域名状态”,还是“站点合规结论”
这是最容易被混淆的一点。
接口返回的是:
- 是否有备案信息
- 备案主体是谁
- 网站名称是什么
- 审核时间是什么时候
它能告诉你“查到了什么”,但不能直接等价为:
- 这个站点就一定安全
- 这个站点就一定可信
- 这个站点就一定适合放行
所以更准确地说,备案信息是一个站点背景信号,不是最终风控结论。
3.2 你的业务消费结果的方式是什么
不同业务对结果的使用方式完全不一样:
| 业务场景 | 更关注什么 |
|---|---|
| 域名录入 | 是否备案、主体名称 |
| 广告审核 | 备案号、网站名、主体类型 |
| 风控系统 | 是否备案、审核时间、是否命中缓存 |
| 企业资料补全 | 公司名称、单位性质 |
| 资产盘点 | 域名、备案状态、审核时间 |
所以,最好不要把第三方原始返回直接透给所有业务,而是做自己的统一结构。
3.3 你是否准备做缓存
这类查询非常适合缓存,因为备案信息不是高频实时变化数据。
如果系统里有:
- 重复域名查询
- 批量录入
- 站点反复审核
- 历史链接回查
那缓存能明显降低上游调用量,也能提升整体响应体验。
四、一个更稳的后端接入方式
下面用 Node.js 演示一种更适合实际项目的封装思路。重点不是“怎么拼 GET 参数”,而是怎么把第三方能力收敛成自己的内部域名信息服务。
4.1 安装依赖
npm install axios
4.2 封装 ICP 查询请求
const axios = require("axios");
async function queryIcp(domain, apiKey) {
if (!domain || !String(domain).trim()) {
throw new Error("domain 不能为空");
}
const headers = {};
if (apiKey) {
headers["Authorization"] = `Bearer ${apiKey}`;
}
const response = await axios.get("https://v1.apizero.cn/api/icp", {
params: { domain },
headers,
timeout: 8000
});
const result = response.data;
if (!result || result.code !== 0 || !result.data) {
throw new Error(result?.msg || "ICP 查询失败");
}
return normalizeIcpResult(result);
}
4.3 做结果标准化
function normalizeIcpResult(raw) {
const data = raw.data || {};
return {
domain: data.domain || "",
isFiled: Boolean(data.is_filed),
icpCode: data.icp_code || "",
siteName: data.site_name || "",
companyName: data.company_name || "",
companyType: data.company_type || "",
auditTime: data.audit_time || "",
requestId: raw.request_id || ""
};
}
4.4 对外暴露内部接口
const express = require("express");
const app = express();
app.get("/api/domain/icp", async (req, res) => {
try {
const domain = req.query.domain;
if (!domain) {
return res.status(400).json({
code: 400,
message: "缺少 domain 参数"
});
}
const data = await queryIcp(domain, process.env.APIZERO_API_KEY);
res.json({
code: 0,
message: "ok",
data
});
} catch (error) {
res.status(500).json({
code: 500,
message: error.message || "服务异常"
});
}
});
app.listen(3000, () => {
console.log("server running at http://localhost:3000");
});
五、为什么不建议把第三方返回直接透传给前端
很多项目为了省事,会这样做:
- 前端直接请求第三方接口
- 或者后端只做转发,不做抽象
短期确实快,但从工程角度看,这样做有几个问题。
5.1 直接透传的问题
- 前端和第三方字段强绑定
- 以后换服务源改动大
- 业务层容易滥用原始字段
- 很难统一做缓存、限流和日志
- 很难做二次判断和风控策略
5.2 更推荐的做法:做内部“域名信息服务层”
这层统一负责:
- 域名清洗
- ICP 查询
- 结果标准化
- 缓存
- 异常分层
- 业务规则映射
这样以后无论是换接口、叠加 Whois、补充站点标题抓取,还是叠加风控标签,都更容易扩展。
六、实际项目里最容易踩的几个坑
6.1 未备案不等于一定有问题
这是最需要提醒的一点。
有些域名没有 ICP 信息,可能是因为:
- 境外站点
- 暂未备案
- 已注销
- 非国内主体
- 当前缓存结果未命中
所以如果业务直接把“未备案”当成“非法”或“危险”,很容易误伤。
更稳的做法是:
- 在国内站点场景下,把备案信息作为重要参考项
- 在站点风控场景里,把它作为风险信号之一,而不是唯一结论
6.2 子域名和主域名口径要统一
例如:
www.example.comm.example.comdocs.example.com
业务上有时希望统一查主域名,有时又希望保留原输入。
所以建议内部结构里同时保留两类信息:
- 原始输入
- 清洗后的查询域名
示意结构:
{
"rawInput": "https://www.baidu.com/abc",
"queryDomain": "baidu.com"
}
这对问题排查很有帮助。
6.3 缓存策略不要一刀切
文档里一般会说明缓存周期,但你自己的系统也最好加一层本地缓存。
建议按结果类型区分:
| 结果类型 | 建议策略 |
|---|---|
| 已备案 | 可以适当长一点缓存 |
| 未备案 | 缓存时间更保守 |
| 请求失败 | 不要长时间缓存错误 |
| 参数非法 | 可直接本地拦截 |
这样能兼顾性能和数据有效性。
6.4 不要把主体名称直接当企业实名认证结果
company_name 很有用,但它更适合做辅助信息。
例如:
- 录入官网时做主体补全
- 商家审核时做人工辅助核对
- 外链审核时做背景识别
但它不应该替代你系统里的实名认证、合同主体校验或人工审核逻辑。
七、推荐的工程化增强方案
7.1 增加本地缓存
最推荐补的能力之一就是缓存。
可以按 domain 做缓存键。
示例:
const crypto = require("crypto");
function getDomainCacheKey(domain) {
return crypto.createHash("md5").update(domain).digest("hex");
}
缓存收益包括:
- 降低第三方调用量
- 提升响应速度
- 减少高频重复查询
7.2 增加域名预处理
虽然上游接口支持较强清洗,但内部最好还是加一层基础预处理,用于:
- 输入校验
- 去空格
- 统一小写
- 过滤明显非法值
示例:
function normalizeDomainInput(input) {
return String(input || "")
.trim()
.toLowerCase();
}
如果后续业务复杂,还可以补:
- 从文本中提取 URL
- 过滤 IP 地址输入
- 处理国际化域名
7.3 增加业务动作映射
比如对不同业务场景做不同结果处理:
| 场景 | 已备案 | 未备案 |
|---|---|---|
| 域名录入 | 正常通过 | 提示补充说明 |
| 外链审核 | 放行或人工复核 | 风险标记 |
| 广告投放 | 正常校验 | 待审或拦截 |
| 企业官网收集 | 自动补全主体 | 只保存原域名 |
这样第三方结果和业务规则就不会混在一起。
7.4 增加日志与审计能力
至少建议记录:
- 原始输入
- 清洗后的域名
- 查询时间
- 查询结果
- request_id
- 业务场景
- 操作人或请求来源
这对排查接口问题、风控误判和调用稳定性非常有帮助。
八、适合哪些项目,不适合哪些项目
8.1 适合的场景
这类 ICP 查询接口通常适合:
- 域名录入工具
- 官网收集系统
- 外链审核平台
- 广告投放后台
- 企业信息补全工具
- 网站资产管理平台
8.2 不适合直接当最终结论的场景
如果你希望它单独承担这些任务,就不太合适:
- 完整站点安全评估
- 企业真实性认证
- 链接安全终审
- 高强度风控判定
原因很简单:
ICP 备案信息适合做域名背景校验,不适合单独承担完整合规和安全结论。
九、从架构角度看,更合理的落地方式
如果让我来设计,我更推荐拆成四层:
9.1 输入层
负责:
- 接收域名或 URL
- 清洗输入
- 参数校验
9.2 查询层
负责:
- 调用 ICP 查询接口
- 处理鉴权头
- 控制超时和重试
- 获取原始结果
9.3 标准化层
负责:
- 字段映射
- 结果结构统一
- 是否备案的布尔判断
- 缓存写入
9.4 业务层
负责:
- 审核策略
- 录入规则
- 风控动作
- 日志与审计
可以用下面这个流程来理解:
十、一个更实用的接入建议清单
10.1 接入前
- 明确输入是域名还是完整 URL
- 明确业务需要哪些字段
- 明确是否需要缓存
- 明确未备案时的业务动作
10.2 接入中
- 不直接透传第三方返回
- 做统一 DTO
- 做基础域名清洗
- 做超时控制
- 做日志和 request_id 记录
10.3 上线后
- 统计查询成功率
- 观察高频重复域名
- 调整缓存策略
- 跟踪未备案场景误伤率
- 对关键业务增加人工复核链路
十一、总结
ICP 备案查询接口看起来只是一个域名信息查询工具,但在真实项目里,它更适合作为一个域名背景校验与主体信息补全组件来使用。
真正值得开发者关注的,不是“有没有备案号这个字段”,而是:
- 输入域名怎么清洗
- 返回结果怎么标准化
- 是否要做缓存
- 未备案结果怎么解释
- 如何和业务规则解耦
如果只是做 Demo,直接请求接口就够了。
如果要进真实项目,重点一定要放在服务封装、缓存、日志、输入治理和业务映射上。

浙公网安备 33010602011771号