Гид по технологиям

Веб-скрейпинг на Go с goquery

6 min read Разработка Обновлено 07 Jan 2026
Веб-скрейпинг на Go с goquery
Веб-скрейпинг на Go с goquery

Что такое веб-скрейпинг

Веб-скрейпинг, или извлечение данных с веб-страниц, — это автоматический сбор информации с сайтов. Скрепер отправляет HTTP-запрос, получает HTML и не отображает его пользователю, а обрабатывает по заданным правилам и сохраняет результат в структуру данных или базу.

Краткое определение: веб-скрейпинг — автоматическое извлечение структурированных данных из веб-страниц.

Важно: не все сайты разрешают скрейпинг. Перед запуском проверьте robots.txt, условия использования и законы о защите данных.

Когда скрейпинг удобен и когда он не работает

  • Работает хорошо: сайты без API, публичные каталоги, страницы с предсказуемой HTML-структурой.
  • Падает: сайты, генерируемые исключительно JavaScript (SPA) без серверной отдачи HTML; страницы с антибот-защитой (CAPTCHA, детекторы поведения); сайты с частыми изменениями верстки.

Альтернатива: если есть официальное API — используйте его. Для динамических сайтов используйте браузерный контроллер (ChromeDP) или сервисы рендеринга.

Инструменты для скрейпинга на Go

  • goquery — парсер, вдохновлённый jQuery, работает с net/html и Cascadia (CSS-селекторы).
  • Colly — специализированная библиотека для скрейпинга с удобным API и очередями.
  • ChromeDP — управление браузером через DevTools Protocol; полезен для страниц, требующих рендеринга JavaScript.

Выбор: goquery хорош для быстрого парсинга HTML; Colly добавляет удобства и управления очередями; ChromeDP подходит для сложного JS.

HTML страницы веб-сайта

Установка goquery

В терминале выполните:

go get github.com/PuerkitoBio/goquery

Если возникают ошибки — обновите версию Go и проверьте настройки GOPATH / модулей.

Общий процесс скрейпинга

  1. Создать HTTP-запрос к странице.
  2. Получить и распарсить HTML.
  3. Найти нужные элементы с помощью CSS-селекторов.
  4. Извлечь текст/атрибуты и сохранить в структуру или базу.
  5. Обрабатывать ошибки, ждать, кэшировать и соблюдать правила сайта.

Делать HTTP-запросы правильно

Стандартный пакет для запросов — net/http. Всегда задавайте таймауты, закрывайте response.Body и устанавливайте корректный User-Agent.

Пример простого запроса (исходный пример, сохранённый):

package main
  
import "net/http"
import "log"
import "fmt"
  
func main() {
    webUrl := "https://news.ycombinator.com/"
    response, err:= http.Get(webUrl)
  
    if err != nil {
        log.Fatalln(err)
    } else if response.StatusCode == 200 {
        fmt.Println("We can scrape this")
    } else {
        log.Fatalln("Do not scrape this")
    }
}

Совет: используйте http.Client с таймаутом и заголовками:

client := &http.Client{Timeout: 15 * time.Second}
req, _ := http.NewRequest("GET", webUrl, nil)
req.Header.Set("User-Agent", "MyGoScraper/1.0 (+https://example.com)")
resp, err := client.Do(req)
if err != nil {
    // обработка
}
defer resp.Body.Close()

Получение и парсинг HTML с goquery

Создание документа из ответа

Пример создания документа (сохранённый):

document, err := goquery.NewDocumentFromReader(response.Body)
  
if err != nil {
    log.Fatalln(err)
}

После этого document содержит дерево DOM, с которым можно работать через CSS-селекторы.

Выбор элементов по селекторам

Нужно посмотреть структуру страницы и составить селектор. Пример из исходного материала:

Инспекция сайта Hacker News в инструментах разработчика

Пример поиска одного элемента:

document.Find("tr.athing")

Примечание: Find возвращает набор элементов; методы, которые применяются к набору, работают для первого совпадения или для всех в зависимости от используемой функции.

