应使用 goldmark 解析 Markdown AST 并遍历 ast.Link/ast.Image 节点提取 URL,而非正则;需用 url.Parse 校验协议与 Host,跳过非 HTTP(S)、相对路径及危险协议;并发验证须限流、复用 client、结构化输出含 Line 和 OriginalText。

如何从 Markdown 字符串中准确提取所有超链接
直接用正则匹配 href 或 [text](url) 容易漏掉嵌套、转义、HTML 混排等情况。真正可靠的提取方式是先解析 Markdown AST,再遍历节点。推荐用 goldmark(Go 生态最活跃的 Markdown 解析器),它支持扩展、稳定、无 CGO 依赖。
关键点:不要自己写正则去匹配 [.*?]\(.*?\) —— 它无法处理换行、反斜杠转义、内联 HTML 中的 <a href="...">,更没法区分链接是普通 inline link 还是 reference link。
- 用
goldmark.Parse()得到文档 AST 根节点 - 递归遍历,只关心
*ast.Link和*ast.Image节点(Image 的Destination也是 URL) - 调用
link.Destination获取原始 URL 字节,用string()转为字符串(注意 UTF-8 安全) - 跳过空链接、
mailto:、tel:等非 HTTP(S) 协议(除非业务明确需要)
并发验证 URL 可达性时如何控制 goroutine 数量和错误传播
对上百个链接开几百个 goroutine,不加限制会触发系统级限制(如文件描述符耗尽、DNS 超时堆积、目标站封 IP)。必须做并发控制,且不能让单个失败链接阻塞整体流程。
推荐用带缓冲的 channel 控制并发数(比如最多 10 个并发请求),配合 context.WithTimeout 统一管理超时,每个 goroutine 返回 struct{ URL string; Err error; Status int } 结构体。
立即学习“go语言免费学习笔记(深入)”;
使用 markitdown 将文档和文件转换为 Markdown。适用于转换 PDF、Word (.docx)、PowerPoint (.pptx)、Excel (.xlsx, .xls)、HTML、CSV、JSON、XML 等格式。
- 避免用
sync.WaitGroup+ 无限制go启动 —— 无法优雅中断 - HTTP client 必须复用,设置
Timeout和Transport.MaxIdleConnsPerHost(建议设为 20~50) - 对 3xx 重定向要显式控制是否跟随(
Client.CheckRedirect = func(...){ return http.ErrUseLastResponse }),否则可能跳转到非预期域名 - 404、410、503 算“可访问但内容不存在/临时不可用”,不应等同于网络失败;只有
net.OpError、context.DeadlineExceeded才算验证失败
如何识别并跳过无效或危险的 URL 格式
不是所有提取出的 Destination 都能直接丢给 http.Get。有些是相对路径、锚点、data URI、甚至 JS 伪协议,盲目请求会 panic 或暴露安全风险。
- 用
url.Parse()解析后检查u.Scheme是否为"http"或"https",其他一律跳过 -
u.Host == ""且u.Path != ""→ 相对路径,需结合文档 base URL 补全(若无 base URL,则跳过) - 禁止请求
javascript:、vbscript:、data:、file:类协议(防止 SSRF 或本地文件读取) - 对
localhost、127.0.0.1、私有网段(如192.168.x.x)做白名单控制 —— 多数生产环境应默认拒绝
验证结果如何结构化输出并保留原始上下文
只返回“链接有效/无效”远远不够。用户需要知道这个链接在原文哪一行、属于哪个标题下、是否是图片链接、响应状态码是多少。否则无法定位和修复。
建议构建一个结果结构体,字段包括:URL、Line(通过 ast.Position 获取)、OriginalText(如 [官网](https://example.com))、IsImage、Status、Err。所有字段都应在验证 goroutine 中一次性收集,避免事后回溯 AST。
- 不要在验证完成后重新解析 Markdown 来找位置 —— AST 在并发中不可安全共享,且位置信息在解析时已丢失部分精度
-
ast.Link节点自带Position字段,解析阶段就应提取并绑定到待验证任务中 - 如果原始 Markdown 含有多个相同 URL,按出现顺序编号(如
URL#1、URL#2),避免结果混淆
最易被忽略的是:链接验证失败时,很多人只记录错误类型,却没保存原始 Markdown 片段。一旦文档更新,就再也无法对应到具体哪一行哪一段 —— 所以 OriginalText 和 Line 必须在第一轮解析时固化下来。

















