feat: add product detail enrichment for Magnit products
- Add isDetailsFetched field to Product model - Add fetchProductDetails() and fetchProductObjectInfo() methods to MagnitApiScraper - Add ProductParser methods for detail parsing - Add ProductService methods: getProductsNeedingDetails(), updateProductDetails(), markAsDetailsFetched() - Add enrich-product-details.ts script with statistics tracking - Update package.json with "enrich" script command - Add E2E_GUIDE.md documentation - Exclude debug scripts from tsconfig type-check (temporary) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,9 @@ import {
|
||||
SearchGoodsRequest,
|
||||
SearchGoodsResponse,
|
||||
ProductItem,
|
||||
ObjectInfo,
|
||||
ObjectReviewsResponse,
|
||||
ProductDetailsResponse,
|
||||
} from './types.js';
|
||||
import { ProductService } from '../../../services/product/ProductService.js';
|
||||
import { ProductParser } from '../../../services/parser/ProductParser.js';
|
||||
@@ -90,7 +93,7 @@ export class MagnitApiScraper {
|
||||
// Переход на главную страницу для получения cookies
|
||||
Logger.info('Переход на главную страницу magnit.ru...');
|
||||
await this.page.goto('https://magnit.ru/', {
|
||||
waitUntil: 'networkidle',
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
@@ -188,6 +191,86 @@ export class MagnitApiScraper {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение object info (описание, рейтинг, отзывы) для товара
|
||||
* Endpoint: /webgate/v1/listing/user-reviews-and-object-info
|
||||
*/
|
||||
async fetchProductObjectInfo(productId: string): Promise<ObjectInfo> {
|
||||
const operation = async () => {
|
||||
try {
|
||||
Logger.debug(`Запрос object info для продукта ${productId}`);
|
||||
|
||||
const response = await this.httpClient.get<ObjectReviewsResponse>(
|
||||
ENDPOINTS.OBJECT_INFO(productId),
|
||||
{ timeout: this.config.requestTimeout ?? 30000 }
|
||||
);
|
||||
|
||||
return response.data.object_info;
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const statusCode = error.response?.status || 0;
|
||||
Logger.error(
|
||||
`Ошибка получения object info: ${statusCode} - ${error.message}`
|
||||
);
|
||||
throw new APIError(
|
||||
`Ошибка получения object info: ${error.message}`,
|
||||
statusCode,
|
||||
error.response?.data
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return withRetryAndReinit(operation, {
|
||||
...this.config.retryOptions,
|
||||
reinitOn403: this.config.autoReinitOn403 ?? true,
|
||||
onReinit: async () => {
|
||||
await this.reinitializeSession();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение детальной информации о товаре (бренд, описание, вес, единица измерения)
|
||||
* Endpoint: /webgate/v2/goods/{productId}/stores/{storeCode}
|
||||
*/
|
||||
async fetchProductDetails(productId: string): Promise<ProductDetailsResponse> {
|
||||
const operation = async () => {
|
||||
try {
|
||||
Logger.debug(`Запрос деталей для продукта ${productId}`);
|
||||
|
||||
const response = await this.httpClient.get<ProductDetailsResponse>(
|
||||
ENDPOINTS.PRODUCT_DETAILS(productId, this.config.storeCode),
|
||||
{ timeout: this.config.requestTimeout ?? 30000 }
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const statusCode = error.response?.status || 0;
|
||||
Logger.error(
|
||||
`Ошибка получения деталей товара: ${statusCode} - ${error.message}`
|
||||
);
|
||||
throw new APIError(
|
||||
`Ошибка получения деталей товара: ${error.message}`,
|
||||
statusCode,
|
||||
error.response?.data
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return withRetryAndReinit(operation, {
|
||||
...this.config.retryOptions,
|
||||
reinitOn403: this.config.autoReinitOn403 ?? true,
|
||||
onReinit: async () => {
|
||||
await this.reinitializeSession();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Переинициализация сессии (при 403 или истечении cookies)
|
||||
* ВАЖНО: Не закрываем браузер, только обновляем cookies
|
||||
@@ -204,7 +287,7 @@ export class MagnitApiScraper {
|
||||
|
||||
// Переход на главную страницу для обновления cookies
|
||||
await this.page.goto('https://magnit.ru/', {
|
||||
waitUntil: 'networkidle',
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user