Обработка множества элементов

Чтобы пройтись по всем совпадениям, используйте Each:

document.Find("tr.athing").Each(func(index int, selector *goquery.Selection) {
    /* Process selector here */
})

Пример извлечения заголовков и ссылок (сохранённый):

document.Find("tr.athing").Each(func(index int, selector *goquery.Selection) {
    title := selector.Find("td.title").Text()
    link, found := selector.Find("a.titlelink").Attr("href")
})

Метод Text() возвращает текст, Attr возвращает значение атрибута и флаг наличия.

Сохранение данных: структуры и базы

Простой способ — сохранить результат в слайс структур, а затем записать в базу.

Пример структуры (исходный):

type Information struct {
    link string
    title string
}

info := make([]Information, 0)

Рекомендация: экспортируйте поля и добавьте json-теги, если планируете сериализацию:

type Information struct {
    Title string `json:"title"`
    Link  string `json:"link"`
}

var info []Information

// Внутри Each:
info = append(info, Information{
    Title: title,
    Link:  link,
})

Печать слайса покажет результат:

fmt.Println(info)

Результат парсинга в структуре данных

Практические советы и лучшие практики

  • Уважайте robots.txt и условия сайта. Если сайт запрещает автоматический сбор, лучше найти легальный способ доступа.
  • Ограничивайте частоту запросов (rate limiting) и используйте backoff при ошибках.
  • Кэшируйте результаты локально, чтобы уменьшить нагрузку на целевой сервер.
  • Обрабатывайте редиректы и коды ошибок (429, 503 и т.д.).
  • Используйте заголовки (User-Agent, Referer, Accept-Language) разумно.
  • Закрывайте response.Body с defer сразу после проверки ошибки.
  • Не храните секреты в коде и не публикуйте свои прокси/ключи.

Критическая запись: всегда тестируйте скрейпер на тестовой среде или с ограниченной частотой, прежде чем запускать в прод.

Анти-скрейпинг: как его обнаруживают и как с этим быть

Анти-скрейпинг меры включают:

  • CAPTCHA и интерактивные проверки.
  • Анализ поведенческих паттернов (скорость кликов, последовательность запросов).
  • Блокировка по IP или ограничение по сессиям.

Когда скрейпинг «ломается»: если HTML меняется динамически, селекторы становятся невалидны. Решения:

  • Переход на ChromeDP (рендеринг JS).
  • Работа с API или договор с владельцем данных.
  • Добавление мониторинга изменений страницы и тестов селекторов.

Примеры альтернативных подходов

  • Коллекторы и очереди: Colly для управления параллелизмом и очередями задач.
  • Браузерный рендеринг: ChromeDP для SPA.
  • Серверы рендеринга/прокси: внешние сервисы, которые возвращают уже отрендеренный HTML.

Мини-методология для проекта скрейпинга

  1. Определите цель и поля данных.
  2. Проверьте легальность и robots.txt.
  3. Найдите стабильные CSS-селекторы.
  4. Напишите прототип с goquery и локальным кэшем.
  5. Добавьте таймауты, ретраи и логирование.
  6. Тестируйте на изменениях верстки.
  7. Переходите к масштабированию и хранению в БД.

Чек-лист по ролям

Разработчик

  • Написал клиент с таймаутами и заголовками.
  • Закрыл response.Body.
  • Добавил retry и backoff.

DevOps

  • Настроил прокси/балансировщик при необходимости.
  • Добавил мониторинг быстрых ошибок и задержек.

QA

  • Описал тесты селекторов и проверки кейсов с отсутствующими полями.
  • Проверил корректную обработку кодов 4xx/5xx.

Продукт

  • Подтвердил законность и соответствие политике конфиденциальности.
  • Определил SLA для обновления данных.

Критерии приёмки

  • Скрейпер корректно извлекает поля, указанные в ТЗ, в 95% тестовых страниц (без изменения верстки).
  • Скрейпер корректно обрабатывает недоступность страницы и не падает.
  • Запросы идут с заданной частотой и не нарушают robots.txt.

