应使用 xml.decoder 流式解析 rss,避免 xml.unmarshal 导致 oom;需手动跳过声明与根节点、用 xmlname 和 ",any" 处理松散规范;http.client 必须设超时与连接复用;pubdate 解析需多 layout 尝试并 fallback。

Go 用 xml.Decoder 流式解析 RSS,别用 xml.Unmarshal
RSS feed 体积动辄几 MB,一次性读进内存再解码,xml.Unmarshal 很容易 OOM 或卡死。流式解析是唯一靠谱做法——用 xml.Decoder 边读边处理,内存占用稳定在 KB 级。
常见错误是先 http.Get 拿到 *http.Response.Body,直接传给 xml.Unmarshal,结果 panic:invalid memory address or nil pointer dereference(因为 Body 已被关闭或未读完)。
- 始终用
xml.NewDecoder(resp.Body),别碰io.ReadAll或bytes.Buffer - 手动调用
decoder.Token()跳过声明和根节点,定位到第一个<item></item> - 遇到
xml.StartElement且 Name.Local =="item"时,启动子解码器解析该节点内字段 - 务必在循环末尾调用
decoder.Skip()跳过已处理的,否则下次Token()会卡住
RSS 字段映射到 Go struct 时,XMLName 和 xml:",any" 很关键
RSS 规范松散,不同源字段名不一致(比如有的用 <title></title>,有的嵌套在 <title></title>),硬写结构体字段会漏数据。得靠 XMLName 捕获原始标签名,再用 xml:",any" 接收未知子节点。
典型场景:想兼容 Atom(<entry></entry>)和 RSS 2.0(<item></item>),又不想写两套逻辑。
立即学习“go语言免费学习笔记(深入)”;
- 定义字段如
Title string `xml:"title"`只能捕获直系<title></title>;若内容在<content></content>里,必须加命名空间前缀:MediaContent string `xml:"media:content"` - 用
XMLName xml.Name `xml:"item"`记录当前节点类型,后续可做分支判断 -
Other map[string]string `xml:",any"`能兜底抓取所有未声明字段,但注意:它只存文本内容,丢弃嵌套结构 - 别依赖
xml:",chardata"直接读 CDATA——RSS 里常混有 HTML,需额外用golang.org/x/net/html清洗
并发拉取多个 RSS 源时,http.Client 超时与连接复用必须手动设
默认 http.DefaultClient 没设超时,某个源挂掉会导致整个订阅器卡死;同时没限制连接数,100 个源并发可能打爆本地端口或远端限流。
错误现象:程序跑着跑着 CPU 100%,netstat 显示大量 TIME_WAIT 连接;或某次请求耗时 30 秒以上才返回 timeout。
- 创建自定义 client:
client := &http.Client{Timeout: 15 * time.Second} - 显式配置
Transport:&http.Transport{MaxIdleConns: 20, MaxIdleConnsPerHost: 20, IdleConnTimeout: 30 * time.Second} - 每个 goroutine 复用同一个 client 实例,别每次 new
- 对失败 URL 做指数退避重试(如首次 1s 后重试,最多 3 次),避免雪崩
列表展示前,时间字段 pubDate 解析最容易出错
RSS 的 <pubdate></pubdate> 格式五花八门:RFC 822(Mon, 01 Jan 2024 00:00:00 GMT)、ISO 8601(2024-01-01T00:00:00Z)、甚至中文格式(2024年1月1日)。Go 的 time.Parse 默认只认 RFC 3339,其他全报 parsing time xxx: month out of range。
使用场景:按发布时间倒序渲染 Feed 列表,时间解析失败会导致排序乱序或 panic。
- 预定义多个 layout 字符串,按顺序尝试
time.Parse,直到成功为止 - 优先用
time.RFC1123Z(带时区缩写)和time.RFC3339,再 fallback 到自定义 layout 如"Mon, 02 Jan 2006 15:04:05 MST" - 解析失败时,退化为当前时间(
time.Now()),别让单条数据拖垮整个列表 - 别用
time.ParseInLocation硬指定本地时区——RSS 时间本意是 UTC 或明确时区,强行转成本地会错乱
真正麻烦的是混合时区字段(比如 <date></date> 用 ISO,<pubdate></pubdate> 用 RFC 822),得各自解析后统一转成 time.Time 再比较。这一步没法偷懒,字段来源一多,就得建个小路由表做分发。










