colly для Go: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

colly для Go: полное руководство по веб-скрапингу

1. Что такое colly и зачем он нужен?

Colly — это мощная и высокопроизводительная библиотека для веб-скрапинга (парсинга) на языке Go. Она предоставляет чистый, асинхронный и легко расширяемый интерфейс для извлечения данных с веб-сайтов. Colly построен на основе пакетов net/http и goquery (аналог jQuery для Go), что позволяет разработчикам использовать знакомый синтаксис CSS-селекторов для навигации по HTML-документу.

Зачем нужна эта библиотека? В современном мире данные — это новая нефть. Веб-скрапинг позволяет автоматизировать сбор информации: мониторинг цен конкурентов, сбор новостей, анализ SEO-метрик, создание баз данных товаров и многое другое. Colly выделяется на фоне других решений благодаря своей скорости (асинхронная архитектура на горутинах), встроенной поддержке кэширования, автоматическому управлению задержками (для соблюдения политик сайтов) и гибкой системе обратных вызовов (callbacks).

Библиотека идеально подходит как для небольших скриптов, так и для крупных распределённых систем сбора данных. Благодаря строгой типизации Go и отсутствию рантайма (в отличие от Python/Node.js), colly-приложения компилируются в один бинарный файл, что упрощает деплой и масштабирование.

2. Установка

Для установки colly используется стандартный менеджер пакетов Go. Убедитесь, что у вас установлен Go версии 1.18 или выше.

go get -u github.com/gocolly/colly/v2

После выполнения команды библиотека будет добавлена в ваш go.mod файл. Также рекомендуется установить goquery для работы с HTML (он автоматически подтянется как зависимость):

go get -u github.com/PuerkitoBio/goquery

3. Быстрый старт: минимальный пример

Создадим простой скрапер, который выводит заголовки (теги <h1>) с главной страницы сайта example.com.

package main

import (
    "fmt"
    "github.com/gocolly/colly/v2"
)

func main() {
    // Создаём нового коллектора
    c := colly.NewCollector()

    // Обработчик для посещённых страниц
    c.OnHTML("h1", func(e *colly.HTMLElement) {
        fmt.Println("Заголовок:", e.Text)
    })

    // Обработчик ошибок
    c.OnError(func(r *colly.Response, err error) {
        fmt.Println("Ошибка:", err)
    })

    // Запускаем парсинг
    c.Visit("https://example.com")
}

Запустите код командой go run main.go. Вы увидите заголовок страницы. Это минимальная основа, от которой можно отталкиваться.

4. Основные методы и функции (минимум 8)

Рассмотрим ключевые методы colly, которые покрывают 90% задач веб-скрапинга.

4.1 colly.NewCollector()

Сигнатура: func NewCollector(options ...func(*Collector)) *Collector

Что делает: Создаёт новый экземпляр коллектора — основного объекта для управления парсингом. Принимает опциональные функции-настройки.

// Создание с настройками
c := colly.NewCollector(
    colly.AllowedDomains("example.com"), // Разрешить только example.com
    colly.CacheDir("./cache"),           // Кэшировать запросы
)

4.2 Collector.Visit()

Сигнатура: func (c *Collector) Visit(URL string) error

Что делает: Инициирует HTTP GET запрос к указанному URL. Это точка входа для парсинга.

err := c.Visit("https://example.com/page1")
if err != nil {
    log.Fatal(err)
}

4.3 Collector.OnHTML()

Сигнатура: func (c *Collector) OnHTML(goquerySelector string, callback func(e *HTMLElement))

Что делает: Регистрирует callback, который вызывается для каждого элемента, соответствующего CSS-селектору. Самый часто используемый метод.

// Собрать все ссылки на странице
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
    link := e.Attr("href")
    fmt.Printf("Найдена ссылка: %s\n", e.Request.AbsoluteURL(link))
})

4.4 Collector.OnXML()

Сигнатура: func (c *Collector) OnXML(xpathQuery string, callback func(e *XMLElement))

Что делает: Аналог OnHTML, но для XPath-запросов. Полезно для парсинга XML/SVG или сложных HTML-документов.

// Парсинг RSS ленты
c.OnXML("//item/title", func(e *colly.XMLElement) {
    fmt.Println("Новость:", e.Text)
})

4.5 Collector.OnResponse()

Сигнатура: func (c *Collector) OnResponse(callback func(r *Response))

Что делает: Вызывается после получения HTTP ответа, но до парсинга HTML/XML. Позволяет проверить статус код, заголовки или сохранить сырой ответ.

c.OnResponse(func(r *colly.Response) {
    fmt.Printf("Статус: %d, Размер: %d байт\n", r.StatusCode, len(r.Body))
    // Сохранить в файл
    r.Save("response.html")
})

4.6 Collector.OnRequest()

Сигнатура: func (c *Collector) OnRequest(callback func(r *Request))

Что делает: Вызывается перед отправкой HTTP запроса. Идеально для логирования, установки заголовков или изменения URL.

c.OnRequest(func(r *colly.Request) {
    fmt.Println("Визит:", r.URL.String())
    // Добавляем User-Agent
    r.Headers.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
})

4.7 Collector.OnError()

Сигнатура: func (c *Collector) OnError(callback func(r *Response, err error))

Что делает: Обрабатывает ошибки (сетевые, таймауты, HTTP ошибки 4xx/5xx). Без этого обработчика программа может запаниковать.

