用 http.Get 下载文件返回 403 是因服务端拦截无 User-Agent 的“裸请求”;需手动设置 User-Agent、Referer 等头,配合 io.Copy 流式下载、合理文件打开模式及超时重试机制。

用 http.Get 下载文件时为什么返回 403?
很多 URL(尤其是 GitHub raw、CDN 或带防盗链的资源)默认拒绝 http.Get 的默认 User-Agent,直接返回 403。这不是代码写错了,而是服务端主动拦截。
- 必须显式设置
req.Header.Set("User-Agent", "Mozilla/5.0"),否则多数静态资源站会拒收 - 某些站点还需 Referer,比如
req.Header.Set("Referer", "https://example.com") - 如果目标是 GitHub raw 链接,注意它对未认证请求有速率限制,连续失败后可能临时封 IP
io.Copy 和 io.CopyN 怎么选?
下载大文件时,别用 bytes.Buffer 全部读进内存再写盘——容易 OOM。直接流式写入最稳妥。
- 用
io.Copy(dst, src):适合完整下载,自动处理分块、缓冲、错误传播 - 用
io.CopyN(dst, src, n):只取前n字节,适合截断下载或校验头信息(如判断是否为 PNG) - 避免自己手写
for { n, _ := src.Read(buf); dst.Write(buf[:n]) }——容易漏错误、忽略 partial write、没设超时
保存文件前必须检查 os.OpenFile 的 flag
用 os.Create 看似简单,但它等价于 os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666),而 O_TRUNC 会清空已有文件——哪怕只是想追加日志或断点续传,这都危险。
- 覆盖写入:用
os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) - 追加写入:用
os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) - 断点续传需先
os.Stat拿当前大小,再设req.Header.Set("Range", "bytes="+strconv.FormatInt(offset, 10)+"-"),且服务端得支持 Range
超时和重试逻辑不能靠裸 http.Client
默认 http.DefaultClient 没设超时,DNS 卡住、TCP 握手挂起、响应迟迟不来都会让 goroutine 无限等待。
立即学习“go语言免费学习笔记(深入)”;
- 必须自定义 client:
http.Client{Timeout: 30 * time.Second},或更细粒度控制Transport的DialContext、ResponseHeaderTimeout - 简单重试建议用指数退避:第一次 1s 后重试,第二次 2s,第三次 4s……最多 3 次;别无脑
for i := 0; i - 注意:重试时要重新构造
*http.Request,因为 body 可能已被读取过,不可复用
真正麻烦的是重定向跳转后的 Referer 清除、HTTPS 证书校验失败、gzip 响应体未自动解压——这些不显式处理,看似下载成功,实际存的是压缩乱码或跳转 HTML。


















