用 pkg.go.dev API 汇总包的许可证与文档状态
来源:17golang原创
时间:2026-10-09 02:45:23 136浏览 收藏
用 pkg.go.dev API 批量盘点 Go 包时,最常见的现象是:请求成功了,但许可证列表和文档正文都是空。原因通常不是包没有许可证或文档,而是 /v1/package/{path} 默认只返回基础元数据;必须显式带上 licenses=true 和 doc=markdown,才能取得许可证详情与 Markdown 文档。
汇总时应把 isRedistributable、licenses[].types、docs 和 synopsis 分开判断。前两项描述 pkg.go.dev 检测到的许可信息,后两项描述文档可用性。它们适合做依赖清单和待复核队列,但不能替代法律审查。
官方 API 文档:https://pkg.go.dev/v1/api
为什么默认请求看不到许可证和文档
先看最小请求。当前 pkg.go.dev API 是无状态、GET-only 的 JSON API,package 路由会返回包路径、模块路径、版本、摘要以及 isRedistributable 等基础字段。但是 doc 参数省略时,响应不会返回 docs;licenses 没有设为 true 时,也不会返回许可证列表。
# 默认请求只取基础元数据,适合先确认包与模块定位 curl -L 'https://pkg.go.dev/v1/package/golang.org/x/time/rate' # 显式请求 Markdown 文档和许可证详情,并固定模块与版本 curl -L 'https://pkg.go.dev/v1/package/golang.org/x/time/rate?module=golang.org/x/time&version=latest&doc=markdown&licenses=true'
官方在 2026 年 5 月发布 API 时,博客示例使用的是 /v1beta。当前交互式文档已经给出 /v1 路由,因此新工具应以当前 API 文档为准,不要把发布文章中的 beta 路径永久写死。
先把请求参数补齐
package 路由的关键参数可以分成三组:
| 参数 | 作用 | 汇总工具建议 |
|---|---|---|
module | 明确提供该包的模块路径 | 依赖清单已知模块时始终传入 |
version | 选择语义版本、latest、main 或 master | 审计构建依赖时传实际锁定版本 |
goos/goarch | 选择文档构建上下文 | 平台相关包应与目标构建环境一致 |
doc | 选择 text、html、md 或 markdown 文档格式 | 仅判断状态时用 markdown |
licenses | 在响应中包含许可证列表 | 设置为 true |

