Перейти к содержанию

Тема 13: Авторизация и Сессии: Как залогиниться на сайт, сохранить Cookies и поддерживать сессию живой?

Новички думают так: "Чтобы спарсить личный кабинет, я буду отправлять логин и пароль в каждом запросе". Это бан. Сервер увидит, что ты "входишь в дверь" 100 раз в секунду. Нормальные люди так не делают.

В вебе работает правило Ночного Клуба:

  1. Ты показываешь паспорт фейс-контролю (Логин/Пароль).
  2. Тебе ставят печать на руку (Cookie / Token).
  3. В баре ты показываешь печать, а не паспорт.

Твоя задача — получить печать один раз и хранить её, пока не смоется.


1. Инструмент: Сессия (Session / Client)

В requests и httpx есть понятие Сессии. Это объект, который имеет "память". Если сервер прислал Cookies в первом ответе, Сессия запомнит их и сама подставит во второй, третий и десятый запрос.

  • Без сессии (Плохо):

    httpx.get(url) # Запрос улетел, куки пришли и... потерялись.
    httpx.get(url) # Сервер снова считает тебя чужаком.
    
  • С сессией (Хорошо):

    client = httpx.Client()
    client.post(login_url, data={...}) # Куки сохранились внутри client
    client.get(profile_url) # Куки полетели сами. Сервер узнал тебя.
    

2. Главная Ловушка: CSRF Token

Ты отправляешь правильный логин/пароль, но получаешь 403. Почему? Потому что на странице входа был скрытый код (CSRF Token), который сервер сгенерировал лично для тебя.

Алгоритм правильного входа:

  1. GET запрос на страницу логина.
  2. Парсим HTML, ищем скрытое поле: <input type="hidden" name="csrf_token" value="a1b2c3...">.
  3. POST запрос с логином, паролем И ЭТИМ ТОКЕНОМ.

3. Сохранение "Лица" (Persisting Cookies)

Парсер упал. Ты перезапускаешь его. Он снова лезет логиниться. Это подозрительно. Сеньор делает так: Залогинился -> Сохранил куки в файл. При перезапуске -> Загрузил куки из файла.


👨‍💻 КОД: Боевой Класс Авторизации

Напишем на httpx. Этот код умеет логиниться, обходить CSRF и сохранять сессию.

import httpx
import json
import os
from bs4 import BeautifulSoup

class AuthSession:
    def __init__(self, cookies_file="cookies.json"):
        self.client = httpx.Client(http2=True)
        self.client.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
        })
        self.cookies_file = cookies_file

    def load_cookies(self):
        """Пытаемся загрузить печать из файла"""
        if os.path.exists(self.cookies_file):
            with open(self.cookies_file, 'r') as f:
                cookies = json.load(f)
                # Загружаем куки в клиент
                for name, value in cookies.items():
                    self.client.cookies.set(name, value)
            return True
        return False

    def save_cookies(self):
        """Сохраняем печать на диск"""
        cookies_dict = {c.name: c.value for c in self.client.cookies}
        with open(self.cookies_file, 'w') as f:
            json.dump(cookies_dict, f)

    def login(self, url, username, password):
        # 1. Сначала проверяем, жива ли старая сессия
        if self.load_cookies():
            # Делаем тестовый запрос в профиль
            resp = self.client.get("<https://site.com/profile>")
            if resp.status_code == 200 and username in resp.text:
                print("✅ Старые куки работают! Логин не нужен.")
                return True
            else:
                print("⚠️ Куки протухли. Логинимся заново...")

        # 2. GET запрос за CSRF токеном
        login_page = self.client.get(url)
        soup = BeautifulSoup(login_page.text, "lxml")

        # Ищем скрытый токен (имя поля зависит от сайта: csrf, _token, authenticity_token)
        csrf_token = soup.find("input", {"name": "csrf_token"})
        token_value = csrf_token["value"] if csrf_token else None

        # 3. Формируем данные
        payload = {
            "username": username,
            "password": password,
        }
        if token_value:
            payload["csrf_token"] = token_value

        # 4. POST запрос (Входим)
        # Важно: используем тот же self.client!
        resp = self.client.post(url, data=payload)

        if resp.status_code == 200 or resp.status_code == 302:
            print("🎉 Успешный вход!")
            self.save_cookies() # Сохраняем на будущее
            return True
        else:
            print("❌ Ошибка входа:", resp.status_code)
            return False

# Использование
session = AuthSession()
session.login("<https://site.com/login>", "admin", "12345")

# Дальше парсим через session.client
response = session.client.get("<https://site.com/secret-data>")

4. Нюанс: Bearer Token (JWT)

На современных сайтах (SPA) вместо кук используют Token. Сервер в ответ на логин присылает JSON: {"access_token": "eyJhbG..."}.

Что делать:

  1. Сохрани этот токен в файл (как строку).
  2. При каждом запросе добавляй его в Заголовки, а не в куки:

    headers = {
        "Authorization": f"Bearer {saved_token}"
    }
    client.get(url, headers=headers)
    

🧠 Резюме Архитектора:

  1. Никогда не логинься в цикле.
  2. Используй httpx.Client() или requests.Session(), чтобы держать стейт.
  3. Всегда проверяй наличие CSRF токена перед отправкой пароля.
  4. Сохраняй куки/токены в файл. Твой парсер должен уметь "просыпаться" уже залогиненным.