因为正则无法处理嵌套、转义、引用式链接及HTML混排,而gomarkdown/markdown的AST解析将链接拆为ast.Link节点,Dest存URL、Children存锚文本,结构清晰、提取鲁棒,支持扩展且便于校验与脱敏。

为什么不用正则而要用 gomarkdown/markdown 的 AST 解析
正则匹配 [text](url) 看似简单,但实际会漏掉嵌套、转义、标题内链接、引用式链接([foo][bar])甚至 HTML 混排场景。gomarkdown/markdown 的 AST 是结构化结果,节点类型明确,ast.Link 节点天然隔离了文本内容与 URL,不依赖字符串位置或边界判断。
它把 [Go 官网](https://go.dev) 拆成:ast.Link 节点 → Link.Dest 存 URL,Link.Children 存 “Go 官网” 文本节点。这种分离让提取更鲁棒,也便于后续做 URL 校验或文本脱敏。
如何用 gomarkdown/markdown 构建并遍历 AST 提取所有链接
关键不是调 markdown.ToHTML,而是先调 markdown.Parse 得到根节点 *ast.Document,再用 ast.Walk 或自定义递归遍历——因为 ast.Inspect 在 markdown AST 中不适用(gomarkdown 自定义节点未实现标准 ast.Node 接口)。
-
markdown.Parse([]byte(src), parser.WithExtensions(...))必须传入扩展选项(如parser.CommonExtensions),否则ast.Link可能被降级为普通文本 - 遍历时检查节点是否为
*ast.Link类型:用类型断言,别直接强制转换,避免 panic -
link.Dest是[]byte,需用string(link.Dest)转换;注意它可能含空格或换行,建议strings.TrimSpace - 如果要提取链接的锚文本(即
[xxx]里的 xxx),得遍历link.Children并拼接所有*ast.Text节点的Literal字段
常见错误现象和兼容性注意点
最常遇到的是 link.Dest 为空或为 nil —— 这通常是因为源码用了引用式链接([name][ref])但没提供对应的定义块,解析器无法解析目标,就留空。此时不能跳过,应记录 warning 或 fallback 到原始 raw text。
立即学习“go语言免费学习笔记(深入)”;
另一个坑是中文路径或带 query 参数的 URL 被自动解码(如 %E4%BD%A0%E5%A5%BD → “你好”),而 link.Dest 返回的是已解码后的字节,若需原始 URL,得改用 parser.WithNoDanglingLinks + 自定义 ast.Renderer 拦截原始 token 流,成本高,一般没必要。
- gomarkdown/markdown 默认不处理 HTML 标签内的链接(
<a href="...">xxx</a>),这类需额外启用parser.SkipHTML或关闭parser.NoHTML - AST 节点字段名大小写敏感:
Dest不是dest,Children不是children,IDE 补全有时误导人 - 不同版本 gomarkdown 对 reference link 的 AST 结构有差异(v2.0+ 把
ast.Reference单独拆出),若项目锁定了旧版,得查对应 tag 的源码确认字段是否存在
性能影响与真实使用边界
单次解析几百 KB Markdown 是毫秒级,但频繁调用 markdown.Parse 会重复初始化 parser state,建议复用 parser.Parser 实例(通过 markdown.New(...) 构造),尤其在 HTTP handler 中批量处理时。
真正卡顿的地方不在 AST 遍历,而在你对每个 link.Dest 做 HTTP HEAD 请求校验——这完全脱离 AST 范畴,得异步或限流处理。AST 本身只负责“提取”,不做网络 IO。
如果你只需要提取链接,别碰 ast.Renderer 或 html.Writer,它们触发完整渲染流程,开销大且无关;专注 *ast.Link 和它的两个字段就够了。


















