从 curl 到工程封装:网站安全综合评分接口的巡检落地

📅 2026/8/1 11:58:37
从 curl 到工程封装:网站安全综合评分接口的巡检落地
背景单次请求与工程化之间的距离安全评估类接口的特点是「单次调用容易稳定调用难」。用 curl 手动查一个域名几十秒就能拿到结果但要把这套能力接入到发布流水线、资产巡检或告警系统里还需要解决参数校验、限流控制、超时重试、结果缓存和异常分级等一系列问题。本文以「网站安全综合评分」接口为例走一遍从 curl 验证到工程封装的完整路径。该接口一次请求即可返回 SSL 证书、域名安全、ICP 备案、微信/QQ 拦截、网站性能五个维度的诊断信息适合作为自动化巡检的基础数据源。适用场景在动手写代码之前先明确这个接口能放进哪些业务环节域名资产定期巡检对持有的全部域名按周或按月批量评分及时发现证书临近过期、备案异常等问题。发布前安全体检新站点上线前把域名作为准入检查项grade 低于 C 时阻止发布。客户侧安全报告生成面向客户的定期安全简报用统一评分口径替代人工逐项检查。证书续期提醒从返回的days_until_expiry字段提取证书剩余天数提前触发续期工单。需要说明的是该接口单次只能传一个domainQPS 上限为 2/s所以它更适合「低频、批量、串行」的巡检场景而不是高并发实时调用。接口能力与边界五维加权评分机制总分为 0-100由五个维度按固定权重加权计算维度权重说明SSL 证书25%HTTPS 是否开启、证书剩余有效期域名安全20%域名相关安全状态ICP 备案20%备案信息是否正常微信/QQ 拦截15%在微信/QQ 环境是否被拦截网站性能20%响应速度等性能指标总分映射为 A/B/C/D/F 五个等级。从工程角度关注grade可以快速做「通过/不通过」判断关注overall_score则适合做趋势跟踪——比如同一个月度对比分数波动。关于响应耗时的预期根据响应示例一次检测耗时约 4.5 秒detection_time: 4521ms。这意味着调用方不能把 HTTP 超时设得太短默认的 5 秒超时在极端情况下可能不够。建议客户端超时设为 15-30 秒并配套合理的重试策略。请求参数与鉴权Query 参数参数必填类型说明domain是string纯域名如baidu.com不要带协议头Header 鉴权接口要求通过请求头传递身份凭证。素材中的参数表标注为Authorization而下方 curl 示例实际使用的是X-API-Key头。两种方式可能并行兼容具体以最新文档为准。写入代码时建议把鉴权头提取为配置项方便统一调整。第一步curl 验证连通性先拿 curl 确认网络链路、鉴权和返回结构都没问题再进入代码封装。下面是一个可直接替换变量的请求模板curl -sS \ -X GET \ -H X-API-Key: $APIZERO_API_KEY \ https://v1.apizero.cn/api/site-security?domainbaidu.com将$APIZERO_API_KEY替换为真实的 API Key把domain换成自己需要检测的域名。若返回 JSON 中code为 0msg为「成功」说明请求链路正常可以进入封装阶段。第二步Python 封装为可复用模块curl 适合验证但无法满足「超时控制、限流、重试、缓存」这些工程化需求。下面用 Python 的requests库做一个轻量封装。基础请求函数import time import requests from typing import Any, Dict API_ENDPOINT https://v1.apizero.cn/api/site-security class SiteSecurityError(Exception): 网站安全评分接口异常的统一包装类。 class SiteSecurityClient: def __init__(self, api_key: str, timeout: int 30): self.api_key api_key self.timeout timeout self.last_request_ts 0.0 def _rate_control(self): QPS 上限 2/s这里保证单客户端串行请求间隔不小于 0.6s。 elapsed time.time() - self.last_request_ts if elapsed 0.6: time.sleep(0.6 - elapsed) self.last_request_ts time.time() def fetch(self, domain: str, max_retries: int 2) - Dict[str, Any]: 获取指定域名的安全评分失败时按指数退避重试。 if not domain or / in domain or :// in domain: raise SiteSecurityError(fdomain 参数必须是纯域名收到: {domain!r}) self._rate_control() url f{API_ENDPOINT}?domain{domain} headers {X-API-Key: self.api_key} for attempt in range(max_retries 1): try: resp requests.get(url, headersheaders, timeoutself.timeout) resp.raise_for_status() payload resp.json() if payload.get(code) ! 0: raise SiteSecurityError( f业务错误: code{payload.get(code)}, msg{payload.get(msg)} ) return payload[data] except (requests.RequestException, ValueError) as exc: if attempt max_retries: raise SiteSecurityError(f请求 {domain} 失败: {exc}) from exc time.sleep(1.5 ** attempt) raise SiteSecurityError(不可达分支)使用示例client SiteSecurityClient(api_keyyour-api-key-here) report client.fetch(baidu.com) print(f域名: {report[domain]}) print(f总分: {report[overall_score]}) print(f等级: {report[grade]}) print(f耗时: {report[detection_time]}) print(fSSL 剩余天数: {report[ssl][days_until_expiry]})这段代码虽然不长但已经覆盖了参数校验、限流、重试和超时控制可以直接作为巡检脚本的入口函数。返回字段解读根据响应示例成功时返回结构如下{ code: 0, data: { detection_time: 4521ms, domain: baidu.com, grade: A, overall_score: 92, ssl: { days_until_expiry: 365, https_enabled: true, score: 100 } }, msg: 成功 }核心字段说明字段类型含义codeint业务状态码0 表示成功msgstring状态描述data.domainstring本次查询的域名data.overall_scoreint综合评分0-100data.gradestring等级A/B/C/D/Fdata.detection_timestring检测耗时含单位data.ssl.https_enabledbool是否开启 HTTPSdata.ssl.days_until_expiryint证书剩余有效天数data.ssl.scoreintSSL 维度得分注意素材只展示了ssl维度的完整结构其余四个维度的具体字段名域名安全、ICP 备案、拦截状态、性能数据未在示例中列出实际接入时以接口文档为准。稳妥的做法是在代码中统一用report.get(ssl, {})这类防御式访问避免某个维度缺失导致KeyError。常见错误与排查思路接口层面的错误可以从两个层面分析HTTP 状态码层面401API Key 缺失或无效。检查请求头是否携带了正确凭证。前面提到素材中参数表与 curl 示例使用的 Header 名称不一致排查时优先对比文档页的鉴权说明。400请求参数不合法。常见原因包括domain参数缺失、带了https://前缀、或传入了端口号。429请求过于频繁超出 QPS 限制。需要在客户端增加限流串行请求时保证间隔不小于 0.6 秒。业务层 code 非 0HTTP 状态是 200但 JSON 中code不为 0、msg不是「成功」时属于业务层错误。封装代码中应当把这种情况显式抛出而不是默默吞掉。客户端层面的坑requests默认不会对超时进行处理忘记传timeout参数时调用可能长时间挂起。解析响应体前先resp.raise_for_status()避免把 HTML 错误页当 JSON 解析。detection_time单位是毫秒且带后缀不要直接int()转换。工程化注意事项1. 限流QPS 2/s 是硬约束QPS 上限是 2/s也就是说两次请求之间的最短间隔是 0.5 秒。上面的封装采用了 0.6 秒间隔留出安全余量。如果检测 100 个域名全量串行大约需要 100 × 0.6 秒 ≈ 1 分钟再加上每次检测本身的耗时会更长。批量场景下要评估这个时间维护复杂度。2. 缓存避免重复调用安全状态短时间内不会剧烈变化。对评分等级为 A 或 B 的域名可以设置 24 小时的 TTL 缓存只有 C 级以下或证书临期剩余天数 30的域名才需要更频繁的检查。这样能显著减少调用次数也更容易控制 QPS。3. 告警阈值设计建议关注三个信号信号建议阈值动作gradeC 级以下触发告警人工复核ssl.days_until_expiry 30 天推送续期工单overall_score环比下降超过 10 分检查变更原因4. 批量巡检的编排方式由于接口一次只能接受一个域名批量巡检时需要循环调用。可以先用一个 JSON 文件维护域名清单再逐条调用并落库import json import sqlite3 with open(domains.json) as f: domain_list json.load(f) conn sqlite3.connect(security_reports.db) c conn.cursor() c.execute(CREATE TABLE IF NOT EXISTS reports ( domain TEXT PRIMARY KEY, score INTEGER, grade TEXT, checked_at TEXT )) client SiteSecurityClient(api_keyyour-api-key-here) for domain in domain_list: data client.fetch(domain) c.execute( INSERT OR REPLACE INTO reports VALUES (?, ?, ?, datetime(now)), (data[domain], data[overall_score], data[grade]), ) conn.commit() conn.close()5. 把功能封装成 CLI如果不想引入调度系统可以用一个很薄的 CLI 包装暴露出来import argparse import json parser argparse.ArgumentParser(description查询网站安全综合评分) parser.add_argument(--domain, requiredTrue, help纯域名如 baidu.com) args parser.parse_args() client SiteSecurityClient(api_keyyour-api-key-here) print(json.dumps(client.fetch(args.domain), ensure_asciiFalse, indent2))这样运维同事不需要懂 Python也能通过python check_site.py --domain baidu.com完成查询。总结从 curl 到工程封装本质上是把「一次验证」变成「一种能力」。curl 验证解决的是连通性问题而真正能放进巡检体系的代码需要具备参数校验、限流、重试、超时管理和结果落库这些基本素质。网站安全综合评分接口单次请求带来的五维数据已经足够完整剩下的事情是在客户端把请求频率、缓存策略和告警规则设计好让每一次调用都产生可沉淀的数据。参考文档网站安全综合评分 - 文档页原始文档 Markdown