Skip to content

接口定义 ​

HTML 库定义了以下核心接口:

Extractor ​

HTML 内容提取的主要接口,Processor 实现了此接口。

go
type Extractor interface {
    // 核心提取
    Extract(htmlBytes []byte) (*Result, error)
    ExtractWithContext(ctx context.Context, htmlBytes []byte) (*Result, error)
    ExtractFromFile(filePath string) (*Result, error)
    ExtractFromFileWithContext(ctx context.Context, filePath string) (*Result, error)

    // 文本提取
    ExtractText(htmlBytes []byte) (string, error)
    ExtractTextFromFile(filePath string) (string, error)
    ExtractTextWithContext(ctx context.Context, htmlBytes []byte) (string, error)
    ExtractTextFromFileWithContext(ctx context.Context, filePath string) (string, error)

    // 格式化输出
    ExtractToMarkdown(htmlBytes []byte) (string, error)
    ExtractToMarkdownFromFile(filePath string) (string, error)
    ExtractToJSON(htmlBytes []byte) ([]byte, error)
    ExtractToJSONFromFile(filePath string) ([]byte, error)
    ExtractToMarkdownWithContext(ctx context.Context, htmlBytes []byte) (string, error)
    ExtractToMarkdownFromFileWithContext(ctx context.Context, filePath string) (string, error)
    ExtractToJSONWithContext(ctx context.Context, htmlBytes []byte) ([]byte, error)
    ExtractToJSONFromFileWithContext(ctx context.Context, filePath string) ([]byte, error)

    // 批量处理
    ExtractBatch(htmlContents [][]byte) *BatchResult
    ExtractBatchWithContext(ctx context.Context, htmlContents [][]byte) *BatchResult
    ExtractBatchFiles(filePaths []string) *BatchResult
    ExtractBatchFilesWithContext(ctx context.Context, filePaths []string) *BatchResult

    // 链接提取
    ExtractAllLinks(htmlBytes []byte) ([]LinkResource, error)
    ExtractAllLinksFromFile(filePath string) ([]LinkResource, error)
    ExtractAllLinksWithContext(ctx context.Context, htmlBytes []byte) ([]LinkResource, error)
    ExtractAllLinksFromFileWithContext(ctx context.Context, filePath string) ([]LinkResource, error)

    // 生命周期
    Close() error
}

方法一览 ​

方法说明
Extractor.Extract从 HTML 字节提取完整 Result
Extractor.ExtractWithContext同上,支持 context 取消
Extractor.ExtractFromFile从 HTML 文件提取
Extractor.ExtractFromFileWithContext同上,支持 context 取消
Extractor.ExtractText仅提取纯文本
Extractor.ExtractTextWithContext同上,支持 context 取消
Extractor.ExtractTextFromFile从文件提取纯文本
Extractor.ExtractTextFromFileWithContext同上,支持 context 取消
Extractor.ExtractToMarkdown提取并渲染为 Markdown
Extractor.ExtractToMarkdownWithContext同上,支持 context 取消
Extractor.ExtractToMarkdownFromFile从文件提取并渲染为 Markdown
Extractor.ExtractToMarkdownFromFileWithContext同上,支持 context 取消
Extractor.ExtractToJSON提取并序列化为 JSON
Extractor.ExtractToJSONWithContext同上,支持 context 取消
Extractor.ExtractToJSONFromFile从文件提取并序列化为 JSON
Extractor.ExtractToJSONFromFileWithContext同上,支持 context 取消
Extractor.ExtractBatch并发批量提取(字节切片输入)
Extractor.ExtractBatchWithContext同上,支持 context 取消
Extractor.ExtractBatchFiles并发批量提取(文件路径输入)
Extractor.ExtractBatchFilesWithContext同上,支持 context 取消
Extractor.ExtractAllLinks枚举全部链接资源(不应用清洗)
Extractor.ExtractAllLinksWithContext同上,支持 context 取消
Extractor.ExtractAllLinksFromFile从文件枚举链接资源
Extractor.ExtractAllLinksFromFileWithContext同上,支持 context 取消
Extractor.Close释放资源(缓存、后台 goroutine)

StatsProvider ​

统计信息和缓存管理接口。

go
type StatsProvider interface {
    GetStatistics() Statistics
    ClearCache()
    ResetStatistics()
}

方法一览 ​

方法说明
StatsProvider.GetStatistics返回当前处理统计
StatsProvider.ClearCache清空缓存内容,保留累计统计
StatsProvider.ResetStatistics重置所有统计计数器归零

Processor 同时实现了 Extractor 和 StatsProvider。通过接口类型引用 Processor,可以将「提取能力」与「监控能力」分别注入不同的消费者:

go
type ExtractionService struct {
    extractor     html.Extractor     // 只需要提取能力
    statsProvider html.StatsProvider // 只需要监控能力
}

func NewService(p *html.Processor) *ExtractionService {
    return &ExtractionService{
        extractor:     p, // *Processor 满足 Extractor
        statsProvider: p, // *Processor 满足 StatsProvider
    }
}

依赖注入与 Mock 测试 ​

Extractor 接口使提取逻辑可解耦,便于单元测试中注入 mock 实现:

go
// mockExtractor 实现 html.Extractor 接口,用于测试
type mockExtractor struct {
    result *html.Result
    err    error
}