Тестовые случаи и приемочные тесты

  • Позитивный: страница доступна, все селекторы возвращают значения.
  • Негативный: страница возвращает 404 — скрейпер логирует и продолжает.
  • Негативный: атрибут отсутствует — скрейпер пропускает элемент без паники.

Пример продвинутого кода: клиент с таймаутом и парсером

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/PuerkitoBio/goquery"
)

func fetch(url string) (*goquery.Document, error) {
    client := &http.Client{Timeout: 15 * time.Second}
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        return nil, err
    }
    req.Header.Set("User-Agent", "MyGoScraper/1.0 (+https://example.com)")

    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != 200 {
        return nil, fmt.Errorf("bad status: %d", resp.StatusCode)
    }

    doc, err := goquery.NewDocumentFromReader(resp.Body)
    if err != nil {
        return nil, err
    }
    return doc, nil
}

func main() {
    doc, err := fetch("https://news.ycombinator.com/")
    if err != nil {
        log.Fatal(err)
    }
    doc.Find("tr.athing").Each(func(i int, s *goquery.Selection) {
        title := s.Find("td.title").Text()
        link, _ := s.Find("a.titlelink").Attr("href")
        fmt.Println(i, title, link)
    })
}

Decision flowchart для выбора инструмента

flowchart TD
    A[Нужен скрейпинг] --> B{Страница статическая?}
    B -- Да --> C[goquery]
    B -- Частично --> D[ChromeDP]
    B -- Нет --> D
    C --> E{Нужен масштаб?}
    E -- Да --> F[Colly или очередь]
    E -- Нет --> G[Прототип и кэш]

Безопасность и приватность

  • Не собирайте персональные данные без согласия.
  • Храните данные в зашифрованном виде, если это требуется политикой безопасности.
  • Логируйте только то, что нужно для отладки; избегайте логирования чувствительных заголовков.

Совместимость и миграция

  • goquery совместим с Go-модулями. Следите за версиями зависимостей.
  • Если сайт переходит на heavy JS, подумайте о миграции на ChromeDP.

Контроль качества и мониторинг

  • Пишите тесты селекторов с фиктивными HTML-файлами.
  • Настройте оповещения на рост числа ошибок 4xx/5xx.

Частые ошибки и как их избежать

  • Забыл defer resp.Body.Close() — утечка дескрипторов.
  • Нет таймаута у http.Client — висящие запросы.
  • Жёстко захардкоженные селекторы без наблюдения за изменениями верстки.

Краткое резюме

  • goquery — быстрый способ парсинга HTML в Go.
  • Для динамических или защищённых сайтов используйте ChromeDP или API.
  • Соблюдайте robots.txt, лимиtы и законы о данных.
  • Структурируйте результат в экспортируемые структуры и храните в БД с кэшем.

Важно: начните с прототипа, добавьте логику повторных попыток и мониторинг перед масштабированием.


Ключевые выводы:

  • Начинайте с проверки robots.txt и юрисдикции данных.
  • Используйте http.Client с таймаутами и корректными заголовками.
  • goquery удобен для статических страниц; для динамических — ChromeDP.
  • Добавляйте кэширование, ретраи и мониторинг для стабильной работы.
Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Анимация из пластилина и стоп-моушн — руководство
Творчество

Анимация из пластилина и стоп-моушн — руководство

Наложение производительности на Steam Deck
Гайды

Наложение производительности на Steam Deck

Резюме в Google Docs — быстрый план
Карьера

Резюме в Google Docs — быстрый план

Вернуть покупку в Oculus Store — инструкция
Помощь

Вернуть покупку в Oculus Store — инструкция

Изменить и сбросить пароль в TikTok
Безопасность

Изменить и сбросить пароль в TikTok

Как увеличить миниатюры в Chrome, Firefox и Edge
Браузеры

Как увеличить миниатюры в Chrome, Firefox и Edge