Processor
Processor — это основной движок обработки библиотеки HTML. По сравнению с функциями пакета Processor повторно использует внутренние ресурсы (кэш, детекторы кодировок), что подходит для высокочастотных вызовов.
Создание
New
Создаёт экземпляр Processor с возможностью передачи конфигурации.
func New(cfg ...Config) (*Processor, error)Параметры: максимум один Config; если не предоставлен, используется DefaultConfig().
p, err := html.New(html.DefaultConfig())
if err != nil {
log.Fatal(err)
}
defer p.Close()Внутренняя инициализация:
New не просто присваивает значения — он выполняет следующие шаги, чтобы возвращённый Processor был сразу готов к использованию:
- Проверка конфигурации: вызывается
Config.Validate(); при недопустимой конфигурации возвращается*ConfigError(errors.Is(err, ErrInvalidConfig)истинно). Проверка охватывает числовые границы (MaxInputSize,MaxCacheEntries,WorkerPoolSize,MaxDepthне могут быть отрицательными/превышать предел) и строки форматов (InlineImageFormat/InlineLinkFormat/TableFormatпринимают допустимые значения). - Установка Scorer: если настроен пользовательский
Scorer, он адаптируется к внутреннему интерфейсу черезscorerAdapter; иначе используетсяSharedDefaultScorer(только для чтения, потокобезопасный). - Предварительный расчёт строк формата:
InlineImageFormat/InlineLinkFormatнормализуются (нижний регистр + удаление пробелов, пустая строка отображается в"none") и кэшируются в поляimageFormat/linkFormat, чтобы избежать повторныхstrings.ToLowerв горячем пути. - Запуск очистки кэша: фоновый goroutine очистки запускается только если
CacheTTL>0иCacheCleanup>0; если хотя бы один из них равен 0, goroutine не запускается.
Потокобезопасность
Параллельное использование
Processor можно безопасно разделять между несколькими goroutine без дополнительной блокировки. Гарантии параллельности обеспечиваются:
- Неизменная конфигурация:
configнеизменяем послеNew()(указатель*Configникогда не переназначается и не изменяется), поэтому форматирующие методы вродеExtractToMarkdownмогут безопасно делать копию по значению для создания временного Processor без какой-либо блокировки — переопределение форматов никогда не записывается обратно в общую конфигурацию. - Счётчики статистики:
TotalProcessed/CacheHits/CacheMisses/ErrorCount/totalProcessTimeполностью используют операцииatomic. - Кэш: внутренний
Cacheимеет собственную блокировку, чтение и запись безопасны. - Scorer: встроенный
DefaultScorerдоступен только для чтения. ПользовательскийScorerдолжен сам обеспечивать потокобезопасность (например, удерживать внутреннюю блокировку), так как один Processor при параллельныхExtractвызывает егоScore/ShouldRemoveиз нескольких goroutine.
Извлечение контента
Ошибки возврата
Семейство методов Extract возвращает явные сигнатурные ошибки на различных этапах обработки, которые можно точно определить через errors.Is:
| Ошибка | Условие срабатывания | Примечание |
|---|---|---|
ErrProcessorClosed | p равен nil или уже Close | Общее для всех методов |
ErrInputTooLarge | Размер ввода в байтах превышает MaxInputSize | Обёрнуто в *InputError, содержит фактический/лимитный размер |
| Ошибка определения кодировки | Сбой определения кодировки или конвертации в UTF-8 | Исходная ошибка обёрнута |
ErrInvalidHTML | Байты не могут быть разобраны как HTML | Нижележащая ошибка парсинга также обёрнута |
ErrMaxDepthExceeded | Глубина вложенности элементов превышает MaxDepth | Итеративная проверка, предотвращает переполнение стека |
ErrProcessingTimeout | Обработка заняла больше времени, чем ProcessingTimeout | ProcessingTimeout=0 означает отсутствие лимита |
ErrInternalPanic | Внутренний непредвиденный panic был восстановлен | Защитный механизм, не должен встречаться при нормальном использовании |
Версии с context также могут возвращать context.Canceled (пользовательская отмена) или context.DeadlineExceeded (тайм-аут контекста, нормализованный до ErrProcessingTimeout).
Extract
func (p *Processor) Extract(htmlBytes []byte) (*Result, error)Извлекает контент из HTML-байтов с автоматическим определением кодировки.
ExtractFromFile
func (p *Processor) ExtractFromFile(filePath string) (*Result, error)Извлекает контент из файла.
ExtractText
func (p *Processor) ExtractText(htmlBytes []byte) (string, error)Возвращает только чистый текст.
ExtractTextFromFile
func (p *Processor) ExtractTextFromFile(filePath string) (string, error)Извлекает чистый текст из файла.
Версии с контекстом
Все методы извлечения имеют версии с ExtractWithContext:
func (p *Processor) ExtractWithContext(ctx context.Context, htmlBytes []byte) (*Result, error)
func (p *Processor) ExtractFromFileWithContext(ctx context.Context, filePath string) (*Result, error)
func (p *Processor) ExtractTextWithContext(ctx context.Context, htmlBytes []byte) (string, error)
func (p *Processor) ExtractTextFromFileWithContext(ctx context.Context, filePath string) (string, error)Форматы вывода
func (p *Processor) ExtractToMarkdown(htmlBytes []byte) (string, error)
func (p *Processor) ExtractToMarkdownFromFile(filePath string) (string, error)
func (p *Processor) ExtractToJSON(htmlBytes []byte) ([]byte, error)
func (p *Processor) ExtractToJSONFromFile(filePath string) ([]byte, error)Версии с контекстом:
func (p *Processor) ExtractToMarkdownWithContext(ctx context.Context, htmlBytes []byte) (string, error)
func (p *Processor) ExtractToMarkdownFromFileWithContext(ctx context.Context, filePath string) (string, error)
func (p *Processor) ExtractToJSONWithContext(ctx context.Context, htmlBytes []byte) ([]byte, error)
func (p *Processor) ExtractToJSONFromFileWithContext(ctx context.Context, filePath string) ([]byte, error)Особенности поведения кэша
Эти два метода принципиально различаются в обработке кэша:
ExtractToMarkdownсоздаёт временный Processor (копирует неизменяемуюconfig, но обнуляетMaxCacheEntriesи отключает аудит), не читает и не пишет основной кэш, поэтому не загрязняет и не попадает в кэш основного Processor. Результат в формате Markdown также не кэшируется.ExtractToJSONнапрямую вызываетp.Extractи проходит через обычный кэш-путь — попадает/записывает в основной кэш, счётчики статистики также обновляются.
Если вы хотите, чтобы Markdown-вывод также пользовался кэшем, создайте отдельный Processor с MarkdownConfig() и вызывайте Extract, либо кэшируйте вывод самостоятельно.
Извлечение ссылок
func (p *Processor) ExtractAllLinks(htmlBytes []byte) ([]LinkResource, error)
func (p *Processor) ExtractAllLinksFromFile(filePath string) ([]LinkResource, error)
func (p *Processor) ExtractAllLinksWithContext(ctx context.Context, htmlBytes []byte) ([]LinkResource, error)
func (p *Processor) ExtractAllLinksFromFileWithContext(ctx context.Context, filePath string) ([]LinkResource, error)Пакетная обработка
func (p *Processor) ExtractBatch(htmlContents [][]byte) *BatchResult
func (p *Processor) ExtractBatchWithContext(ctx context.Context, htmlContents [][]byte) *BatchResult
func (p *Processor) ExtractBatchFiles(filePaths []string) *BatchResult
func (p *Processor) ExtractBatchFilesWithContext(ctx context.Context, filePaths []string) *BatchResultСтатистика и кэш
Подробное описание поведения кэша
Когда MaxCacheEntries > 0, Extract включает кэширование:
- Путь попадания: обнаружив запись в кэше, увеличивает
CacheHitsиTotalProcessedна 1 и возвращаетcloneResult— глубокую копию срезовImages/Links/Videos/Audiosчерезcopy. Изменения возвращаемого значения вызывающей стороной не влияют на записи в кэше, а также предотвращается гонка данных при одновременных попаданиях. - Путь промаха: после завершения обработки результат записывается в кэш, после чего возвращается копия
cloneResult(также глубокая копия). Таким образом, кэш-запись и возвращаемое значение не являются псевдонимами. - Отключение кэша: при
MaxCacheEntries = 0Extractпропускает генерацию ключа кэша иGet/Set(короткое замыкание), не неся никаких накладных расходов на кэш.
GetStatistics
Возвращает текущую статистику обработки.
func (p *Processor) GetStatistics() StatisticsНазначение полей Statistics:
| Поле | Описание |
|---|---|
TotalProcessed | Количество завершённых без ошибок извлечений, включая попадания в кэш |
CacheHits | Количество прямых попаданий в кэш |
CacheMisses | Количество промахов, потребовавших полной обработки |
ErrorCount | Количество извлечений, вернувших ошибку |
AverageProcessTime | Среднее wall-clock время одного извлечения (0, если TotalProcessed равен 0) |
stats := p.GetStatistics()
fmt.Printf("Обработано: %d, попаданий в кэш: %d\n",
stats.TotalProcessed, stats.CacheHits)ClearCache
Очищает кэш, сохраняя накопленную статистику.
func (p *Processor) ClearCache()ResetStatistics
Сбрасывает все счётчики статистики.
func (p *Processor) ResetStatistics()Аудит
GetAuditLog
Получает записи журнала аудита.
func (p *Processor) GetAuditLog() []AuditEntryClearAuditLog
Очищает журнал аудита.
func (p *Processor) ClearAuditLog()Жизненный цикл
Close
Освобождает ресурсы, удерживаемые Processor. Должен вызываться после завершения использования.
func (p *Processor) Close() errorp, _ := html.New(cfg)
defer p.Close()
// ... использование p для извлеченияРекомендации по жизненному циклу
- Повторное использование синглтона: в долго живущем сервисе (HTTP handler, worker) создайте один Processor и разделяйте его между параллельными запросами, чтобы максимизировать выгоду от кэша. Сам Processor потокобезопасен, создавать новый на каждый запрос не нужно.
defer Close(): сразу после создания выполняйтеdefer p.Close(), чтобы даже в исключительных путях освободить фоновый goroutine очистки и ресурсы аудита.Closeостанавливает goroutine очистки кэша, очищает кэш и закрывает audit sink.- Не используйте после Close: вызов любого метода после
CloseвернётErrProcessorClosed.CloseиспользуетCompareAndSwapдля идемпотентности — повторный вызов безопасен, но бессмысленен.