func (m *mockExtractor) Extract([]byte) (*html.Result, error) { return m.result, m.err }
func (m *mockExtractor) ExtractWithContext(ctx context.Context, b []byte) (*html.Result, error) {
    return m.result, m.err
}
func (m *mockExtractor) ExtractFromFile(string) (*html.Result, error)         { return m.result, m.err }
func (m *mockExtractor) ExtractFromFileWithContext(context.Context, string) (*html.Result, error) {
    return m.result, m.err
}
func (m *mockExtractor) ExtractText([]byte) (string, error)                   { return m.result.Text, m.err }
func (m *mockExtractor) ExtractTextFromFile(string) (string, error)           { return m.result.Text, m.err }
func (m *mockExtractor) ExtractTextWithContext(context.Context, []byte) (string, error) {
    return m.result.Text, m.err
}
func (m *mockExtractor) ExtractTextFromFileWithContext(context.Context, string) (string, error) {
    return m.result.Text, m.err
}
// ... 其余方法返回零值

// 业务代码依赖接口而非具体类型
type ArticleService struct {
    extractor html.Extractor
}

func (s *ArticleService) GetTitle(htmlBytes []byte) (string, error) {
    result, err := s.extractor.Extract(htmlBytes)
    if err != nil {
        return "", err
    }
    return result.Title, nil
}

// 测试中注入 mock
func TestGetTitle(t *testing.T) {
    svc := &ArticleService{
        extractor: &mockExtractor{
            result: &html.Result{Title: "测试标题"},
        },
    }
    title, err := svc.GetTitle([]byte("<html></html>"))
    assert.NoError(t, err)
    assert.Equal(t, "测试标题", title)
}

ContentNode ​

HTML 节点的抽象接口,用于内容评分算法。Type、Data、AttrValue、Attrs 提供节点信息的只读访问,FirstChild、NextSibling、Parent 用于遍历节点树。

go
type ContentNode interface {
    Type() string                    // 节点类型 ("element", "text", "comment" 等)
    Data() string                    // 标签名或文本内容
    AttrValue(key string) string     // 属性值
    Attrs() []NodeAttr               // 所有属性
    FirstChild() ContentNode         // 第一个子节点
    NextSibling() ContentNode        // 下一个兄弟节点
    Parent() ContentNode             // 父节点
}

方法一览 ​

方法签名说明
ContentNode.TypeType() string节点类型:"element"、"text"、"comment"、"document"、"doctype"、"error"、"raw" 或 "unknown"
ContentNode.DataData() string元素节点的标签名(如 "div"、"p"),文本节点则为文本内容
ContentNode.AttrValueAttrValue(key string) string按属性名取值,属性不存在时返回空字符串
ContentNode.AttrsAttrs() []NodeAttr返回节点全部属性
ContentNode.FirstChildFirstChild() ContentNode第一个子节点,无则返回 nil
ContentNode.NextSiblingNextSibling() ContentNode下一个兄弟节点,无则返回 nil
ContentNode.ParentParent() ContentNode父节点,根节点时返回 nil

Scorer ​

内容评分算法接口,用于自定义文章识别策略。

go
type Scorer interface {
    Score(node ContentNode) int          // 计算节点相关性分数
    ShouldRemove(node ContentNode) bool  // 判断节点是否应移除
}

方法一览 ​

方法签名说明
Scorer.ScoreScore(node ContentNode) int计算节点的内容相关性分数
Scorer.ShouldRemoveShouldRemove(node ContentNode) bool判断节点是否应从内容树移除

注意:实现必须并发安全

当单个 Processor 被多个并发 Extract 调用共享时,会从多个 goroutine 同时触发 Score/ShouldRemove。因此任何 Scorer 实现都必须自身保证并发安全。

库内置的默认评分器只读、天然满足并发安全;自定义 Scorer 若持有可变状态(如缓存、计数器),必须自行加锁同步。

通过 Config.Scorer 字段注入自定义评分器:

go
type MyScorer struct{}

func (s *MyScorer) Score(node html.ContentNode) int {
    // 自定义评分逻辑
    return 0
}

func (s *MyScorer) ShouldRemove(node html.ContentNode) bool {
    // 自定义移除逻辑
    return false
}

cfg := html.DefaultConfig()
cfg.Scorer = &MyScorer{}

ContentNode 与内部类型的关系 ​

ContentNode 是一个抽象接口,屏蔽了底层 golang.org/x/net/html.Node 类型。库内部通过 contentNodeAdapter 将 *html.Node 包装为 ContentNode:

text
调用方 Scorer.Score(ContentNode)
                          │
                    contentNodeAdapter(适配器)
                          │
                  内部 *html.Node(golang.org/x/net/html)

这种双层接口设计实现了:

  • 公共 API 干净——用户实现自定义 Scorer 无需导入 golang.org/x/net/html
  • 内部高性能——库内部的 DefaultScorer 直接操作 *html.Node(通过内部 Scorer 接口),避免适配层开销
  • 适配由库自动完成——scorerAdapter 在公共 Scorer 和内部 Scorer 之间双向转换,用户无感知

ContentNode 返回 nil 的约定

FirstChild、NextSibling、Parent 在没有对应节点时返回 nil。自定义 Scorer 遍历子树时务必做 nil 检查,否则会 panic。

AuditSink ​

审计日志输出接口。

go
type AuditSink interface {
    Write(entry AuditEntry)
    Close() error
}

内置 Sink 实现详见 审计系统。