Skip to content

Извлечение контента на практике

Это руководство поможет вам понять принципы работы и лучшие практики извлечения HTML-контента через реальные сценарии.

Обзор процесса извлечения

При вызове Extract библиотека выполняет следующие шаги:

text
Ввод HTML → валидация ввода → определение кодировки (автоконвертация в UTF-8) → разбор DOM → проверка глубины
    → безопасное санирование (опц.) → распознавание статьи (опц.) → извлечение содержимого → форматирование → возврат Result

Проверка глубины выполняется до санирования: сначала итеративно проверяется глубина DOM (чтобы избежать переполнения стека из-за рекурсивного обхода), а затем выполняется безопасное санирование разобранного DOM-дерева. Обе операции работают с разобранным деревом узлов, поэтому разбор DOM всегда предшествует им обеим.

Каждый шаг можно настроить через конфигурацию.

Базовое извлечение текста

Простейшее использование — извлечение контента из байтов HTML:

go
package main

import (
    "fmt"
    "log"

    "github.com/cybergodev/html"
)

func main() {
    data := []byte(`<html>
        <head><title>Руководство по Go</title></head>
        <body>
            <article>
                <h1>Введение в Go</h1>
                <p>Go — это статически типизированный компилируемый язык со встроенной поддержкой параллелизма.</p>
                <p>Он отличается быстрой компиляцией, простотой развёртывания и подходит для создания высокопроизводительных сервисов.</p>
                <img src="gopher.png" alt="Талисман Gopher" />
                <a href="https://go.dev">Официальный сайт Go</a>
            </article>
        </body>
    </html>`)

    result, err := html.Extract(data)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Заголовок:", result.Title)
    // Заголовок: Руководство по Go

    fmt.Println("Текст:", result.Text)
    // Текст: Введение в Go
    //       Go — это статически типизированный компилируемый язык со встроенной поддержкой параллелизма.
    //       Он отличается быстрой компиляцией, простотой развёртывания и подходит для создания высокопроизводительных сервисов.
    //       Официальный сайт Go

    fmt.Println("Слов:", result.WordCount)
    // Слов: 7

    fmt.Println("Время чтения:", result.ReadingTime)
    // Время чтения: 2.1с (при расчёте 200 слов/мин)

    fmt.Println("Изображений:", len(result.Images))
    // Изображений: 1

    fmt.Println("Ссылок:", len(result.Links))
    // Ссылок: 1
}

Понимание результата извлечения

Result содержит следующие поля:

ПолеТипОписание
TitlestringЗаголовок страницы, приоритет: <title>, затем <h1>, <h2>
TextstringОсновной текст (очищенный, без тегов и лишних пробелов)
Images[]ImageInfoСписок извлечённых изображений
Links[]LinkInfoСписок извлечённых ссылок
Videos[]VideoInfoСписок извлечённых видео
Audios[]AudioInfoСписок извлечённых аудио
WordCountintКоличество слов в тексте
ReadingTimetime.DurationРасчётное время чтения (200 слов/мин)
ProcessingTimetime.DurationВремя обработки

Извлечение из файла

Для обработки локальных HTML-файлов используйте ExtractFromFile:

go
result, err := html.ExtractFromFile("article.html")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Заголовок:", result.Title)

Файловые операции включают встроенные проверки безопасности:

  • Автоматическое обнаружение атак с обходом пути (например, ../../../etc/passwd)
  • Размер файла ограничен MaxInputSize
  • Сообщения об ошибках скрывают полный путь через SafePath()

Алгоритм распознавания статей

Когда ExtractArticle равен true (по умолчанию), библиотека автоматически определяет «основную область контента» на странице.

Принцип работы

  1. Оценка кандидатов: обход дерева DOM, вычисление оценки релевантности контента для каждого элемента
  2. Выбор лучшего кандидата: выбор узла с наивысшей оценкой в качестве контейнера статьи
  3. Механизм отката: если подходящий кандидат не найден, откат к узлу <body>

Подходящие сценарии

Распознавание статей лучше всего подходит для новостных сайтов, блогов, документации и других страниц с чётко выраженной «областью основного контента». Для навигационных страниц и страниц-списков может быть затруднено точное определение основного текста.

Пользовательский скоринг

Настройка логики скоринга через реализацию интерфейса Scorer:

go
type myScorer struct{}

func (s myScorer) Score(node html.ContentNode) int {
    // Возвращаем оценку на основе характеристик узла
    class := node.AttrValue("class")
    if strings.Contains(class, "article") || strings.Contains(class, "post") {
        return 100
    }
    if strings.Contains(class, "sidebar") || strings.Contains(class, "comment") {
        return -50
    }
    return 0
}

func (s myScorer) ShouldRemove(node html.ContentNode) bool {
    // Возвращаем true для удаления узла
    return node.Data() == "nav" || node.Data() == "footer"
}

Примечание

Функция strings.Contains в этом примере из стандартного пакета strings. Полный работоспособный пример см. в Тестирование и пользовательские расширения.

Извлечение только текста

Когда нужен только текст без изображений, ссылок и других метаданных:

go
text, err := html.ExtractText(data)
if err != nil {
    log.Fatal(err)
}
fmt.Println(text)

Это особенно полезно для анализа текста, построения поисковых индексов и других сценариев.

Обработка не-UTF-8 кодировок

Библиотека автоматически определяет 15+ кодировок (включая UTF-8, GBK, Shift_JIS, Windows-1252 и др.) и автоматически конвертирует в UTF-8.

go
// Автоматическое определение кодировки
result, err := html.Extract(gbkEncodedData)

// Ручное указание кодировки
cfg := html.DefaultConfig()
cfg.Encoding = "gbk"
result, err = html.Extract(gbkEncodedData, cfg)

Контекст и тайм-аут

Для больших файлов или HTML из ненадёжных источников рекомендуется использовать версии с контекстом:

go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

result, err := html.ExtractWithContext(ctx, data)
if errors.Is(err, html.ErrProcessingTimeout) {
    log.Println("Тайм-аут обработки")
}

Следующие шаги