---
sidebar_label: "Пакетная обработка на практике"
title: "Пакетная обработка - CyberGo html | параллельное извлечение"
description: "Пакетная обработка CyberGo html: четыре пакетных API, структура BatchResult, параллелизм WorkerPoolSize, отмена контекста и обработка частичных неудач."
sidebar_position: 2
---

# Пакетная обработка на практике

При обработке большого количества HTML-документов пакетные API выполняются автоматически в параллель, в несколько раз быстрее, чем вызовы в цикле по одному. Это руководство охватывает четыре пакетных API, структуру результата и настройку параллелизма.

## Обзор пакетных API

Библиотека предоставляет четыре пакетные функции, соответствующие комбинациям «ввод байтами / ввод файлами» × «без контекста / с контекстом»:

| API | Ввод | Контекст | Описание |
|-----|------|----------|----------|
| `ExtractBatch` | `[][]byte` | нет | пакетное извлечение среза байтов |
| `ExtractBatchFiles` | `[]string` | нет | пакетное извлечение по путям файлов |
| `ExtractBatchWithContext` | `[][]byte` | да | поддержка тайм-аута/отмены |
| `ExtractBatchFilesWithContext` | `[]string` | да | поддержка тайм-аута/отмены |

Все функции можно вызывать как на экземпляре `Processor`, так и на уровне пакета:

```go
// Уровень пакета (использует внутренний пул Processor)
br := html.ExtractBatch(pages)

// Экземпляр Processor (повторное использование кэша)
p, _ := html.New()
defer p.Close()
br := p.ExtractBatch(pages)
```

## Структура BatchResult

Пакетные операции возвращают `*BatchResult`, содержащий результаты по каждому элементу и сводные счётчики:

| Поле | Тип | Описание |
|------|-----|----------|
| `Results` | `[]*Result` | результат извлечения для каждого элемента; `nil` для неудачных или отменённых |
| `Errors` | `[]error` | ошибка для каждого элемента; `nil` для успешных; индекс соответствует вводу |
| `Success` | `int` | количество успешно извлечённых |
| `Failed` | `int` | количество неудачных извлечений |
| `Cancelled` | `int` | количество пропущенных из-за отмены контекста |

:::tip Соответствие индексов
`Results[i]` и `Errors[i]` соответствуют `i`-му входному элементу. При успехе `Results[i]` не равно nil, а `Errors[i]` равно nil; при неудаче — наоборот.
:::

## Базовый пример

Пакетное извлечение 3 срезов байтов HTML:

```go
package main

import (
    "fmt"
    "log"

    "github.com/cybergodev/html"
)

func main() {
    pages := [][]byte{
        []byte(`<html><body><article><h1>Первая страница</h1><p>Руководство по языку Go.</p></article></body></html>`),
        []byte(`<html><body><article><h1>Вторая страница</h1><p>Руководство по параллельному программированию.</p></article></body></html>`),
        []byte(`<html><body><article><h1>Третья страница</h1><p>Советы по оптимизации производительности.</p></article></body></html>`),
    }

    // Пакетное параллельное извлечение (функция уровня пакета)
    br := html.ExtractBatch(pages)

    fmt.Printf("Успешно: %d, неудачно: %d, отменено: %d\n", br.Success, br.Failed, br.Cancelled)
    // Успешно: 3, неудачно: 0, отменено: 0

    // Перебор результатов (индекс соответствует вводу)
    for i, result := range br.Results {
        if result != nil {
            fmt.Printf("  [%d] Заголовок: %s\n", i+1, result.Title)
        } else if br.Errors[i] != nil {
            fmt.Printf("  [%d] Ошибка: %v\n", i+1, br.Errors[i])
        }
    }
    // [1] Заголовок: Первая страница
    // [2] Заголовок: Вторая страница
    // [3] Заголовок: Третья страница
}
```

## Пакетное извлечение из файлов

```go
files := []string{"page1.html", "page2.html", "page3.html"}

br := html.ExtractBatchFiles(files)

fmt.Printf("Успешно: %d, неудачно: %d\n", br.Success, br.Failed)

for i, err := range br.Errors {
    if err != nil {
        fmt.Printf("Сбой файла %s: %v\n", files[i], err)
    }
}
```

## Функции пакета против экземпляра Processor

Поведение кэша у этих двух способов вызова различается:

| Способ вызова | Кэш | Применимый сценарий |
|---------------|-----|----------------------|
| `html.ExtractBatch(pages)` | отключен (пул Processor очищает кэш при каждом возврате) | разовая пакетная задача |
| `p.ExtractBatch(pages)` | включён (повторное использование кэша Processor) | высокочастотные пакеты, повторяющийся контент |

:::warning Функции пакета не кэшируют
Функции уровня пакета используют Processor из внутреннего `sync.Pool`, конфигурация которого отключает кэш (`MaxCacheEntries = 0`) и очищает кэш при каждом возврате. Если в пакете есть повторяющийся контент, используйте экземпляр Processor для ускорения через кэш. Подробнее см. [Повторное использование Processor и кэш](./processor-cache).
:::

