---
title: "Формы и вход: POST, cookies, сессии"
description: "colly логин cookies: c.Post с полями формы, сессия внутри коллектора, проверка входа по «logout» и CSRF в два прохода. Пример против quotes.toscrape.com."
course: "Colly: парсинг и веб-скрапинг на Go"
url: "https://golangmaster.ru/courses/colly/lessons/13-formy-login/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 20
---

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

*Курс «Colly: парсинг и веб-скрапинг на Go», урок 13 из 25. ~20 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/colly/lessons/13-formy-login/*

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

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

> **Запуск — только локальный Go.** Все примеры урока ходят по сети к 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 — вход сохранится. Границы у этой аккуратности жёсткие, о них — сразу после кода.

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

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

> **Новый коллектор — новая пустая сессия.** 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](/courses/colly/lessons/14-zagolovki-useragent/). Такой лог из трёх строк экономит часы гаданий; я вешаю его на любой новый сайт до того, как писать боевые колбэки, и снимаю, когда вход стабилен.

### Проверка: откуда берутся cookies

**Вопрос:** Откуда c.Cookies(url) берёт cookies, если вы ни разу не вызывали SetCookies?

- Из файла cookies.txt на диске
- ✅ Из cookie-jar коллектора: всё, что сервер прислал в Set-Cookie, colly сохраняет сама
- Из переменных окружения
- Никуда: без SetCookies вернётся пустой слайс

У коллектора свой cookie-jar: ответы на Visit и Post пополняют его автоматически, и следующие запросы несут 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 не проходят — там начинаются браузерные инструменты, о которых отдельно рассказывает [урок про антибот-защиту](/courses/colly/lessons/22-antibot-zaschita/).

### Проверка: почему одним POST не работает

**Вопрос:** Логин с CSRF-токеном одним POST не работает. Почему?

- POST не может нести cookies, поэтому сессия теряется
- ✅ Токен персонален и заранее неизвестен: его нужно сначала вынуть из разметки формы
- colly не поддерживает POST-запросы к формам с токенами
- CSRF-токен передаётся только в заголовках

Токен генерируется сервером под вашу сессию и вшивается в HTML формы. Не открыв форму — не узнаете значение; POST без него или с чужим отклоняется. Отсюда порядок: GET формы, парсинг скрытого input, POST с токеном тем же коллектором.

### Что напечатает программа?

```go
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")
```

**Что выведет программа?**

- ✅ welcome: Logout и потом post done
- post done, а welcome — не напечатается никогда
- Только ошибка: POST не вызывает колбэки
- welcome: Logout дважды

c.Post проходит весь конвейер: colly отправит форму, проследует редиректом на главную и разберёт финальный HTML колбэками — ссылка «Logout» найдётся ещё до возврата из Post. Затем печатается post done. Если сомневаетесь — запустите: quotes.toscrape.com принимает любые учётные данные.

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

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

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

Подсказка: POST у коллектора — метод Post; имена полей берутся из name-атрибутов input-ов страницы /login.

Правильные ответы в пропусках: `Post`, `username`, `password`

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

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

Правильный порядок строк:

1. `c := colly.NewCollector()`
2. `c.OnHTML("a[href='/logout']", func(e *colly.HTMLElement) {`
3. `  fmt.Println("вход выполнен")`
4. `})`
5. `c.Post("https://quotes.toscrape.com/login", map[string]string{"username": "demo", "password": "demo"})`
6. `c.Visit("https://quotes.toscrape.com/")`

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

- **c.Post** — Отправка POST-формы: c.Post(url, map[string]string). Полный проход конвейера colly — OnRequest, редиректы, OnHTML по итоговой странице.
- **Cookie-jar коллектора** — Cookies ведутся автоматически: Set-Cookie из ответов сохраняется и возвращается в следующих запросах. Новый NewCollector — новая пустая сессия.
- **c.Cookies(url)** — Слайс []*http.Cookie по адресу — для отладки и переноса сессии. Обратный метод — c.SetCookies(url, cookies).
- **CSRF-токен** — Скрытый input формы, персональный для сессии. Логин в два прохода: GET формы с парсингом e.ChildAttr, затем POST с токеном тем же коллектором.
- **Признак входа** — Элемент, который виден только залогиненным: a[href='/logout'], имя в шапке. Проверка входа — поиск элемента, а не анализ кода ответа.

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

Сессия есть — скрапера можно опознать и по другим приметам: какие заголовки он шлёт и как выглядит его User-Agent. Этому посвящён [следующий урок про заголовки](/courses/colly/lessons/14-zagolovki-useragent/). А если хочется видеть, что происходит на каждом шаге входа — коды ответов, заголовки, редиректы, — вернитесь к [уроку про OnRequest и OnResponse](/courses/colly/lessons/09-onrequest-onresponse/): лог запросов превращает непонятный отказ сервера в понятный диалог.

## Коротко: что запомнить

- Отправка формы на 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: видно, какой адрес и статус пришёл на самом деле.
