Golang初级实战:编写一个简单的Markdown图片下载器 Go语言正则与网络

4次阅读

regexp.compile 会 panic 而非返回 Error,因传入非法正则(如未闭合括号)时直接崩溃;应改用 compileposix 或 defer-recover 捕获;http.get 403 多因缺失 user-agent,需手动设置;保存文件须解析 url 取 base 名并 clean 路径;并发下载须限流与设超时。

Golang初级实战:编写一个简单的Markdown图片下载器 Go语言正则与网络

为什么 regexp.Compile 会 panic 而不是返回 error?

因为传了非法正则表达式(比如未闭合的括号、无效转义),regexp.Compile 会直接 panic,而不是像多数 go 函数那样返回 error。这是它和 regexp.CompilePOSIX 的关键区别。

  • 常见错误现象:panic: regexp: Compile(`![.*?]((.*?))`) : error parsing regexp: missing closing ): `![.*?]((.*?)` —— 实际上是字符串里少了一个右括号,但错误信息只截断显示,容易误判
  • 使用场景:解析 markdown 图片语法 ![alt](url) 时,若正则写成 `![.*?]((.*?)`(漏了末尾 )),就会 panic
  • 正确做法:用 regexp.Compile 前加 defer-recover 捕获,或改用更安全的 regexp.CompilePOSIX(兼容性略低,但不会 panic)
  • 参数差异:CompilePOSIX 不支持 perl 风格的 K(?i) 等,但对基础 Markdown 提取够用;Compile 功能强,但容错差

下载图片时 http.Get 返回 403,但浏览器能打开

绝大多数情况是目标服务器校验 User-Agent,Go 默认请求头里这个字段为空,被当成爬虫拦截。

  • 常见错误现象:GET https://example.com/img.png: 403 Forbiddencurl 或浏览器手动访问却正常
  • 解决方法:手动设置请求头,不要直接用 http.Get,改用 http.NewRequest + http.DefaultClient.Do
  • 示例片段:
    req, _ := http.NewRequest("GET", imgURL, nil) req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") resp, err := http.DefaultClient.Do(req)
  • 注意:有些站点还检查 Referer 或要求 TLS 1.2+,但 User-Agent 是第一道关卡,90% 的 403 卡在这儿

保存文件时遇到 open ./images/: is a Directory

这是路径拼接出错的典型表现:你把目录当成了文件名,或者没提取出原始文件后缀。

  • 常见错误现象:用 filepath.Join("./images", imgURL) 直接保存,但 imgURL 是完整 URL(如 https://a.b/c/d.jpg),导致路径变成 ./images/https://a.b/c/d.jpg,系统试图在 ./images/ 下创建名为 https: 的子目录,失败
  • 正确做法:先用 url.Parse 解析 URL,再用 path.Base 取文件名,最后用 filepath.Clean 过滤掉危险路径段(如 ../
  • 示例关键行:
    u, _ := url.Parse(imgURL) filename := path.Base(u.Path) safeName := filepath.Clean(filename) if safeName == "." || safeName == "/" { safeName = "image.png" } dstPath := filepath.Join("./images", safeName)
  • 性能影响:filepath.Clean 开销极小,但不加它可能被恶意 URL 注入路径遍历漏洞

并发下载图片时 goroutine 泄漏或连接超时

没控制并发数 + 没设超时,几秒内起几百个 goroutine,HTTP 连接积,最终卡死或报 context deadline exceeded

立即学习go语言免费学习笔记(深入)”;

  • 使用场景:遍历 200 个图片链接,用 go downloadOne(...) 全部启动,不加限制
  • 必须做两件事:① 用带缓冲的 channelsemaphore 控制并发数(建议 5–10);② 给每个 http.RequestContext 超时(如 10 秒)
  • 简单限流示例:
    sem := make(chan struct{}, 5) for _, u := range urls {     sem <- struct{}{} // 阻塞直到有空位     go func(urlStr string) {         defer func() { <-sem }()         // 下载逻辑     }(u) }
  • 容易被忽略的点:HTTP client 复用很重要——别在每个 goroutine 里新建 http.Client,全局一个,只改它的 Timeout 字段

事情说清了就结束。真正难的不是写正则或发请求,而是把 URL 解析、路径净化、并发控制、错误恢复这四层嵌套逻辑,在不堆 try-catch 的前提下稳住。

text=ZqhQzanResources