Технологии и защита 10 мин чтения

Парсинг сайта с авторизацией: полное руководство с примерами кода

Парсинг сайтов за авторизацией: cookies и сессии, токены, CSRF, капча при входе и примеры логина из кода.

КP
Команда Parsing.agency
Сбор данных под задачи бизнеса
Опубликовано: 19 апреля 2025

Большинство задач веб-скрапинга решаются простым GET-запросом к публичной странице. Но как только нужные данные оказываются «за логином» — в личном кабинете, закрытом разделе или под платной подпиской — обычный запрос возвращает форму входа или ошибку 401/403. Чтобы добраться до содержимого, парсер должен сначала пройти авторизацию ровно так же, как это делает браузер пользователя.

Где это применяется чаще всего

Самый распространённый сценарий парсинга с авторизацией — мониторинг цен интернет-магазинов. Типичная ситуация выглядит так: у вас есть поставщик, актуальные цены и остатки которого доступны только в личном кабинете на его сайте. Полноценного доступа к данным по API нет, выгрузок в нужном формате тоже, а цены меняются часто и вручную их переносить нерационально.

При этом важно подчеркнуть: такой доступ согласован с поставщиком и не нарушает правил использования сайта. То есть вы автоматизируете получение тех данных, которые вам и так разрешено видеть под своей учётной записью, — просто делаете это программно, а не кликами мышью. Именно в таком виде парсинг с авторизацией является легитимным рабочим инструментом, а не способом обойти ограничения.

Прежде чем писать код, всегда стоит убедиться, что:

  • доступ к данным разрешён владельцем ресурса (договор, письменное согласие, условия партнёрской программы);
  • автоматический сбор не запрещён пользовательским соглашением (ToS) и файлом robots.txt;
  • нагрузка на сайт остаётся разумной и не мешает его работе;
  • персональные данные третьих лиц не собираются и не обрабатываются без оснований.

Как устроена авторизация: четыре основных механизма

Чтобы выбрать подход к коду, нужно понять, каким способом сайт аутентифицирует пользователя. На практике встречаются четыре основных варианта.

1. Форма входа и сессионные cookie. Самый частый случай. Вы отправляете логин и пароль POST-запросом на endpoint авторизации, сервер в ответ устанавливает сессионный cookie (например, sessionid или PHPSESSID), и дальше этот cookie прикрепляется ко всем запросам. Сессия живёт, пока действителен cookie.

2. CSRF-токен. Многие формы защищены токеном, который прячется в HTML страницы входа (в скрытом поле или в meta-теге) либо выдаётся отдельным cookie. Перед отправкой формы нужно сначала загрузить страницу логина, вытащить токен и передать его вместе с учётными данными. Без этого сервер отклонит запрос.

3. Токены (Bearer / JWT). Современные сайты и SPA часто авторизуют пользователя через API, возвращая токен в JSON. Дальше токен передаётся в заголовке Authorization: Bearer <token>. Cookie тут может вообще не использоваться.

4. HTTP Basic Auth. Простейший вариант: логин и пароль кодируются в base64 и передаются в заголовке Authorization. Встречается у внутренних систем и некоторых API.

Отдельная сложность — сайты, которые формируют контент через JavaScript. Там обычного HTTP-клиента недостаточно, и нужен «headless»-браузер (Playwright, Puppeteer, Selenium), который выполняет скрипты и отдаёт уже готовый DOM.

Python: requests с сессией

requests.Session() автоматически хранит cookie между запросами — это идеальная основа для парсинга с авторизацией. Пример с предварительным получением CSRF-токена:

python
import requests
from bs4 import BeautifulSoup

LOGIN_URL = "https://supplier.example.com/login"
PRICES_URL = "https://supplier.example.com/account/prices"

session = requests.Session()
session.headers.update({
    "User-Agent": "Mozilla/5.0 (compatible; PriceMonitor/1.0)"
})

# 1. Загружаем страницу логина и достаём CSRF-токен
login_page = session.get(LOGIN_URL, timeout=30)
soup = BeautifulSoup(login_page.text, "html.parser")
csrf_token = soup.select_one('input[name="csrf_token"]')["value"]

# 2. Отправляем форму входа
payload = {
    "username": "your_login",
    "password": "your_password",
    "csrf_token": csrf_token,
}
resp = session.post(LOGIN_URL, data=payload, timeout=30)
resp.raise_for_status()

if "Личный кабинет" not in resp.text:
    raise RuntimeError("Авторизация не удалась — проверьте учётные данные")

# 3. Сессия установлена — запрашиваем закрытую страницу с ценами
prices_page = session.get(PRICES_URL, timeout=30)
soup = BeautifulSoup(prices_page.text, "html.parser")

