编码检测实战
HTML 文档可能使用各种字符编码(GBK、Shift_JIS、Windows-1252 等)。库内置自动编码检测,从 HTML 字节识别编码并转 UTF-8,支持 15+ 种编码,无需手动处理。
检测优先级
库按以下顺序确定输入编码,依次尝试,首个命中即生效:
| 优先级 | 来源 | 说明 |
|---|---|---|
| ① 最高 | Config.Encoding 手动指定 | 非空时直接使用,跳过所有自动检测 |
| ② | HTML meta 标签声明 | <meta charset> 或 http-equiv="content-type",扫描前 1024 字节 |
| ③ | 统计算法智能检测 | 采样最多 10KB,置信度 ≥ 80 才采纳 |
| ④ 兜底 | UTF-8 | 以上均未命中时回退 UTF-8 |
Config.Encoding 非空?── 是 ──→ 直接使用
│
否
│
meta 标签声明编码?── 是 ──→ 使用声明值
│
否
│
统计算法置信度 ≥ 80?── 是 ──→ 采纳统计结果
│
否
│
└──→ UTF-8BOM 检测
除上述四级外,库还会检测 BOM(字节顺序标记):UTF-8 BOM(EF BB BF)、UTF-16 LE BOM(FF FE)、UTF-16 BE BOM(FE FF)。BOM 存在时直接确定编码。
支持的编码
| 分类 | 编码 | 备注 |
|---|---|---|
| Unicode | UTF-8、UTF-16LE、UTF-16BE | 默认回退 UTF-8 |
| 西欧 | Windows-1252、ISO-8859-1、ISO-8859-15 | ISO-8859-15 含欧元符号 |
| 中欧 | Windows-1250 | — |
| 西里尔 | Windows-1251 | 俄语等 |
| 简体中文 | GBK | 别名 gb2312 自动归一化为 gbk |
| 繁体中文 | Big5 | — |
| 日语 | Shift_JIS、EUC-JP | — |
| 韩语 | EUC-KR | — |
编码别名归一化
编码名称和别名不区分大小写,自动归一化为标准名:
| 输入别名 | 归一化结果 |
|---|---|
gb2312、GB2312 | gbk |
sjis、x-sjis、shift-jis | shift_jis |
latin1、latin-1 | iso-8859-1 |
utf8、utf_8 | utf-8 |
8859-1、iso88591 | iso-8859-1 |
cp1252、windows1252 | windows-1252 |
自动检测示例
GBK 编码的中文 HTML,通过 meta 标签声明自动识别:
package main
import (
"fmt"
"log"
"github.com/cybergodev/html"
"golang.org/x/text/encoding/simplifiedchinese"
)
func main() {
// GBK 编码的中文 HTML(meta 标签声明 charset=gbk)
gbkHTML := `<html><head><meta charset="gbk">` +
`<title>中文网页</title></head>` +
`<body><article><h1>你好世界</h1>` +
`<p>这是一段中文内容。</p></article></body></html>`
// 将 UTF-8 字符串编码为 GBK 字节(模拟真实 GBK 网页)
gbkBytes, err := simplifiedchinese.GBK.NewEncoder().Bytes([]byte(gbkHTML))
if err != nil {
log.Fatal(err)
}
// 自动检测编码并提取(从 meta charset 识别 GBK,转 UTF-8 后提取)
result, err := html.Extract(gbkBytes)
if err != nil {
log.Fatal(err)
}
fmt.Println("标题:", result.Title)
// 标题:中文网页
fmt.Println("正文:", result.Text)
// 正文:你好世界
// 这是一段中文内容。
}手动指定编码
当 meta 标签缺失、声明错误,或自动检测结果不确定时,通过 Config.Encoding 强制指定:
cfg := html.DefaultConfig()
cfg.Encoding = "gbk"
result, err := html.Extract(gbkBytes, cfg)| 适用场景 | 说明 |
|---|---|
| 已知来源编码 | 从 HTTP Content-Type 头获取编码,直接指定避免误检 |
| meta 标签缺失 | 无 <meta charset> 声明的旧网页 |
| 自动检测出错 | 统计算法置信度不足,结果不正确 |
Config.Encoding 优先级最高
设置 Config.Encoding 后,库完全跳过自动检测,直接使用指定编码解码。适合确定性场景,避免统计检测的不确定性。
Shift_JIS 自动检测实战
日文网页常使用 Shift_JIS 编码。即使没有 meta 声明,统计算法也能识别:
package main
import (
"fmt"
"log"
"github.com/cybergodev/html"
"golang.org/x/text/encoding/japanese"
)
func main() {
// Shift_JIS 编码的日文 HTML(无 meta charset 声明)
sjisHTML := `<html><head><title>日本語ページ</title></head>` +
`<body><article><h1>こんにちは</h1>` +
`<p>東京の天気は晴れです。</p></article></body></html>`
// 编码为 Shift_JIS 字节
sjisBytes, err := japanese.ShiftJIS.NewEncoder().Bytes([]byte(sjisHTML))
if err != nil {
log.Fatal(err)
}
// 统计算法自动识别 Shift_JIS(采样字节分析日文字符分布)
result, err := html.Extract(sjisBytes)
if err != nil {
log.Fatal(err)
}
fmt.Println("标题:", result.Title)
// 标题:日本語ページ
fmt.Println("正文:", result.Text)
// 正文:こんにちは
// 東京の天気は晴れです。
}Windows-1252 手动指定
西欧编码(含 é、€ 等字符)可手动指定:
package main
import (
"fmt"
"log"
"github.com/cybergodev/html"
"golang.org/x/text/encoding/charmap"
)
func main() {
// Windows-1252 编码的西欧文本
winHTML := `<html><head><title>Café Menu</title></head>` +
`<body><article><h1>Café</h1>` +
`<p>Price: 100 €. Résumé available.</p></article></body></html>`
winBytes, err := charmap.Windows1252.NewEncoder().Bytes([]byte(winHTML))
if err != nil {
log.Fatal(err)
}
// 手动指定 Windows-1252 编码
cfg := html.DefaultConfig()
cfg.Encoding = "windows-1252"
result, err := html.Extract(winBytes, cfg)
if err != nil {
log.Fatal(err)
}
fmt.Println("标题:", result.Title)
// 标题:Café Menu
fmt.Println("正文:", result.Text)
// 正文:Café
// Price: 100 €. Résumé available.
}编码检测失败
当编码检测或转换失败时(如数据损坏、使用了不支持的编码),返回 wrapping error:
result, err := html.Extract(data)
if err != nil {
if strings.Contains(err.Error(), "encoding detection failed") {
// 编码检测失败,fallback 手动指定
cfg := html.DefaultConfig()
cfg.Encoding = "windows-1252"
result, err = html.Extract(data, cfg)
if err != nil {
log.Fatal(err)
}
} else {
log.Fatal(err)
}
}错误消息格式
编码检测失败的错误消息固定包含 "encoding detection failed" 前缀,可用 strings.Contains 匹配。检测失败时建议 fallback 到手动指定编码。
审计记录
启用审计后,编码检测问题会记录为 AuditEventEncodingIssue(info 级别):
package main
import (
"fmt"
"log"
"github.com/cybergodev/html"
)
func main() {
cfg := html.DefaultConfig()
cfg.Audit = html.DefaultAuditConfig()
cfg.Audit.Enabled = true
// LogEncodingIssues 默认为 true(DefaultAuditConfig 已启用)
p, err := html.New(cfg)
if err != nil {
log.Fatal(err)
}
defer p.Close()
// 处理 HTML(编码问题时自动记录到审计日志)
p.Extract([]byte(`<html><body><p>content</p></body></html>`))
// 查询审计日志中的编码事件
for _, entry := range p.GetAuditLog() {
if entry.EventType == html.AuditEventEncodingIssue {
fmt.Printf("[编码问题] %s\n", entry.Message)
}
}
fmt.Println("编码事件检查完成")
// 编码事件检查完成
}触发条件
AuditEventEncodingIssue 仅在编码检测或转换失败时记录(如使用了不支持的编码且数据非有效 UTF-8)。正常文档不会产生此事件。编码问题属于 info 级别(最低),表明数据可能未被完美解码但不影响安全性。如需过滤,使用 LevelFilteredSink 设置最低级别为 warning 即可排除。