requests.Session: она сама хранит куки между запросами. Если на форме есть скрытый CSRF-токен, его сначала забирают со страницы входа и отправляют вместе с паролем. Если вход собран на JavaScript, вместо requests подключают Playwright — он управляет настоящим браузером.
Ниже разберём все варианты по шагам. Примеры работают на учебных сайтах, которые сделаны специально для тренировки парсеров: quotes.toscrape.com и httpbin.org. Их можно запускать сколько угодно, никому не мешая.
Содержание
Как устроен вход на сайт
Представьте пропуск в офисное здание. На проходной вы показываете паспорт, охранник выдаёт карточку. Дальше по этажам вы ходите с карточкой, паспорт больше не нужен. На сайте всё так же:- Вход. Браузер отправляет форму с логином и паролем. Это POST-запрос (запрос, который не просто читает страницу, а передаёт серверу данные).
- Сессия. Сервер проверяет пароль и в ответе кладёт куки с номером сессии. Это и есть карточка-пропуск.
- Закрытые страницы. Каждый следующий запрос несёт эти куки. Сервер видит их и отдаёт то, что положено вошедшему пользователю.
requests.get без сессии — новый посетитель без карточки.
Перед тем как писать код, полезно один раз войти на сайт руками с открытыми инструментами разработчика (F12, вкладка «Сеть» / Network). Там видно, на какой адрес уходит форма, какие поля в ней есть и какие куки сервер вернул. Это экономит час угадываний.
Простейший случай: HTTP-аутентификация
Самый старый способ закрыть страницу — базовая HTTP-аутентификация (Basic Auth). Сайт не рисует форму: браузер сам показывает серое окошко «Имя пользователя / Пароль». Такое встречается у внутренних панелей, тестовых стендов и некоторых API. В requests логин и пароль передаются параметромauth — кортежем из двух строк. Проверим на httpbin.org: адрес /basic-auth/user/passwd пускает только с логином user и паролем passwd.
import requests
url = "https://httpbin.org/basic-auth/user/passwd"
ok = requests.get(url, auth=("user", "passwd"), timeout=10)
print(ok.status_code, ok.json())
bad = requests.get(url, auth=("user", "wrong"), timeout=10)
print(bad.status_code)
Вывод:
200 {'authenticated': True, 'user': 'user'}
401
Код 401 означает «не авторизован»: пароль не подошёл. Удобно проверять именно код, а не искать на странице слово «ошибка».
Форма входа с CSRF-токеном
Большинство сайтов входят через обычную HTML-форму. И почти все современные формы защищены CSRF-токеном (CSRF — подделка запроса с чужого сайта). Работает это как номерок в гардеробе: сайт выдаёт случайную строку вместе с формой и принимает пароль только вместе с этой строкой. Отправить форму «с потолка», не открыв её, не получится. Порядок такой:- GET-запросом открыть страницу входа.
- Найти в HTML скрытое поле
<input type="hidden" name="csrf_token">и взять его значение. - POST-запросом отправить токен, логин и пароль в той же сессии.
/login ровно с такой защитой. Учебный сайт пускает с любым логином и паролем, а после входа показывает у каждой цитаты ссылку «(Goodreads page)». По ней и проверим, что вход сработал.
import time
import requests
from bs4 import BeautifulSoup
BASE = "https://quotes.toscrape.com"
HEADERS = {"User-Agent": "learning-scraper/1.0 (+mailto:you@example.com)"}
with requests.Session() as session:
session.headers.update(HEADERS)
# 1. Открываем форму и забираем токен
page = session.get(f"{BASE}/login", timeout=10)
page.raise_for_status()
soup = BeautifulSoup(page.text, "html.parser")
token = soup.find("input", {"name": "csrf_token"})["value"]
print("Токен:", token[:12] + "...")
time.sleep(1) # не торопимся, сайт не резиновый
# 2. Отправляем форму в той же сессии
form = {"csrf_token": token, "username": "student", "password": "any"}
resp = session.post(f"{BASE}/login", data=form, timeout=10)
resp.raise_for_status()
# 3. Проверяем, что вошли
soup = BeautifulSoup(resp.text, "html.parser")
print("Есть кнопка выхода:", soup.select_one("a[href='/logout']") is not None)
print("Куки сессии:", list(session.cookies.get_dict()))
first = soup.select_one(".quote")
print("Ссылка Goodreads видна:", first.find("a", string="(Goodreads page)") is not None)
Вывод (токен каждый раз новый):
Токен: qPBtNceAhsnD...
Есть кнопка выхода: True
Куки сессии: ['session']
Ссылка Goodreads видна: True
Для сравнения: та же главная страница без входа на вопрос «Ссылка Goodreads видна?» отвечает False. Значит, разницу дала именно сессия.
Пара деталей, на которых часто спотыкаются. Имена полей (username, password, csrf_token) берите из HTML формы, а не придумывайте: на каждом сайте они свои. Адрес отправки — атрибут action у тега <form>. Если в форме есть ещё скрытые поля, отправьте их все, а не только токен. Сервер может проверять любое.
Как понять, что вход удался
Многие сайты при неверном пароле отвечают кодом 200 и просто снова показывают форму. Поэтомуraise_for_status() здесь не спасает: ошибки с точки зрения HTTP нет. Надёжнее искать на странице признак вошедшего пользователя. Подходят ссылка «Выйти», имя в шапке, пункт меню «Личный кабинет». Ещё один вариант — сразу после входа открыть закрытую страницу и проверить, куда вас привели. Если адрес ответа (resp.url) снова ведёт на /login, значит, сайт вас не пустил. Такую проверку стоит оформить отдельной функцией и вызывать перед основной работой. Иначе скрипт тихо соберёт сотню страниц-заглушек вместо данных.
Куки и сессия: что хранит requests.Session
Сессия — это объект, который помнит куки и общие заголовки между запросами. Без неё каждый запрос стартует с пустыми карманами. Посмотреть, как это работает, удобно на httpbin.org: адрес/cookies/set ставит куки, а /cookies показывает, какие куки пришли от клиента.
import requests
with requests.Session() as s:
s.get("https://httpbin.org/cookies/set", params={"lesson": "auth"}, timeout=10)
print(s.cookies.get_dict())
print(s.get("https://httpbin.org/cookies", timeout=10).json())
Вывод:
{'lesson': 'auth'}
{'cookies': {'lesson': 'auth'}}
Первый запрос получил куки, второй отправил их сам, без нашего участия. Ровно это и нужно после входа.
Когда что-то не работает, первым делом напечатайте session.cookies.get_dict() и сравните с тем, что видно в браузере. Чаще всего выясняется одно из трёх: куки не пришли (вход не удался), пришли, но на другой домен, или сайт ждёт ещё и заголовок вроде Referer.
Отправка файлов через форму
Если форма умеет загружать файл (аватар, документ), она шлёт данные в формате multipart (несколько частей в одном запросе: поля отдельно, файл отдельно). В requests для этого есть параметрfiles:
with open("report.pdf", "rb") as f:
resp = session.post(upload_url, data={"title": "Отчёт"}, files={"document": f}, timeout=30)
Имя ключа (document) — это атрибут name у <input type="file"> в форме. Чекбоксы и радиокнопки отправляются как обычные поля словаря data: имя поля и значение выбранного варианта.
Когда вход работает на JavaScript: Playwright
Бывает, что в HTML страницы входа формы нет вовсе: её рисует JavaScript, а пароль уходит запросом из скрипта с хитрыми заголовками. Повторять такое на requests долго. Проще взять Playwright — библиотеку, которая запускает настоящий браузер без окна (headless — «без головы», то есть без видимого интерфейса) и нажимает кнопки за вас. Установка:pip install playwright, затем playwright install chromium.
import os
from playwright.sync_api import sync_playwright
LOGIN = os.environ["SITE_LOGIN"]
PASSWORD = os.environ["SITE_PASSWORD"]
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://quotes.toscrape.com/login")
page.fill("#username", LOGIN)
page.fill("#password", PASSWORD)
page.click("input[type=submit]")
page.wait_for_selector("a[href='/logout']") # ждём признак входа
print("Вошли:", page.url)
page.context.storage_state(path="state.json") # сохраняем куки на потом
browser.close()
Вывод:
Вошли: https://quotes.toscrape.com/
Последняя строка перед закрытием сохраняет куки в файл state.json. В следующий раз можно открыть браузер уже вошедшим: browser.new_context(storage_state="state.json"). Так сайт не видит десяток входов подряд, а вы не ждёте лишние секунды.
Рабочая связка на практике: войти через Playwright, а дальше, если сайт позволяет, переложить куки в requests.Session и качать страницы обычными запросами. Браузер медленный и прожорливый, держать его ради каждой страницы незачем.
Двухфакторную защиту (код из СМС или приложения) и капчу парсер обходить не должен. Если сайт их показывает, это ясный сигнал: автоматический вход здесь не предусмотрен. Подробнее о капче — в разделе про работу с капчей, где разбирается распознавание текста на картинках.
Где хранить логин и пароль
Пароль в коде — плохая идея: код уходит в Git, в чат коллеге, в облако. Логин и пароль кладут в переменные окружения (настройки, которые операционная система передаёт программе при запуске) или в файл.env, который не попадает в репозиторий.
import os
login = os.environ["SITE_LOGIN"]
password = os.environ["SITE_PASSWORD"]
Задать переменные в Windows (PowerShell): $env:SITE_LOGIN="student", в Linux и macOS: export SITE_LOGIN=student. Если удобнее файл, есть библиотека python-dotenv: она читает .env и раскладывает значения по переменным окружения. Сам .env сразу впишите в .gitignore.
Как не попасть под блокировку
- Ставьте паузу между запросами:
time.sleep(1)и больше. Сайт для людей, а не для вашего скрипта. - Указывайте честный User-Agent (подпись программы в заголовке) с контактом. Только латиницей: кириллицу в заголовке requests не пропустит и упадёт с ошибкой кодировки.
- Входите один раз и переиспользуйте сессию. Сто входов в минуту похожи на подбор пароля, и аккаунт заблокируют.
- Всегда задавайте
timeout. Без него зависший сервер подвесит и ваш скрипт.
Законно ли это: что важно знать
Вход в аккаунт добавляет к обычным вопросам парсинга ещё один. Заходите только в свой аккаунт или в чужой с явного разрешения владельца. Чужие логины и пароли использовать нельзя, даже если они «где-то нашлись». Подбор паролей и обход защиты входа может подпадать под ст. 272 УК РФ «Неправомерный доступ к компьютерной информации». Дальше — то же, что для любого парсинга. Прочитайте пользовательское соглашение сайта и файл robots.txt: многие сервисы прямо запрещают автоматический сбор, и нарушение может стоить аккаунта. Закрытые разделы часто содержат данные других людей (имена, телефоны, переписку). Их сбор и хранение регулирует Федеральный закон № 152-ФЗ «О персональных данных», и без оснований для обработки есть риск нарушить закон. Если вы выгружаете большую часть чужого каталога или базы, это может задевать права её создателя по ст. 1334 ГК РФ «Исключительное право изготовителя базы данных». Это не юридическая консультация. Для коммерческого проекта лучше уточнить вопрос у юриста. Индивидуальное и групповое обучение «Аналитик данных» Если вы хотите стать экспертом в аналитике, могу помочь. Запишитесь на мой курс «Аналитик данных» и начните свой путь в мир ИТ уже сегодня! Контакты Для получения дополнительной информации и записи на курсы свяжитесь со мной: Телеграм: https://t.me/Vvkomlev Email: victor.komlev@mail.ru Объясняю сложное простыми словами. Даже если вы никогда не работали с ИТ и далеки от программирования, теперь у вас точно все получится! Проверено десятками примеров моих учеников. Гибкий график обучения. Я предлагаю занятия в мини-группах и индивидуально, что позволяет каждому заниматься в удобном темпе. Вы можете совмещать обучение с работой или учебой. Практическая направленность. 80%: практики, 20% теории. У меня множество авторских заданий, которые фокусируются на практике. Вы не просто изучаете теорию, а сразу применяете знания в реальных проектах и задачах. Разнообразие учебных материалов: Теория представлена в виде текстовых уроков с примерами и видео, что делает обучение максимально эффективным и удобным. Понимаю, что обучение информационным технологиям может быть сложным, особенно для новичков. Моя цель – сделать этот процесс максимально простым и увлекательным. У меня персонализированный подход к каждому ученику. Максимальный фокус внимания на ваши потребности и уровень подготовки.Другие статьи о парсинге
- BeautifulSoup с нуля — как вытащить данные из страницы после входа
- Веб-скрейпинг: обзор — весь путь от первого запроса до сбора больших объёмов