for row in soup.select("table.prices tr"):
    cells = row.select("td")
    if len(cells) >= 2:
        name = cells[0].get_text(strip=True)
        price = cells[1].get_text(strip=True)
        print(f"{name}: {price}")

Хорошая практика — хранить логин и пароль не в коде, а в переменных окружения (os.environ) или в .env-файле, чтобы случайно не закоммитить их в репозиторий.

Python: авторизация по токену (API)

Если сайт авторизует через JSON-API и возвращает токен, код проще:

python
import requests

auth = requests.post(
    "https://supplier.example.com/api/auth/login",
    json={"login": "your_login", "password": "your_password"},
    timeout=30,
)
auth.raise_for_status()
token = auth.json()["access_token"]

headers = {"Authorization": f"Bearer {token}"}
data = requests.get(
    "https://supplier.example.com/api/prices",
    headers=headers,
    timeout=30,
).json()

for item in data["items"]:
    print(item["sku"], item["price"])

Python: Playwright для сайтов на JavaScript

Когда личный кабинет — это SPA и цены подгружаются скриптами, помогает headless-браузер. Playwright умеет логиниться как живой пользователь и даже сохранять состояние сессии в файл, чтобы не входить заново при каждом запуске.

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    context = browser.new_context()
    page = context.new_page()

    # Авторизация
    page.goto("https://supplier.example.com/login")
    page.fill("input[name='username']", "your_login")
    page.fill("input[name='password']", "your_password")
    page.click("button[type='submit']")
    page.wait_for_url("**/account/**")

    # Сохраняем сессию, чтобы переиспользовать позже
    context.storage_state(path="auth_state.json")

    # Переходим к ценам и ждём загрузки данных
    page.goto("https://supplier.example.com/account/prices")
    page.wait_for_selector("table.prices")

    rows = page.query_selector_all("table.prices tr")
    for row in rows:
        cells = row.query_selector_all("td")
        if len(cells) >= 2:
            print(cells[0].inner_text(), "—", cells[1].inner_text())

    browser.close()

Сохранённый auth_state.json потом подключается через browser.new_context(storage_state="auth_state.json") — и шаг логина можно пропускать, пока сессия не истекла.

В Node для хранения cookie между запросами используют связку axios + tough-cookie + axios-cookiejar-support.

javascript
const axios = require("axios");
const { wrapper } = require("axios-cookiejar-support");
const { CookieJar } = require("tough-cookie");
const cheerio = require("cheerio");

const jar = new CookieJar();
const client = wrapper(axios.create({ jar, withCredentials: true }));

async function run() {
  // 1. Получаем CSRF-токен со страницы логина
  const loginPage = await client.get("https://supplier.example.com/login");
  const $ = cheerio.load(loginPage.data);
  const csrf = $('input[name="csrf_token"]').val();

  // 2. Логинимся
  await client.post(
    "https://supplier.example.com/login",
    new URLSearchParams({
      username: "your_login",
      password: "your_password",
      csrf_token: csrf,
    }),
  );

  // 3. Запрашиваем цены
  const pricesPage = await client.get(
    "https://supplier.example.com/account/prices",
  );
  const $$ = cheerio.load(pricesPage.data);

  $$("table.prices tr").each((_, el) => {
    const cells = $$(el).find("td");
    if (cells.length >= 2) {
      const name = $$(cells[0]).text().trim();
      const price = $$(cells[1]).text().trim();
      console.log(`${name}: ${price}`);
    }
  });
}

run().catch(console.error);

Node.js: Puppeteer для динамических страниц

javascript
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();

  await page.goto("https://supplier.example.com/login");
  await page.type("input[name='username']", "your_login");
  await page.type("input[name='password']", "your_password");
  await Promise.all([
    page.click("button[type='submit']"),
    page.waitForNavigation(),
  ]);

  await page.goto("https://supplier.example.com/account/prices");
  await page.waitForSelector("table.prices");

  const prices = await page.evaluate(() =>
    Array.from(document.querySelectorAll("table.prices tr"))
      .map((row) => {
        const td = row.querySelectorAll("td");
        return td.length >= 2
          ? { name: td[0].innerText.trim(), price: td[1].innerText.trim() }
          : null;
      })
      .filter(Boolean),
  );

  console.log(prices);
  await browser.close();
})();

В PHP cookie между запросами хранят в файле через опции COOKIEJAR и COOKIEFILE.

php
<?php
$cookieFile = __DIR__ . "/cookies.txt";

function curlInit(string $cookieFile) {
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_COOKIEJAR      => $cookieFile,
        CURLOPT_COOKIEFILE     => $cookieFile,
        CURLOPT_USERAGENT      => "Mozilla/5.0 (compatible; PriceMonitor/1.0)",
    ]);
    return $ch;
}

