Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 13 из 25 ~20 мин +50 XP

Формы и вход: POST, cookies, сессии

colly логин cookies собирается из трёх частей: c.Post с картой полей формы, автоматическое хранение cookies внутри коллектора и проверка входа по элементу, который видит только залогиненный. Дальше — POST против GET-форм, перенос сессии между коллекторами и CSRF-токен в два прохода.

Содержание урока · 10 разделов

Половина полезных данных в интернете лежит за формой входа: прайсы поставщика, личный кабинет, платные каталоги. Скрапер из прошлых уроков умеет только читать — форма требует разговора: отправить поля, принять 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

GO
package 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 показывает, что сессия уже живёт: главная отдаёт страницу, где у залогиненного видна ссылка на выход.

Cookies: сессия, которую colly ведёт сама#

Сервер узнаёт пользователя по cookies: в ответ на логин он кладёт идентификатор сессии, и дальше каждая страница отдаётся с оглядкой на этот идентификатор. Весь colly логин cookies сводится к автоматике: у коллектора свой cookie-jar, и то, что пришло в Set-Cookie, вернётся в следующих Visit и Post без единой строки кода с вашей стороны. Достать cookies можно методом c.Cookies(url) — вернётся слайс []*http.Cookie, полезный для отладки и переноса сессии. Положить извне — c.SetCookies(url, cookies). Отсюда рабочая схема наследования сессии: скрапер залогинился, cookies вынули, создали второй коллектор с другими настройками и влили сессию через SetCookies — вход сохранится. Границы у этой аккуратности жёсткие, о них — сразу после кода.

Перенос сессии между коллекторами

GO
package 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 по адресу песочницы и вливается во второй — тот стартует уже вошедшим. Так собираются скраперы, где логин и сбор живут с разными настройками: у логина своя скорость и своя аккуратность, у сборщика — свои лимиты и параллелизм.

Проверка входа: ищите элемент залогиненного#

Как понять, что вход удался? Не по коду ответа: редирект и 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 в два прохода

GO
c := 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")

Восстановите отправку формы

Метод отправки и имена полей формы.

err := c.("https://quotes.toscrape.com/login", map[string]string{ "": "demo", "": "demo", })

Соберите логин по шагам

От коллектора до проверки входа.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: формы и сессии

Термин

c.Post

нажмите, чтобы перевернуть
Определение

Отправка POST-формы: c.Post(url, map[string]string). Полный проход конвейера colly — OnRequest, редиректы, OnHTML по итоговой странице.

1 / 5

Что дальше по курсу#

Сессия есть — скрапера можно опознать и по другим приметам: какие заголовки он шлёт и как выглядит его 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 · Формы и вход: POST, cookies, сессии)

Вопросы и ответы по уроку

colly умеет заполнять формы автоматически?

Базовая colly — нет: она отправляет готовые данные через c.Post или PostRaw. Значения с произвольной разметки собираются через OnHTML и ChildAttr; сложные браузерные формы с JavaScript — территория chromedp и других браузерных движков.

Как сохранять сессию между запусками скрапера?

Cookies живут в памяти коллектора и умирают с процессом. Сохраните c.Cookies в файл сами — например, в JSON через encoding/json — и при старте влейте обратно через c.SetCookies: сессия продолжится, пока сервер не выкинул её по времени жизни.

Почему после логина всё равно вижу страницу для гостей?

Проверьте три вещи: правильные имена полей (name-атрибуты, а не плейсхолдеры), доезжают ли cookies — распечатайте c.Cookies перед следующим Visit — и не требуется ли CSRF-токен, который нужно сначала вынуть из формы. Диагностику ведите через OnResponse: видно, какой адрес и статус пришёл на самом деле.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

Testify25 уроков~9 чпрактика25 уроков уже доступны

Testify: тестирование Go с assert и require

Интерактивный курс Testify для Go: assert и require, табличные тесты, suite, mock-объекты, Eventually, HTTP-тесты. 25 уроков с примерами и практикой в браузере.

  • Писать читаемые проверки через assert и require, а не лестницы if
  • Проверять ошибки, паники и краевые случаи без ручного кода
  • Сравнивать структуры, слайсы, карты и числа с плавающей точкой
  • Строить табличные тесты и сьюты с хуками подготовки