Содержание урока · 10 разделов
- colly форма отправка: c.Post против GET-форм
- Cookies: сессия, которую colly ведёт сама
- Проверка входа: ищите элемент залогиненного
- Диагностика: лог входа как диалог
- GET-формы: просто Visit с параметрами
- CSRF: сначала GET формы, потом POST
- Что дальше по курсу
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Половина полезных данных в интернете лежит за формой входа: прайсы поставщика, личный кабинет, платные каталоги. Скрапер из прошлых уроков умеет только читать — форма требует разговора: отправить поля, принять cookies, носить их в каждом следующем запросе. Ручной способ «открыл devtools, скопировал запрос с печеньками» живёт один вечер: сессия протухает, и всё по новой. Colly закрывает сцену целиком: POST отправляется методом c.Post, cookies из ответа коллектор складывает сам и дальше носит автоматически, а вход заметен по ссылке «Выход», которая появляется только у залогиненных. Урок построен против живой песочницы quotes.toscrape.com: у неё есть форма /login без капчи и CSRF, идеально для первого входа из кода — подойдут любые учётные данные, песочница для того и создана.
colly форма отправка: c.Post против GET-форм#
Формы бывают двух пород, и разница не косметическая. GET-форма кладёт поля в строку запроса: поиск по каталогу превращается в /search?q=colly&page=2 — такой запрос отправляется обычным Visit с готовым URL, POST не нужен. POST-форма несёт поля в теле запроса: логин, комментарий, загрузка файла — всё, что меняет состояние или не должно светиться в адресной строке. Для POST у коллектора есть метод c.Post(url, map[string]string): ключи карты — имена input-полей формы, значения — то, что вы вводили бы руками. Важная деталь: c.Post — полноценный проход через конвейер colly. Сработает OnRequest, придёт OnResponse, OnHTML разберёт итоговый HTML так же, как после Visit. Редирект после успешного логина colly проходит сама, как это делает браузер, и колбэки увидят уже финальную страницу.
Вход на quotes.toscrape.com
GOpackage main
import (
"fmt"
"github.com/gocolly/colly/v2"
)
func main() {
c := colly.NewCollector(
colly.AllowedDomains("quotes.toscrape.com"),
)
c.OnHTML("a[href='/logout']", func(e *colly.HTMLElement) {
fmt.Println("вход выполнен, элемент:", e.Text)
})
err := c.Post("https://quotes.toscrape.com/login", map[string]string{
"username": "demo",
"password": "demo",
})
if err != nil {
fmt.Println("ошибка POST:", err)
return
}
c.Visit("https://quotes.toscrape.com/")
}Имена полей username и password — это name-атрибуты input-ов на странице /login, посмотреть их можно в devtools. c.Post отправляет форму, colly следует редиректу на главную и разбирает её HTML теми же колбэками: OnHTML находит ссылку «Logout» и печатает. Затем отдельный Visit показывает, что сессия уже живёт: главная отдаёт страницу, где у залогиненного видна ссылка на выход.
Все примеры урока ходят по сети к quotes.toscrape.com, а браузерный интерпретатор сетевых вызовов не исполняет — runner-ов в курсе не будет. Понадобится локальный Go: go mod init demo, затем go get github.com/gocolly/colly/v2. Модуль ставится за минуту, дальше вход, cookies и CSRF запускаются в вашем терминале.
Cookies: сессия, которую colly ведёт сама#
Сервер узнаёт пользователя по cookies: в ответ на логин он кладёт идентификатор сессии, и дальше каждая страница отдаётся с оглядкой на этот идентификатор. Весь colly логин cookies сводится к автоматике: у коллектора свой cookie-jar, и то, что пришло в Set-Cookie, вернётся в следующих Visit и Post без единой строки кода с вашей стороны. Достать cookies можно методом c.Cookies(url) — вернётся слайс []*http.Cookie, полезный для отладки и переноса сессии. Положить извне — c.SetCookies(url, cookies). Отсюда рабочая схема наследования сессии: скрапер залогинился, cookies вынули, создали второй коллектор с другими настройками и влили сессию через SetCookies — вход сохранится. Границы у этой аккуратности жёсткие, о них — сразу после кода.
Перенос сессии между коллекторами
GOpackage main
import (
"fmt"
"github.com/gocolly/colly/v2"
)
func main() {
login := colly.NewCollector()
_ = login.Post("https://quotes.toscrape.com/login", map[string]string{
"username": "demo",
"password": "demo",
})
cookies := login.Cookies("https://quotes.toscrape.com/")
fmt.Println("cookies получено:", len(cookies))
scraper := colly.NewCollector(
colly.AllowedDomains("quotes.toscrape.com"),
)
scraper.SetCookies("https://quotes.toscrape.com/", cookies)
scraper.OnHTML("a[href='/logout']", func(e *colly.HTMLElement) {
fmt.Println("новый коллектор уже залогинен:", e.Text)
})
scraper.Visit("https://quotes.toscrape.com/")
}Первый коллектор логинится, из него вынимается слайс cookies по адресу песочницы и вливается во второй — тот стартует уже вошедшим. Так собираются скраперы, где логин и сбор живут с разными настройками: у логина своя скорость и своя аккуратность, у сборщика — свои лимиты и параллелизм.
Cookies живут в памяти коллектора: второй colly.NewCollector() стартует анонимным, даже если первый только что залогинился. То же происходит при перезапуске программы — сессия не переживает процесс. Продолжить вход в другом скрапере можно через c.Cookies плюс c.SetCookies; пережить перезапуск — только сохранив cookies на диск самостоятельно: colly за вас это не сделает.
Проверка входа: ищите элемент залогиненного#
Как понять, что вход удался? Не по коду ответа: редирект и 200 скажут и о странице с ошибкой «Неверный пароль». Надёжный признак — элемент, который существует только для залогиненных. На quotes.toscrape.com это ссылка выхода: селектор a[href='/logout'] срабатывает, когда сессия признана. Признак выбирается под сайт: на другом ресурсе это имя пользователя в шапке, кнопка «Мой кабинет», блок с балансом. Ошибка входа, кстати, живёт на той же странице: блок с текстом про неверный пароль ловится отдельным OnHTML — один проход отвечает сразу на оба исхода. Правило простое: проверка входа — поиск элемента, а не анализ статуса. Код ответа говорит, что страница отдалась, но не говорит, чья она.
Диагностика: лог входа как диалог#
Когда вход не срабатывает, претензии без лога не разбираются. Подвесьте OnRequest и OnResponse с печатью адреса и статуса — и вход превратится в читаемый диалог: POST на /login уходит, приходит 302 на главную, за ним GET, и вот тут видно, где сломалось. Статус 200 на странице логина после POST означает, что сервер не принял форму и вернул её с сообщением об ошибке — ищите блок ошибки отдельным OnHTML. 302, за которым снова /login, — потерялась сессия: cookies не доехали или коллектор сменился на полпути. Соединение закрылось без ответа — сайт режет клиента по заголовкам, и дальше уже территория урока про User-Agent. Такой лог из трёх строк экономит часы гаданий; я вешаю его на любой новый сайт до того, как писать боевые колбэки, и снимаю, когда вход стабилен.
Проверка: откуда берутся cookies
Откуда c.Cookies(url) берёт cookies, если вы ни разу не вызывали SetCookies?
GET-формы: просто Visit с параметрами#
Поисковые формы чаще всего GET — и тогда POST вам не нужен. Поля складываются в строку запроса, и «отправка» — это Visit с готовым адресом вида /search?q=термин&page=2. Схему адреса подглядывают в devtools: вкладка Network показывает, какой запрос улетел, когда вы искали руками. Осторожность нужна с кодированием значений: пробел и кириллица в адресе должны быть закодированы — net/url из стандартной библиотеки справляется, Values.Encode() собирает корректную строку запроса из карты параметров. GET-форма — самый благодарный случай скрапинга: никаких сессий, никаких токенов, страница для каждой комбинации параметров существует отдельно и отлично кэшируется. Не пропустите и обратную ситуацию: форма выглядит как логин, но метод у неё GET и поля видны в адресной строке — тогда и сессия не нужна, и весь сценарий сводится к одному Visit. Отдельный практический случай — страницы-фильтры без всякой формы: каталог, отфильтрованный по цене или тегу, часто выражается адресом, который собирается из параметров, и собирать такие адреса циклом удобнее, чем заполнять форму. Правило выбора одно: посмотрите в Network, что реально улетает, и воспроизводите адрес, а не жесты пользователя.
CSRF: сначала GET формы, потом POST#
Третий слой сложности — формы с CSRF-токеном: сервер кладёт в разметку скрытый input, обычно input[name=csrf_token], и ждёт, что в POST придёт то же значение. Токен персональный: у вашей сессии он свой, чужой не подойдёт, угадать нельзя. Отсюда правило, которое нарушают все, кто спешит: логин с CSRF одним POST не работает — сервер отклонит его как запрос без токена или с чужим. Правильный порядок двухпроходный. Первый Visit открывает страницу формы и парсит скрытый input: e.ChildAttr("input[name=csrf_token]", "value") вынимает значение. Второй шаг — c.Post уже с токеном среди полей. Токен вынимается тем же коллектором, который будет постить: он привязан к сессии, и сессия должна доехать до POST несменённой.
Логин с CSRF в два прохода
GOc := colly.NewCollector()
token := ""
c.OnHTML("form", func(e *colly.HTMLElement) {
token = e.ChildAttr("input[name=csrf_token]", "value")
})
// первый проход: GET страницы формы, колбэк вынимает токен
c.Visit("https://example.com/login")
// второй проход: POST с токеном среди полей
err := c.Post("https://example.com/login", map[string]string{
"username": "demo",
"password": "demo",
"csrf_token": token,
})Токен вынимается из разметки формы через e.ChildAttr: селектор ищет скрытый input по имени, второй аргумент — имя атрибута. Между Visit и Post коллектор один: cookies первой сессии доезжают до POST, иначе сервер сгенерировал бы новый токен для анонима и отклонил бы наш. Пустой token после первого прохода — знак, что селектор не сработал: включайте лог OnHTML, прежде чем постить.
Типичные ошибки на пути CSRF три. Первая: токен вынут, но POST уходит от свежесозданного коллектора — новая сессия, у сервера новый токен, наш не валиден. Вторая: токен одноразовый — после успешного POST он умирает, и второй POST с тем же значением отклоняется; каждой форме нужен свой проход GET. Третья: имя поля не csrf_token, а что-то своё — authenticity_token у Rails, _csrf у иных фреймворков; имя смотрится в devtools, а не берётся из привычки. И честная граница приёма: CAPTCHA и JavaScript-проверки colly не проходят — там начинаются браузерные инструменты, о которых отдельно рассказывает урок про антибот-защиту.
Проверка: почему одним POST не работает
Логин с CSRF-токеном одним POST не работает. Почему?
Что напечатает программа?
Прочитайте код и выберите, что он выведет:
c := colly.NewCollector()
c.OnHTML("a[href='/logout']", func(e *colly.HTMLElement) {
fmt.Println("welcome:", e.Text)
})
c.Post("https://quotes.toscrape.com/login", map[string]string{
"username": "demo",
"password": "demo",
})
fmt.Println("post done")Восстановите отправку формы
Метод отправки и имена полей формы.
Соберите логин по шагам
От коллектора до проверки входа.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: формы и сессии
Что дальше по курсу#
Сессия есть — скрапера можно опознать и по другим приметам: какие заголовки он шлёт и как выглядит его User-Agent. Этому посвящён следующий урок про заголовки. А если хочется видеть, что происходит на каждом шаге входа — коды ответов, заголовки, редиректы, — вернитесь к уроку про OnRequest и OnResponse: лог запросов превращает непонятный отказ сервера в понятный диалог.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Отправка формы на colly — c.Post(url, map[string]string): имена полей из формы, полный проход конвейера, автоматическое следование редиректам.
- Cookies коллектор ведёт сам: Set-Cookie сохраняется и возвращается в следующих запросах; новый NewCollector — пустая сессия.
- Перенос сессии — c.Cookies плюс c.SetCookies в другом коллекторе; пережить перезапуск cookies могут только через сохранение на диск.
- Проверка входа — поиск элемента залогиненного (a[href='/logout']), а не анализ кода ответа.
- GET-формы проще: Visit с готовой строкой параметров, POST не нужен.
- CSRF в два прохода: GET формы, парсинг скрытого input, POST с токеном тем же коллектором.
Домашнее задание
Войдите на quotes.toscrape.com из кода и выведите все cookies через c.Cookies — найдите среди них идентификатор сессии. Затем соберите два коллектора: первый логинится, второй получает сессию через SetCookies и собирает цитаты — убедитесь, что на его странице видна ссылка Logout. Третий шаг: подвесьте OnRequest и OnResponse с печатью адреса и статуса и проследите диалог входа построчно. Финальный шаг: отправьте POST без полей username и password и словами опишите, что именно вернул сервер и как это видно через OnHTML.
Личные заметкиПусто
Вопросы и ответы по уроку
colly умеет заполнять формы автоматически?
Базовая colly — нет: она отправляет готовые данные через c.Post или PostRaw. Значения с произвольной разметки собираются через OnHTML и ChildAttr; сложные браузерные формы с JavaScript — территория chromedp и других браузерных движков.
Как сохранять сессию между запусками скрапера?
Cookies живут в памяти коллектора и умирают с процессом. Сохраните c.Cookies в файл сами — например, в JSON через encoding/json — и при старте влейте обратно через c.SetCookies: сессия продолжится, пока сервер не выкинул её по времени жизни.
Почему после логина всё равно вижу страницу для гостей?
Проверьте три вещи: правильные имена полей (name-атрибуты, а не плейсхолдеры), доезжают ли cookies — распечатайте c.Cookies перед следующим Visit — и не требуется ли CSRF-токен, который нужно сначала вынуть из формы. Диагностику ведите через OnResponse: видно, какой адрес и статус пришёл на самом деле.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
- User-Agent и заголовки: когда сайт капризничает
- Прокси в Colly: ротация и пул адресов
- Ошибки и повторы: OnError и таймауты
- Кэш и офлайн-режим: не дёргаем сайт дважды
- Пагинация: обходим все страницы каталога
- Параллельный скрапинг: Async и WaitGroup
- LimitRule: задержки и параллелизм
- OnRequest и OnResponse: запросы под контролем
- Все курсы по Go
Следующая ступень учебного пути
Testify: тестирование Go с assert и require
Интерактивный курс Testify для Go: assert и require, табличные тесты, suite, mock-объекты, Eventually, HTTP-тесты. 25 уроков с примерами и практикой в браузере.
- Писать читаемые проверки через assert и require, а не лестницы if
- Проверять ошибки, паники и краевые случаи без ручного кода
- Сравнивать структуры, слайсы, карты и числа с плавающей точкой
- Строить табличные тесты и сьюты с хуками подготовки