// 1. Загружаем страницу логина и достаём CSRF-токен
$ch = curlInit($cookieFile);
curl_setopt($ch, CURLOPT_URL, "https://supplier.example.com/login");
$html = curl_exec($ch);

preg_match('/name="csrf_token"\s+value="([^"]+)"/', $html, $m);
$csrf = $m[1] ?? "";

// 2. Отправляем форму входа
curl_setopt($ch, CURLOPT_URL, "https://supplier.example.com/login");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    "username"   => "your_login",
    "password"   => "your_password",
    "csrf_token" => $csrf,
]));
curl_exec($ch);

// 3. Запрашиваем цены
curl_setopt($ch, CURLOPT_URL, "https://supplier.example.com/account/prices");
curl_setopt($ch, CURLOPT_POST, false);
$pricesHtml = curl_exec($ch);
curl_close($ch);

// Разбор HTML
$dom = new DOMDocument();
@$dom->loadHTML($pricesHtml);
$xpath = new DOMXPath($dom);
foreach ($xpath->query("//table[@class='prices']//tr") as $row) {
    $cells = $row->getElementsByTagName("td");
    if ($cells->length >= 2) {
        echo trim($cells->item(0)->textContent) . ": "
           . trim($cells->item(1)->textContent) . PHP_EOL;
    }
}

Go: net/http с cookiejar

В стандартной библиотеке Go есть net/http/cookiejar, который автоматически ведёт cookie за вас.

go
package main

import (
    "fmt"
    "net/http"
    "net/http/cookiejar"
    "net/url"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

func main() {
    jar, _ := cookiejar.New(nil)
    client := &http.Client{Jar: jar}

    // 1. Получаем CSRF-токен
    resp, _ := client.Get("https://supplier.example.com/login")
    doc, _ := goquery.NewDocumentFromReader(resp.Body)
    resp.Body.Close()
    csrf, _ := doc.Find(`input[name="csrf_token"]`).Attr("value")

    // 2. Логинимся
    form := url.Values{
        "username":   {"your_login"},
        "password":   {"your_password"},
        "csrf_token": {csrf},
    }
    client.Post(
        "https://supplier.example.com/login",
        "application/x-www-form-urlencoded",
        strings.NewReader(form.Encode()),
    )

    // 3. Парсим цены
    pricesResp, _ := client.Get("https://supplier.example.com/account/prices")
    pricesDoc, _ := goquery.NewDocumentFromReader(pricesResp.Body)
    pricesResp.Body.Close()

    pricesDoc.Find("table.prices tr").Each(func(_ int, s *goquery.Selection) {
        cells := s.Find("td")
        if cells.Length() >= 2 {
            name := strings.TrimSpace(cells.Eq(0).Text())
            price := strings.TrimSpace(cells.Eq(1).Text())
            fmt.Printf("%s: %s\n", name, price)
        }
    })
}

Практические рекомендации

Переиспользуйте сессию. Не логиньтесь при каждом запросе — это лишняя нагрузка и риск блокировки. Сохраняйте cookie или токен и обновляйте только когда сессия истекла.

Обрабатывайте истечение сессии. Cookie и токены имеют срок жизни. Заложите проверку: если запрос вернул редирект на форму логина или код 401 — повторно авторизуйтесь и повторите запрос.

Соблюдайте разумный темп. Делайте паузы между запросами (например, 1–3 секунды), не запускайте десятки параллельных потоков. Это и вежливо по отношению к серверу поставщика, и снижает шанс попасть под защиту от ботов.

Храните секреты безопасно. Логин, пароль и токены — в переменных окружения или в защищённом хранилище, а не в коде и тем более не в публичном репозитории.

Будьте устойчивы к изменениям вёрстки. Сайты меняются, селекторы ломаются. Логируйте ошибки парсинга и настройте уведомления, чтобы быстро замечать, когда структура страницы поменялась.

Указывайте честный User-Agent и при возможности контактные данные. Если поставщик согласовал доступ, идентифицируемый бот упрощает диагностику на его стороне, если что-то пойдёт не так.

Заключение

Технически парсинг с авторизацией сводится к тому, чтобы воспроизвести шаги, которые браузер делает при входе: получить и отправить форму (с CSRF-токеном, если он есть), сохранить сессионный cookie или токен и прикреплять его к последующим запросам. Для статичных страниц достаточно HTTP-клиента с поддержкой сессий (requests, axios, cURL, net/http), для динамических — headless-браузера (Playwright, Puppeteer).

Самое же главное лежит не в коде, а в основаниях для сбора данных. Парсинг личного кабинета поставщика для мониторинга цен — рабочий и легитимный инструмент ровно тогда, когда доступ согласован с владельцем ресурса и не нарушает правил использования. Технология одинаково хорошо работает в обе стороны, поэтому ответственность за её корректное применение остаётся на вас.