c.OnError(func(r *colly.Response, err error) {
    log.Printf("Ошибка при запросе %s: %v", r.Request.URL, err)
})

4.8 Collector.Limit()

Сигнатура: func (c *Collector) Limit(rule *LimitRule) error

Что делает: Устанавливает ограничения на скорость запросов (лимиты). Критически важно для избежания блокировки со стороны сайта.

// Не более 2 запросов в секунду, с параллельностью 1 горутина
err := c.Limit(&colly.LimitRule{
    DomainGlob:  "*",
    Parallelism: 1,
    Delay:       500 * time.Millisecond,
    RandomDelay: 200 * time.Millisecond,
})

4.9 Collector.Clone()

Сигнатура: func (c *Collector) Clone() *Collector

Что делает: Создаёт глубокую копию коллектора с теми же настройками и обработчиками. Полезно для параллельного парсинга разных доменов.

c1 := colly.NewCollector()
c2 := c1.Clone() // c2 имеет те же обработчики, но свой стейт

4.10 HTMLElement.ChildText()

Сигнатура: func (h *HTMLElement) ChildText(goquerySelector string) string

Что делает: Извлекает текст из первого дочернего элемента, соответствующего селектору. Удобно для вложенных структур.

c.OnHTML("div.product", func(e *colly.HTMLElement) {
    name := e.ChildText("h2.product-name")
    price := e.ChildText("span.price")
    fmt.Printf("Товар: %s, Цена: %s\n", name, price)
})

5. Реальный практический пример (100+ строк кода)

Напишем полноценный скрапер для интернет-магазина книг (Books to Scrape — демо-сайт для обучения). Соберём: название, цену, рейтинг и ссылку на книгу, а затем пройдём по всем страницам каталога.

package main

import (
    "encoding/csv"
    "fmt"
    "log"
    "os"
    "time"

    "github.com/gocolly/colly/v2"
)

type Book struct {
    Title  string
    Price  string
    Rating string
    URL    string
}

func main() {
    // Создаём CSV файл для записи
    file, err := os.Create("books.csv")
    if err != nil {
        log.Fatal("Не удалось создать файл:", err)
    }
    defer file.Close()

    writer := csv.NewWriter(file)
    defer writer.Flush()

    // Записываем заголовки CSV
    writer.Write([]string{"Title", "Price", "Rating", "URL"})

    // Настройка коллектора
    c := colly.NewCollector(
        colly.AllowedDomains("books.toscrape.com"),
        colly.CacheDir("./book_cache"),
    )

    // Лимиты: не более 1 запроса в секунду
    c.Limit(&colly.LimitRule{
        DomainGlob:  "*books.toscrape.com",
        Parallelism: 1,
        Delay:       1 * time.Second,
    })

    // Обработчик для карточки книги
    c.OnHTML("article.product_pod", func(e *colly.HTMLElement) {
        book := Book{
            Title:  e.ChildText("h3 a"),
            Price:  e.ChildText("div.product_price p.price_color"),
            Rating: e.ChildAttr("p.star-rating", "class"),
            URL:    e.ChildAttr("h3 a", "href"),
        }

        // Очищаем рейтинг: "star-rating One" -> "One"
        if len(book.Rating) > 12 {
            book.Rating = book.Rating[12:]
        }

        // Записываем в CSV
        writer.Write([]string{book.Title, book.Price, book.Rating, book.URL})
        fmt.Printf("Собрано: %s - %s\n", book.Title, book.Price)
    })

    // Обработчик для пагинации: находим ссылку "next"
    c.OnHTML("li.next a", func(e *colly.HTMLElement) {
        nextPage := e.Request.AbsoluteURL(e.Attr("href"))
        fmt.Println("Переход на страницу:", nextPage)
        e.Request.Visit(nextPage)
    })

    // Логирование запросов
    c.OnRequest(func(r *colly.Request) {
        fmt.Println("Запрос:", r.URL.String())
    })

    // Обработка ошибок
    c.OnError(func(r *colly.Response, err error) {
        log.Printf("Ошибка на %s: %v", r.Request.URL, err)
    })

    // Стартуем с первой страницы
    startURL := "https://books.toscrape.com/catalogue/page-1.html"
    fmt.Println("Начинаем сбор данных...")
    err = c.Visit(startURL)
    if err != nil {
        log.Fatal("Ошибка старта:", err)
    }

    fmt.Println("Сбор данных завершён. Результат в books.csv")
}

Этот скрипт:

  • Собирает данные со всех страниц каталога (автоматически переходит по пагинации).
  • Сохраняет результат в CSV-файл.
  • Использует кэширование для ускорения повторных запусков.
  • Соблюдает вежливость (1 запрос в секунду).

6. Советы по производительности и типичные ошибки

Советы по производительности:

  • Используйте кэширование: colly.CacheDir("./cache") — при повторном запуске не будут скачиваться уже полученные страницы. Это экономит трафик и время.
  • Правильно настраивайте лимиты: Используйте Limit() с Parallelism (количество горутин) и Delay. Для быстрых сайтов можно увеличить параллелизм до 2-4.
  • Избегайте глобальных переменных: Используйте Clone() для создания независимых коллекторов под разные задачи.
  • Отключайте ненужные парсеры: Если вам не нужен

Рекомендаций