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

为什么 regexp.Compile 会 panic 而不是返回 error?
因为传了非法正则表达式(比如未闭合的括号、无效转义),regexp.Compile 会直接 panic,而不是像多数 Go 函数那样返回 error。这是它和 regexp.CompilePOSIX 的关键区别。
- 常见错误现象:
panic: regexp: Compile(`!\[.*?\]\((.*?)\)`) : error parsing regexp: missing closing ): `![.*?\]\((.*?)`—— 实际上是字符串里少了一个右括号,但错误信息只截断显示,容易误判 - 使用场景:解析 Markdown 图片语法
时,若正则写成`!\[.*?\]\((.*?)`(漏了末尾)),就会 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 Forbidden,curl 或浏览器手动访问却正常 - 解决方法:手动设置请求头,不要直接用
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(...)全部启动,不加限制 - 必须做两件事:① 用带缓冲的 channel 或
semaphore控制并发数(建议 5–10);② 给每个http.Request设Context超时(如 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 的前提下稳住。










