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()для создания независимых коллекторов под разные задачи. - Отключайте ненужные парсеры: Если вам не нужен