Определение интерфейсов
Библиотека HTML определяет следующие ключевые интерфейсы:
Extractor
Основной интерфейс извлечения HTML-контента, Processor реализует этот интерфейс.
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
}StatsProvider
Интерфейс статистики и управления кэшем.
type StatsProvider interface {
GetStatistics() Statistics
ClearCache()
ResetStatistics()
}Processor одновременно реализует Extractor и StatsProvider. Ссылаясь на Processor через тип интерфейса, можно внедрить «способность извлечения» и «способность мониторинга» разным потребителям по отдельности:
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-реализаций в модульных тестах:
// 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 ContentNode interface {
Type() string // Тип узла ("element", "text", "comment" и др.)
Data() string // Имя тега или текстовое содержимое
AttrValue(key string) string // Значение атрибута
Attrs() []NodeAttr // Все атрибуты
FirstChild() ContentNode // Первый дочерний узел
NextSibling() ContentNode // Следующий родственный узел
Parent() ContentNode // Родительский узел
}Scorer
Интерфейс алгоритма скоринга контента для пользовательской стратегии распознавания статей.
type Scorer interface {
Score(node ContentNode) int // Вычисление оценки релевантности узла
ShouldRemove(node ContentNode) bool // Определение, следует ли удалить узел
}Предупреждение
Когда один Processor разделяется между параллельными вызовами Extract, Score/ShouldRemove могут вызываться из нескольких горутин одновременно. Поэтому любая реализация Scorer должна самостоятельно обеспечивать потокобезопасность.
Встроенный скорер библиотеки по умолчанию доступен только для чтения и изначально потокобезопасен; пользовательский Scorer, хранящий изменяемое состояние (например, кэш или счётчик), должен самостоятельно выполнять блокировку и синхронизацию.
Внедрение пользовательского скорера через поле Config.Scorer:
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:
Вызывающая сторона Scorer.Score(ContentNode)
│
contentNodeAdapter (адаптер)
│
внутренний *html.Node (golang.org/x/net/html)Такой двухуровневый дизайн интерфейса обеспечивает:
- Чистый публичный API — пользователю не нужно импортировать
golang.org/x/net/htmlдля реализации собственного Scorer - Высокую производительность внутри — внутренний
DefaultScorerработает напрямую с*html.Node(через внутренний интерфейсScorer), избегая накладных расходов адаптера - Адаптация выполняется библиотекой автоматически —
scorerAdapterдвусторонне преобразует между публичнымScorerи внутреннимScorer, прозрачно для пользователя
Соглашение о возврате nil для ContentNode
FirstChild()/NextSibling()/Parent() возвращают nil, если соответствующего узла нет. Пользовательский Scorer при обходе поддерева обязан выполнять nil-проверку, иначе возникнет panic.
AuditSink
Интерфейс вывода журнала аудита.
type AuditSink interface {
Write(entry AuditEntry)
Close() error
}Встроенные реализации Sink описаны в Система аудита.