包路径可能由多个模块提供。例如子目录后来被拆成独立模块时,同一个 package path 可能对应不止一个 module。pkg.go.dev 网页会选择最长匹配模块,API 则强调精确性:遇到歧义会返回错误,并在 candidates 中列出候选。批处理不能擅自取第一项,应回到依赖清单或 go.mod 确认模块。
实现一个可控的 Go API 客户端
下面的完整示例把目标包、模块与版本写成配置,使用 15 秒客户端超时、8 MiB 响应上限和约 33 QPS 的节流。官方文档给出的限制是每个 IP block 45 QPS;保留余量可以降低多个作业共享出口时触发 429 的概率。
package main
import (
"context"
"encoding/csv"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"sort"
"strings"
"time"
)
const maxResponse = 8 maxResponse {
return nil, nil, fmt.Errorf("响应超过 %d 字节", maxResponse)
}
if resp.StatusCode != http.StatusOK {
// API 错误同样是 JSON;保留 candidates 和 fixes 供人工判断
var apiErr APIError
if err := json.Unmarshal(body, &apiErr); err != nil {
return nil, nil, fmt.Errorf("HTTP %d 且错误响应无法解析: %w", resp.StatusCode, err)
}
if apiErr.Code == 0 {
apiErr.Code = resp.StatusCode
}
return nil, &apiErr, nil
}
var pkg Package
if err := json.Unmarshal(body, &pkg); err != nil {
return nil, nil, err
}
return &pkg, nil, nil
}
func summarize(pkg *Package) (licenseStatus, licenseTypes, docStatus string) {
// 类型去重后排序,保证多次汇总的 CSV 输出稳定
typeSet := make(map[string]struct{})
for _, lic := range pkg.Licenses {
for _, typ := range lic.Types {
typeSet[typ] = struct{}{}
}
}
for typ := range typeSet {
licenseTypes += typ + ","
}
parts := strings.FieldsFunc(licenseTypes, func(r rune) bool { return r == ',' })
sort.Strings(parts)
licenseTypes = strings.Join(parts, ",")
switch {
case pkg.IsRedistributable && licenseTypes != "":
licenseStatus = "检测到可再分发许可证"
case licenseTypes != "":
licenseStatus = "检测到许可证,需人工复核"
default:
licenseStatus = "未返回可识别许可证"
}
switch {
case strings.TrimSpace(pkg.Docs) != "":
docStatus = "文档正文可用"
case strings.TrimSpace(pkg.Synopsis) != "":
docStatus = "仅摘要可用"
default:
docStatus = "未返回文档"
}
return
}
func main() {
// 实际工程可从 go list -m 结果生成这份目标清单
targets := []Target{
{Package: "golang.org/x/time/rate", Module: "golang.org/x/time", Version: "latest"},
{Package: "github.com/google/go-cmp/cmp", Module: "github.com/google/go-cmp", Version: "latest"},
}
client := &http.Client{Timeout: 15 * time.Second}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
// 30 毫秒一个请求,约 33 QPS,低于官方 45 QPS 上限
ticker := time.NewTicker(30 * time.Millisecond)
defer ticker.Stop()
w := csv.NewWriter(os.Stdout)
defer w.Flush()
_ = w.Write([]string{"package", "module", "version", "license_status", "license_types", "doc_status", "note"})
for i, target := range targets {
if i > 0 {
select {
case
示例没有自动重试所有错误。批量治理工具更应该先准确分类错误,再决定是否重试:超时和临时 5xx 可以有限退避;400 路径歧义需要补 module;404 需要检查包、模块与版本;429 应降低速率并尊重退避,而不是立即并发重放。
把 API 字段归并成可读状态
isRedistributable 是 pkg.go.dev 根据许可证检测结果给出的布尔值;licenses 列表则包含检测到的类型、文件路径,以及在请求允许时返回的内容。状态归并时不要只看其中一个字段。

| 组合 | 建议状态 | 后续动作 |
|---|---|---|
isRedistributable=true 且 licenses 有类型 | 检测到可再分发许可证 | 记录类型和文件路径,仍保留项目级审核 |
| licenses 有类型但 isRedistributable=false | 检测到许可证,需复核 | 检查具体条款与项目使用方式 |
| licenses 为空 | 未返回可识别许可证 | 检查版本、许可证文件名和上游仓库 |
| docs 非空 | 文档正文可用 | 可进一步做摘要、索引或离线检索 |
| docs 为空但 synopsis 非空 | 仅摘要可用 | 检查 doc 参数、构建上下文与许可限制 |
| docs 与 synopsis 都为空 | 未返回文档 | 检查包是否能在目标 GOOS/GOARCH 构建 |
pkg.go.dev 的许可证策略明确说明,检测依赖文件名和内容启发式,结果不构成法律建议,也不保证绝对准确。许可证未被识别时,站点可能只提供有限的包或模块信息。因此“文档为空”不能直接等同于“作者没写文档”,还可能是许可、版本、构建上下文或包定位问题。
三类异常最容易让汇总结果失真
1. 包路径歧义
错误响应中的 candidates 是待选择集合,不是排序后的推荐答案。最稳妥的做法是从本项目的模块图取得实际 module path,再重新请求。如果盘点的是构建产物,version 也应使用依赖锁定版本,而不是 latest。
2. 文档构建上下文不一致
一些包只在特定 GOOS/GOARCH 下存在,或不同平台导出不同符号。默认文档上下文通常是 linux/amd64,但工具不应假设所有包都如此。汇总 Windows、WASM 或移动端依赖时,显式传入 goos 和 goarch。
3. 429 被误记为“无文档”
官方限制为每个 IP block 45 QPS。HTTP 429 是请求速率问题,不能落入 docs 为空的业务分支。应保留 HTTP 状态、错误 message 和重试次数,把限流失败单独汇总。
如何确认汇总逻辑没有误判
不需要依赖网页抓取来复查 API。可以在测试数据中准备四类目标:许可证与文档均可用的包、只有摘要的包、平台相关包,以及故意省略 module 的歧义包。检查 CSV 是否分别落入“可用”“仅摘要”“未返回”和“需人工选择”状态。
还应把原始字段保留下来,而不是只存最终中文标签。至少保存 package、module、version、GOOS、GOARCH、isRedistributable、license types、doc 长度、HTTP 状态和采集时间。未来规则变化时,可以重新计算状态,不必重新发起全部请求。
常见问题
只需要判断文档是否存在,还要下载完整 docs 吗?
package 基础响应只有 synopsis,不能代表完整文档正文可用。要严格判断正文状态,需要请求 doc=text 或 doc=markdown;如果包很多,可以记录长度或哈希,不必长期保存全文。
许可证类型为空就能认定没有许可证吗?
不能。它只表示 pkg.go.dev 没有返回可识别的许可证类型。原因可能是许可证文件名、文本差异、版本或检测范围。应把它标记为人工复核,而不是直接判定为无许可证。
为什么汇总结果要固定 version?
省略 version 时 API 选择 latest。依赖治理关注的是项目实际使用版本,latest 的许可证、文档和可再分发状态可能与锁定版本不同,因此构建审计应传入真实版本。
可以直接抓 pkg.go.dev 网页吗?
不建议。官方 JSON API 提供稳定字段、错误模型、分页和限流说明,比解析网页结构可靠。网页适合人工阅读,自动化汇总应使用 https://pkg.go.dev/v1/api 定义的接口。
最终原则很简单:先用 module 与 version 精确定位,再显式打开 doc 与 licenses,最后把许可证、文档和异常分开归并。这样生成的清单既能服务依赖治理,也不会把“API 没返回”误写成“包不存在”。
-
151 收藏
-
101 收藏
-
323 收藏
-
428 收藏
-
143 收藏
-
458 收藏
-
363 收藏
-
462 收藏
-
466 收藏
-
394 收藏
-
414 收藏
-
332 收藏
-
478 收藏
-
311 收藏
-
172 收藏
-
190 收藏
-
101 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习