Initial commit: Supermarket scraper MVP

This commit is contained in:
2025-12-28 23:29:30 +05:00
commit 19c0426cdc
30 changed files with 4839 additions and 0 deletions

423
cursor_forbidden_api.md Normal file
View File

@@ -0,0 +1,423 @@
# Ошибка Forbidden в API Магнит
_Exported on 12/14/2025 at 03:06:00 GMT+5 from Cursor (2.2.20)_
---
**User**
@.cursor/plans/supermarket_scraper_system_1af4ed29.plan.md
Вопрос сейчас только про api магнит
https://magnit.ru/webgate/v2/goods/1000257489/stores/992301?storetype=6&catalogtype=1
ответ Forbidden
Transaction ID: 2b7f613f-b7fe-4f95-9dd4-b30c1f5cb4a5
Хотя в браузере работает у них на сайте
https://magnit.ru/product/1000257489-chay_chernyy_greenfield_english_edition_100pak?shopCode=992301&shopType=6
вот заголовки в браузере
URL запроса:
https://magnit.ru/webgate/v2/goods/1000257489/stores/992301?storetype=6&catalogtype=1
Метод запроса:
GET
Код статуса:
200 OK
Удаленный адрес:
178.248.238.72:443
Правило для URL перехода:
origin
content-encoding:
gzip
content-type:
application/json
date:
Sat, 13 Dec 2025 21:41:24 GMT
document-policy:
js-profiling
referrer-policy:
origin
server:
QRATOR
strict-transport-security:
max-age=31536000; includeSubDomains
vary:
Accept-Encoding,Accept-Encoding
x-content-type-options:
nosniff
x-frame-options:
SAMEORIGIN
x-trace-id:
544850473b9c35d2d1c00538cc1d16c6
x-xss-protection:
1
:authority:
magnit.ru
:method:
GET
:path:
/webgate/v2/goods/1000257489/stores/992301?storetype=6&catalogtype=1
:scheme:
https
accept:
*/*
accept-encoding:
gzip, deflate, br, zstd
accept-language:
ru,en-US;q=0.9,en;q=0.8
baggage:
sentry-environment=production,sentry-public_key=7992e659d14bf5d8da08d2e6a0706d0a,sentry-trace_id=596895b1cc7b4c05935d288b1cc91e8a,sentry-sampled=false,sentry-sample_rand=0.6716192411675364,sentry-sample_rate=0.05
content-type:
application/json
cookie:
nmg_dt=; shopCode=992301; x_shop_type=ME; mg_udi=9D4909B8-9F79-5B1D-8457-EA519EC9AA3F; oxxfgh=ef3934b0-03b7-44fc-b6d8-9b0dd7412ea3%230%237884000000%235000%231800000%2312840; uwyii=b172df84-99bb-4b86-51b5-909db1474363; nmg_sp=Y; mg_uac=1; mg_adlt=Y; uwyiert=65182acc-a917-c963-d7fe-a14c0cab4e8d
dnt:
1
priority:
u=1, i
referer:
https://magnit.ru/
sec-ch-ua:
"Not?A_Brand";v="99", "Chromium";v="130"
sec-ch-ua-mobile:
?0
sec-ch-ua-platform:
"Windows"
sec-fetch-dest:
empty
sec-fetch-mode:
cors
sec-fetch-site:
same-origin
sec-gpc:
1
sentry-trace:
596895b1cc7b4c05935d288b1cc91e8a-b581de5bc3a814d5-0
user-agent:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36
x-app-version:
2025.12.12-16.56
x-client-name:
magnit
x-device-id:
9D4909B8-9F79-5B1D-8457-EA519EC9AA3F
x-device-platform:
Web
x-device-tag:
disabled
x-new-magnit:
true
x-platform-version:
Windows Chrome 130
---
**Cursor**
API Магнита возвращает 403, потому что требуется авторизация и корректные заголовки. Анализирую требования и способы их соблюдения:
API Магнита возвращает 403, потому что требуется авторизация и корректные заголовки. По заголовкам из браузера видно, что нужны:
## Проблема
API проверяет:
1. Cookies (сессия)
2. Заголовки, имитирующие браузер
3. Referer и origin
## Решение
### Вариант 1: Использовать Playwright для получения cookies и заголовков
Сначала зайти на сайт через Playwright, получить cookies, затем использовать их в HTTP-запросах:
```typescript
// src/scrapers/api/magnit/MagnitApiScraper.ts
import { chromium, Browser, Page } from 'playwright';
import axios, { AxiosInstance } from 'axios';
export class MagnitApiScraper {
private browser: Browser | null = null;
private page: Page | null = null;
private httpClient: AxiosInstance;
private cookies: string = '';
private deviceId: string = '';
constructor() {
this.httpClient = axios.create({
baseURL: 'https://magnit.ru',
headers: {
'accept': '*/*',
'accept-language': 'ru,en-US;q=0.9,en;q=0.8',
'content-type': 'application/json',
'referer': 'https://magnit.ru/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
'x-app-version': '2025.12.12-16.56',
'x-client-name': 'magnit',
'x-device-platform': 'Web',
'x-device-tag': 'disabled',
'x-new-magnit': 'true',
'x-platform-version': 'Windows Chrome 130',
}
});
}
async initialize() {
// Запускаем браузер и получаем cookies
this.browser = await chromium.launch({ headless: true });
this.page = await this.browser.newPage();
// Переходим на главную страницу
await this.page.goto('https://magnit.ru/', { waitUntil: 'networkidle' });
// Получаем cookies
const cookies = await this.page.context().cookies();
this.cookies = cookies.map(c => `${c.name}=${c.value}`).join('; ');
// Извлекаем device-id из cookies
const mgUdiCookie = cookies.find(c => c.name === 'mg_udi');
if (mgUdiCookie) {
this.deviceId = mgUdiCookie.value;
} else {
// Генерируем новый device-id если нет
this.deviceId = this.generateDeviceId();
}
// Обновляем заголовки с device-id
this.httpClient.defaults.headers['x-device-id'] = this.deviceId;
this.httpClient.defaults.headers['cookie'] = this.cookies;
}
async getProductStores(productId: string, storeCode: string = '992301') {
if (!this.page) {
await this.initialize();
}
try {
const response = await this.httpClient.get(
`/webgate/v2/goods/${productId}/stores/${storeCode}`,
{
params: {
storetype: '6',
catalogtype: '1'
}
}
);
return response.data;
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 403) {
// Если 403, обновляем сессию
await this.initialize();
return this.getProductStores(productId, storeCode);
}
throw error;
}
}
private generateDeviceId(): string {
// Генерация UUID формата: 9D4909B8-9F79-5B1D-8457-EA519EC9AA3F
return 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.replace(/[x]/g, () => {
return (Math.random() * 16 | 0).toString(16).toUpperCase();
});
}
async close() {
if (this.browser) {
await this.browser.close();
}
}
}
```
### Вариант 2: Использовать Playwright напрямую для API-запросов
Делать запросы через контекст браузера Playwright:
```typescript
async getProductStoresViaBrowser(productId: string, storeCode: string = '992301') {
if (!this.page) {
await this.initialize();
}
// Используем request API Playwright, который автоматически использует cookies браузера
const response = await this.page!.request.get(
`https://magnit.ru/webgate/v2/goods/${productId}/stores/${storeCode}?storetype=6&catalogtype=1`,
{
headers: {
'accept': '*/*',
'referer': 'https://magnit.ru/',
'x-app-version': '2025.12.12-16.56',
'x-client-name': 'magnit',
'x-device-platform': 'Web',
'x-new-magnit': 'true',
}
}
);
if (!response.ok()) {
throw new Error(`API request failed: ${response.status()}`);
}
return await response.json();
}
```
## Важные моменты
1. Обязательные заголовки:
- `x-device-id` — из cookie `mg_udi`
- `x-client-name: magnit`
- `x-device-platform: Web`
- `referer: https://magnit.ru/`
- `cookie` — все cookies из сессии
2. Cookies, которые нужно передавать:
- `shopCode=992301`
- `mg_udi` (device-id)
- `oxxfgh`, `uwyii`, `uwyiert` (сессионные)
3. Рекомендация: использовать Playwright для инициализации сессии и получения cookies, затем делать запросы через `page.request` или передавать cookies в axios.
Нужна полная реализация `MagnitApiScraper` с этой логикой?
```typescript
// src/scrapers/api/magnit/MagnitApiScraper.ts
import { chromium, Browser, Page } from 'playwright';
import axios, { AxiosInstance } from 'axios';
export class MagnitApiScraper {
private browser: Browser | null = null;
private page: Page | null = null;
private httpClient: AxiosInstance;
private cookies: string = '';
private deviceId: string = '';
constructor() {
this.httpClient = axios.create({
baseURL: 'https://magnit.ru',
headers: {
'accept': '*/*',
'accept-language': 'ru,en-US;q=0.9,en;q=0.8',
'content-type': 'application/json',
'referer': 'https://magnit.ru/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
'x-app-version': '2025.12.12-16.56',
'x-client-name': 'magnit',
'x-device-platform': 'Web',
'x-device-tag': 'disabled',
'x-new-magnit': 'true',
'x-platform-version': 'Windows Chrome 130',
}
});
}
async initialize() {
// Запускаем браузер и получаем cookies
this.browser = await chromium.launch({ headless: true });
this.page = await this.browser.newPage();
// Переходим на главную страницу
await this.page.goto('https://magnit.ru/', { waitUntil: 'networkidle' });
// Получаем cookies
const cookies = await this.page.context().cookies();
this.cookies = cookies.map(c => `${c.name}=${c.value}`).join('; ');
// Извлекаем device-id из cookies
const mgUdiCookie = cookies.find(c => c.name === 'mg_udi');
if (mgUdiCookie) {
this.deviceId = mgUdiCookie.value;
} else {
// Генерируем новый device-id если нет
this.deviceId = this.generateDeviceId();
}
// Обновляем заголовки с device-id
this.httpClient.defaults.headers['x-device-id'] = this.deviceId;
this.httpClient.defaults.headers['cookie'] = this.cookies;
}
async getProductStores(productId: string, storeCode: string = '992301') {
if (!this.page) {
await this.initialize();
}
try {
const response = await this.httpClient.get(
`/webgate/v2/goods/${productId}/stores/${storeCode}`,
{
params: {
storetype: '6',
catalogtype: '1'
}
}
);
return response.data;
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 403) {
// Если 403, обновляем сессию
await this.initialize();
return this.getProductStores(productId, storeCode);
}
throw error;
}
}
private generateDeviceId(): string {
// Генерация UUID формата: 9D4909B8-9F79-5B1D-8457-EA519EC9AA3F
return 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.replace(/[x]/g, () => {
return (Math.random() * 16 | 0).toString(16).toUpperCase();
});
}
async close() {
if (this.browser) {
await this.browser.close();
}
}
}
```
```typescript
async getProductStoresViaBrowser(productId: string, storeCode: string = '992301') {
if (!this.page) {
await this.initialize();
}
// Используем request API Playwright, который автоматически использует cookies браузера
const response = await this.page!.request.get(
`https://magnit.ru/webgate/v2/goods/${productId}/stores/${storeCode}?storetype=6&catalogtype=1`,
{
headers: {
'accept': '*/*',
'referer': 'https://magnit.ru/',
'x-app-version': '2025.12.12-16.56',
'x-client-name': 'magnit',
'x-device-platform': 'Web',
'x-new-magnit': 'true',
}
}
);
if (!response.ok()) {
throw new Error(`API request failed: ${response.status()}`);
}
return await response.json();
}
```
Проверяю структуру проекта и план, чтобы понять текущее состояние и что нужно реализовать:
Проверяю, есть ли уже код для работы с API Магнита. Если нет, предложу решение для обхода 403 Forbidden.