Большинство задач веб-скрапинга решаются простым 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-токена:
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 и возвращает токен, код проще:
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 умеет логиниться как живой пользователь и даже сохранять состояние сессии в файл, чтобы не входить заново при каждом запуске.
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.js: axios с сохранением cookie
В Node для хранения cookie между запросами используют связку axios + tough-cookie + axios-cookiejar-support.
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 для динамических страниц
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: cURL с сессионными cookie
В PHP cookie между запросами хранят в файле через опции COOKIEJAR и COOKIEFILE.
<?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 за вас.
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).
Самое же главное лежит не в коде, а в основаниях для сбора данных. Парсинг личного кабинета поставщика для мониторинга цен — рабочий и легитимный инструмент ровно тогда, когда доступ согласован с владельцем ресурса и не нарушает правил использования. Технология одинаково хорошо работает в обе стороны, поэтому ответственность за её корректное применение остаётся на вас.