```go
// Рекомендуется: повторное использование Processor для высокочастотных пакетов
p, _ := html.New()
defer p.Close()

for batch := range batchQueue {
    br := p.ExtractBatch(batch) // кэш работает, повторяющийся контент попадает напрямую
    processResult(br)
}
```

## Управление параллелизмом

`WorkerPoolSize` управляет количеством параллельных воркеров для пакетной обработки (по умолчанию 4, максимум 256):

```go
cfg := html.DefaultConfig()

// Устанавливаем параллелизм по числу ядер CPU (с потолком 256)
if n := runtime.NumCPU(); n > 256 {
    n = 256
}
cfg.WorkerPoolSize = n

p, _ := html.New(cfg)
defer p.Close()

br := p.ExtractBatch(pages)
```

| Параметр | По умолчанию | Максимум | Описание |
|----------|--------------|----------|----------|
| `WorkerPoolSize` | 4 | 256 | количество параллельных воркеров, должно быть положительным целым |

:::tip Настройка WorkerPoolSize
Для CPU-интенсивных задач устанавливайте равным числу ядер CPU; для I/O-интенсивных (например, чтение файлов) можно увеличить. Значения выше 256 отклоняются при валидации конфигурации.
:::

## Предел размера пакета

Один пакет поддерживает максимум 10000 элементов. При превышении **все** элементы возвращают ошибку (а не частичная обработка):

```go
huge := make([][]byte, 10001) // превышает предел

br := html.ExtractBatch(huge)

fmt.Printf("Неудачно: %d\n", br.Failed)
// Неудачно: 10001

fmt.Printf("Ошибка первого элемента: %v\n", br.Errors[0])
// Ошибка первого элемента: html: batch size 10001 exceeds maximum 10000
```

:::warning Поведение при превышении
`maxBatchSize = 10000` — жёсткий предел. При превышении ни один элемент не обрабатывается; для всех входных данных возвращается единая ошибка. Чтобы обработать больше, разбейте на несколько пакетов.
:::

## Отмена через контекст

`ExtractBatchWithContext` корректно завершает работу при отмене контекста:

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

br := p.ExtractBatchWithContext(ctx, pages)

fmt.Printf("Успешно: %d, неудачно: %d, отменено: %d\n",
    br.Success, br.Failed, br.Cancelled)
```

| Статус элемента | Способ обработки |
|-----------------|------------------|
| Завершён | результат сохраняется в `Results` |
| В процессе | после завершения записывается как обычно |
| Не начат | пропускается, учитывается в `Cancelled`, `Errors[i]` = `ctx.Err()` |

:::tip Доступность частичных результатов
После отмены контекста результаты завершённых элементов остаются в `br.Results` (не nil). Можно безопасно использовать уже готовые результаты, не отбрасывая весь вывод из-за отмены.
:::

## Обработка частичных неудач

Пакетная обработка допускает **частичный успех** — неудача одного элемента не влияет на остальные:

```go
pages := [][]byte{
    validHTML,   // корректный
    []byte(""),  // пустой ввод, вызывает ошибку
    validHTML2,  // корректный
}

br := p.ExtractBatch(pages)

// Элемент 2 неудачен, элементы 1 и 3 успешны
fmt.Printf("Успешно: %d, неудачно: %d\n", br.Success, br.Failed)
// Успешно: 2, неудачно: 1

// Постепенная обработка с пропуском неудачных элементов
for i, result := range br.Results {
    if result == nil {
        fmt.Printf("[%d] Неудача: %v\n", i, br.Errors[i])
        continue
    }
    fmt.Printf("[%d] Заголовок: %s\n", i, result.Title)
}
```

## Рекомендации по производительности

| Число документов | Рекомендуемая стратегия | Описание |
|------------------|-------------------------|----------|
| 1–10 | по одному через `Extract` | накладные расходы на пакет могут превысить выигрыш от параллелизма |
| 10–1000 | `ExtractBatch` + функция пакета | автоматический параллелизм, без управления Processor |
| 1000+ | `p.ExtractBatch` + экземпляр Processor | повторное использование кэша, обработка порциями во избежание пиков памяти |
| 10000+ | порциями (≤10000 на пакет) + экземпляр Processor | превышение предела одного пакета, обработка фрагментами |

```go
// Пример крупномасштабной обработки порциями
p, _ := html.New()
defer p.Close()

const batchSize = 5000
for i := 0; i < len(allPages); i += batchSize {
    end := i + batchSize
    if end > len(allPages) {
        end = len(allPages)
    }

    br := p.ExtractBatch(allPages[i:end])
    // Обработка результатов текущей порции...
}
```

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

- [Повторное использование Processor и кэш](./processor-cache) - отличие кэша между функциями пакета и экземпляром
- [Оптимизация производительности](./performance) - повышение пропускной способности и тайм-ауты
- [Обработка ошибок](../error-handling) - сигнатурные ошибки и обработка пакетных ошибок
- [Справочник API: пакетная обработка](../../api-reference/modules/batch) - полные сигнатуры API
