Files
Promoloko/src/scrapers/mercadolivre.js
2026-04-06 20:49:50 -03:00

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 };