Initial commit: Supermarket scraper MVP
This commit is contained in:
423
cursor_forbidden_api.md
Normal file
423
cursor_forbidden_api.md
Normal 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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user