206 lines
7.0 KiB
JavaScript
206 lines
7.0 KiB
JavaScript
/**
|
|
* mercadolivre.js - Scraper para Mercado Livre
|
|
*
|
|
* Monitora a página de Ofertas do Dia do Mercado Livre
|
|
* e extrai: nome, preço, preço original, desconto, imagem e link.
|
|
*/
|
|
|
|
const { config } = require('../config');
|
|
const { scrapeWithRetry } = require('../utils/browser');
|
|
|
|
/**
|
|
* Executa o scraping da página de ofertas do Mercado Livre
|
|
* @returns {Array<Object>} Lista de ofertas extraídas
|
|
*/
|
|
async function scrapeMercadoLivre() {
|
|
console.log('🤝 [Mercado Livre] Iniciando scraping...');
|
|
|
|
const offers = await scrapeWithRetry(async (page) => {
|
|
// Navegar para a página de ofertas
|
|
await page.goto(config.urls.mercadolivre, {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: config.scraping.pageTimeout,
|
|
});
|
|
|
|
// Aguardar carregamento dos cards de produto
|
|
await page.waitForSelector('.promotion-item, .poly-card, .andes-card, [class*="poly-card"], .deal-card', {
|
|
timeout: 10000,
|
|
}).catch(() => {
|
|
console.log(' ⚠️ [Mercado Livre] Seletores primários não encontrados, tentando alternativas...');
|
|
});
|
|
|
|
// Scroll para carregar mais itens
|
|
await autoScroll(page);
|
|
|
|
// Extrair dados dos produtos
|
|
const products = await page.evaluate((maxOffers) => {
|
|
const items = [];
|
|
|
|
// Seletores para cards do Mercado Livre
|
|
const cardSelectors = [
|
|
'.poly-card',
|
|
'.promotion-item',
|
|
'.andes-card',
|
|
'[class*="poly-card"]',
|
|
'.deal-card',
|
|
'.ui-search-layout__item',
|
|
];
|
|
|
|
let cards = [];
|
|
for (const selector of cardSelectors) {
|
|
cards = document.querySelectorAll(selector);
|
|
if (cards.length > 0) break;
|
|
}
|
|
|
|
cards.forEach((card) => {
|
|
if (items.length >= maxOffers) return;
|
|
|
|
try {
|
|
// Nome do produto
|
|
const nameEl = card.querySelector(
|
|
'.poly-component__title, .promotion-item__title, .ui-search-item__title, [class*="title"], h2, h3'
|
|
);
|
|
const name = nameEl ? nameEl.textContent.trim() : '';
|
|
|
|
// Preço atual
|
|
const priceEl = card.querySelector(
|
|
'.poly-price__current .andes-money-amount, .andes-money-amount--cents-superscript, [class*="price"], .price-tag-fraction'
|
|
);
|
|
let price = '';
|
|
if (priceEl) {
|
|
// Mercado Livre separa reais e centavos em elementos diferentes
|
|
const fractionEl = priceEl.querySelector('.andes-money-amount__fraction, .price-tag-fraction');
|
|
const centsEl = priceEl.querySelector('.andes-money-amount__cents, .price-tag-cents');
|
|
const currencyEl = priceEl.querySelector('.andes-money-amount__currency-symbol');
|
|
|
|
if (fractionEl) {
|
|
const currency = currencyEl ? currencyEl.textContent.trim() : 'R$';
|
|
const fraction = fractionEl.textContent.trim();
|
|
const cents = centsEl ? centsEl.textContent.trim() : '00';
|
|
price = `${currency} ${fraction},${cents}`;
|
|
} else {
|
|
price = priceEl.textContent.trim();
|
|
}
|
|
}
|
|
|
|
// Preço original
|
|
const originalPriceEl = card.querySelector(
|
|
'.andes-money-amount--previous, [class*="original-price"], s .andes-money-amount, .price-tag-deleted'
|
|
);
|
|
let originalPrice = '';
|
|
if (originalPriceEl) {
|
|
const origFraction = originalPriceEl.querySelector('.andes-money-amount__fraction, .price-tag-fraction');
|
|
if (origFraction) {
|
|
originalPrice = `R$ ${origFraction.textContent.trim()}`;
|
|
} else {
|
|
originalPrice = originalPriceEl.textContent.trim();
|
|
}
|
|
}
|
|
|
|
// Desconto
|
|
const discountEl = card.querySelector(
|
|
'.poly-component__discount, [class*="discount"], .ui-search-price__second-line__label'
|
|
);
|
|
const discount = discountEl ? discountEl.textContent.trim() : '';
|
|
|
|
// Imagem
|
|
const imgEl = card.querySelector(
|
|
'img[src*="http2.mlstatic"], img[data-src*="http2.mlstatic"], img[src*="meli"], img'
|
|
);
|
|
let image = '';
|
|
if (imgEl) {
|
|
image = imgEl.getAttribute('src') || imgEl.getAttribute('data-src') || '';
|
|
// Garantir URL de imagem válida
|
|
if (image && image.startsWith('data:')) {
|
|
image = imgEl.getAttribute('data-src') || '';
|
|
}
|
|
}
|
|
|
|
// Link do produto
|
|
const linkEl = card.querySelector(
|
|
'a[href*="mercadolivre.com.br"], a[href*="produto.mercadolivre"], a[href*="/MLB-"], a[href]'
|
|
);
|
|
let link = linkEl ? linkEl.getAttribute('href') : '';
|
|
if (link && !link.startsWith('http')) {
|
|
link = 'https://www.mercadolivre.com.br' + link;
|
|
}
|
|
|
|
// Cupom (ML ocasionalmente mostra cupons)
|
|
const couponEl = card.querySelector(
|
|
'[class*="coupon"], [class*="cupom"], [class*="highlight"]'
|
|
);
|
|
let coupon = null;
|
|
if (couponEl) {
|
|
const couponText = couponEl.textContent.trim();
|
|
if (couponText.toLowerCase().includes('cupom') || couponText.toLowerCase().includes('off')) {
|
|
coupon = couponText;
|
|
}
|
|
}
|
|
|
|
// Frete grátis (informação extra útil)
|
|
const freeShippingEl = card.querySelector(
|
|
'[class*="free-shipping"], .poly-component__shipping, [class*="shipping"]'
|
|
);
|
|
const hasFreeShipping = freeShippingEl &&
|
|
freeShippingEl.textContent.toLowerCase().includes('grátis');
|
|
|
|
if (name && price && link) {
|
|
const offer = {
|
|
name,
|
|
price,
|
|
originalPrice: originalPrice || null,
|
|
discount: discount || null,
|
|
image: image || null,
|
|
link,
|
|
coupon,
|
|
store: 'mercadolivre',
|
|
};
|
|
|
|
// Adicionar info de frete grátis ao cupom/observação
|
|
if (hasFreeShipping && !coupon) {
|
|
offer.coupon = '🚚 Frete Grátis';
|
|
} else if (hasFreeShipping && coupon) {
|
|
offer.coupon += ' | 🚚 Frete Grátis';
|
|
}
|
|
|
|
items.push(offer);
|
|
}
|
|
} catch (e) {
|
|
// Ignorar cards com erro
|
|
}
|
|
});
|
|
|
|
return items;
|
|
}, config.scraping.maxOffersPerSite);
|
|
|
|
console.log(` ✅ [Mercado Livre] ${products.length} ofertas extraídas.`);
|
|
return products;
|
|
|
|
}, 'Mercado Livre');
|
|
|
|
return offers;
|
|
}
|
|
|
|
/**
|
|
* Scroll automático
|
|
*/
|
|
async function autoScroll(page) {
|
|
await page.evaluate(async () => {
|
|
await new Promise((resolve) => {
|
|
let totalHeight = 0;
|
|
const distance = 400;
|
|
const timer = setInterval(() => {
|
|
window.scrollBy(0, distance);
|
|
totalHeight += distance;
|
|
if (totalHeight >= 3000) {
|
|
clearInterval(timer);
|
|
resolve();
|
|
}
|
|
}, 200);
|
|
});
|
|
});
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
}
|
|
|
|
module.exports = { scrapeMercadoLivre };
|