Node modules retirado
This commit is contained in:
@@ -1,2 +1,2 @@
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Icon=folder-games
|
Icon=folder-games
|
||||||
|
|||||||
284
.gitignore
vendored
284
.gitignore
vendored
@@ -1,143 +1,143 @@
|
|||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
pids
|
pids
|
||||||
*.pid
|
*.pid
|
||||||
*.seed
|
*.seed
|
||||||
*.pid.lock
|
*.pid.lock
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
lib-cov
|
lib-cov
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
# Coverage directory used by tools like istanbul
|
||||||
coverage
|
coverage
|
||||||
*.lcov
|
*.lcov
|
||||||
|
|
||||||
# nyc test coverage
|
# nyc test coverage
|
||||||
.nyc_output
|
.nyc_output
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
.grunt
|
.grunt
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
# Bower dependency directory (https://bower.io/)
|
||||||
bower_components
|
bower_components
|
||||||
|
|
||||||
# node-waf configuration
|
# node-waf configuration
|
||||||
.lock-wscript
|
.lock-wscript
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
build/Release
|
build/Release
|
||||||
|
|
||||||
# Dependency directories
|
# Dependency directories
|
||||||
node_modules/
|
node_modules/
|
||||||
jspm_packages/
|
jspm_packages/
|
||||||
|
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
web_modules/
|
web_modules/
|
||||||
|
|
||||||
# TypeScript cache
|
# TypeScript cache
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
# Optional npm cache directory
|
# Optional npm cache directory
|
||||||
.npm
|
.npm
|
||||||
|
|
||||||
# Optional eslint cache
|
# Optional eslint cache
|
||||||
.eslintcache
|
.eslintcache
|
||||||
|
|
||||||
# Optional stylelint cache
|
# Optional stylelint cache
|
||||||
.stylelintcache
|
.stylelintcache
|
||||||
|
|
||||||
# Optional REPL history
|
# Optional REPL history
|
||||||
.node_repl_history
|
.node_repl_history
|
||||||
|
|
||||||
# Output of 'npm pack'
|
# Output of 'npm pack'
|
||||||
*.tgz
|
*.tgz
|
||||||
|
|
||||||
# Yarn Integrity file
|
# Yarn Integrity file
|
||||||
.yarn-integrity
|
.yarn-integrity
|
||||||
|
|
||||||
# dotenv environment variable files
|
# dotenv environment variable files
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
.cache
|
.cache
|
||||||
.parcel-cache
|
.parcel-cache
|
||||||
|
|
||||||
# Next.js build output
|
# Next.js build output
|
||||||
.next
|
.next
|
||||||
out
|
out
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
# Nuxt.js build / generate output
|
||||||
.nuxt
|
.nuxt
|
||||||
dist
|
dist
|
||||||
.output
|
.output
|
||||||
|
|
||||||
# Gatsby files
|
# Gatsby files
|
||||||
.cache/
|
.cache/
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
# public
|
# public
|
||||||
|
|
||||||
# vuepress build output
|
# vuepress build output
|
||||||
.vuepress/dist
|
.vuepress/dist
|
||||||
|
|
||||||
# vuepress v2.x temp directory
|
# vuepress v2.x temp directory
|
||||||
.temp
|
.temp
|
||||||
|
|
||||||
# Sveltekit cache directory
|
# Sveltekit cache directory
|
||||||
.svelte-kit/
|
.svelte-kit/
|
||||||
|
|
||||||
# vitepress build output
|
# vitepress build output
|
||||||
**/.vitepress/dist
|
**/.vitepress/dist
|
||||||
|
|
||||||
# vitepress cache directory
|
# vitepress cache directory
|
||||||
**/.vitepress/cache
|
**/.vitepress/cache
|
||||||
|
|
||||||
# Docusaurus cache and generated files
|
# Docusaurus cache and generated files
|
||||||
.docusaurus
|
.docusaurus
|
||||||
|
|
||||||
# Serverless directories
|
# Serverless directories
|
||||||
.serverless/
|
.serverless/
|
||||||
|
|
||||||
# FuseBox cache
|
# FuseBox cache
|
||||||
.fusebox/
|
.fusebox/
|
||||||
|
|
||||||
# DynamoDB Local files
|
# DynamoDB Local files
|
||||||
.dynamodb/
|
.dynamodb/
|
||||||
|
|
||||||
# Firebase cache directory
|
# Firebase cache directory
|
||||||
.firebase/
|
.firebase/
|
||||||
|
|
||||||
# TernJS port file
|
# TernJS port file
|
||||||
.tern-port
|
.tern-port
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
.vscode-test
|
.vscode-test
|
||||||
|
|
||||||
# pnpm
|
# pnpm
|
||||||
.pnpm-store
|
.pnpm-store
|
||||||
|
|
||||||
# yarn v3
|
# yarn v3
|
||||||
.pnp.*
|
.pnp.*
|
||||||
.yarn/*
|
.yarn/*
|
||||||
!.yarn/patches
|
!.yarn/patches
|
||||||
!.yarn/plugins
|
!.yarn/plugins
|
||||||
!.yarn/releases
|
!.yarn/releases
|
||||||
!.yarn/sdks
|
!.yarn/sdks
|
||||||
!.yarn/versions
|
!.yarn/versions
|
||||||
|
|
||||||
# Vite files
|
# Vite files
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
.vite/
|
.vite/
|
||||||
3102
package-lock.json
generated
3102
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
38
package.json
38
package.json
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.16.1",
|
"axios": "^1.16.1",
|
||||||
"puppeteer": "^25.0.4",
|
"puppeteer": "^25.0.4",
|
||||||
"puppeteer-extra": "^3.3.6",
|
"puppeteer-extra": "^3.3.6",
|
||||||
"puppeteer-extra-plugin-stealth": "^2.11.2"
|
"puppeteer-extra-plugin-stealth": "^2.11.2"
|
||||||
},
|
},
|
||||||
"name": "codevscodesjs",
|
"name": "codevscodesjs",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "commonjs"
|
"type": "commonjs"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
const ProductEntity = require('../domain/ProductEntity');
|
const ProductEntity = require('../domain/ProductEntity');
|
||||||
|
|
||||||
class ProcessPromotionsUseCase {
|
class ProcessPromotionsUseCase {
|
||||||
constructor(scraper, affiliateGenerator, apiClient) {
|
constructor(scraper, affiliateGenerator, apiClient) {
|
||||||
this.scraper = scraper;
|
this.scraper = scraper;
|
||||||
this.affiliateGenerator = affiliateGenerator;
|
this.affiliateGenerator = affiliateGenerator;
|
||||||
this.apiClient = apiClient;
|
this.apiClient = apiClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
async executar() {
|
async executar() {
|
||||||
console.log("Iniciando processamento das promoções...");
|
console.log("Iniciando processamento das promoções...");
|
||||||
|
|
||||||
const dadosBrutos = await this.scraper.extrairDados();
|
const dadosBrutos = await this.scraper.extrairDados();
|
||||||
const dadosComAfiliacao = await this.affiliateGenerator.converterLista(dadosBrutos);
|
const dadosComAfiliacao = await this.affiliateGenerator.converterLista(dadosBrutos);
|
||||||
|
|
||||||
let enviados = 0;
|
let enviados = 0;
|
||||||
|
|
||||||
for (const dado of dadosComAfiliacao) {
|
for (const dado of dadosComAfiliacao) {
|
||||||
const produto = new ProductEntity(dado);
|
const produto = new ProductEntity(dado);
|
||||||
|
|
||||||
if (produto.isValid()) {
|
if (produto.isValid()) {
|
||||||
await this.apiClient.enviarProduto(produto);
|
await this.apiClient.enviarProduto(produto);
|
||||||
enviados++;
|
enviados++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Processo finalizado! ${enviados} produtos foram enviados para a API.`);
|
console.log(`Processo finalizado! ${enviados} produtos foram enviados para a API.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ProcessPromotionsUseCase;
|
module.exports = ProcessPromotionsUseCase;
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
class ProductEntity {
|
class ProductEntity {
|
||||||
constructor({ nomeProduto, precoOriginal, precoPromocao, porcetagemPromo, imagem, linkProduto }) {
|
constructor({ nomeProduto, precoOriginal, precoPromocao, porcetagemPromo, imagem, linkProduto }) {
|
||||||
this.nomeProduto = nomeProduto || 'sem título';
|
this.nomeProduto = nomeProduto || 'sem título';
|
||||||
this.precoOriginal = precoOriginal || 0;
|
this.precoOriginal = precoOriginal || 0;
|
||||||
this.precoPromocao = precoPromocao || 0;
|
this.precoPromocao = precoPromocao || 0;
|
||||||
this.porcetagemPromo = porcetagemPromo || 0;
|
this.porcetagemPromo = porcetagemPromo || 0;
|
||||||
this.imagem = imagem || '';
|
this.imagem = imagem || '';
|
||||||
this.linkProduto = linkProduto || '';
|
this.linkProduto = linkProduto || '';
|
||||||
this.plataforma = 'Mercado Livre'; // Regra de negócio fixa para este robô
|
this.plataforma = 'Mercado Livre'; // Regra de negócio fixa para este robô
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid() {
|
isValid() {
|
||||||
return this.nomeProduto !== 'Sem título' && this.precoPromocao > 0;
|
return this.nomeProduto !== 'Sem título' && this.precoPromocao > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ProductEntity;
|
module.exports = ProductEntity;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
class Produto {
|
class Produto {
|
||||||
constructor({ nomeProduto, precoOriginal, precoPromocao, porcentagemPromo, imagem, linkProduto }) {
|
constructor({ nomeProduto, precoOriginal, precoPromocao, porcentagemPromo, imagem, linkProduto }) {
|
||||||
this.nomeProduto = nomeProduto;
|
this.nomeProduto = nomeProduto;
|
||||||
this.precoOriginal = precoOriginal;
|
this.precoOriginal = precoOriginal;
|
||||||
this.precoPromocao = precoPromocao;
|
this.precoPromocao = precoPromocao;
|
||||||
this.porcentagemPromo = porcentagemPromo;
|
this.porcentagemPromo = porcentagemPromo;
|
||||||
this.imagem = imagem;
|
this.imagem = imagem;
|
||||||
this.linkProduto = linkProduto;
|
this.linkProduto = linkProduto;
|
||||||
this.plataforma = "Mercado Livre";
|
this.plataforma = "Mercado Livre";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exemplo de Clean Code: médoto com resposabilidade única para validação
|
// Exemplo de Clean Code: médoto com resposabilidade única para validação
|
||||||
isValido() {
|
isValido() {
|
||||||
return this.nomeProduto !== 'sem título' && this.precoPromocao > 0;
|
return this.nomeProduto !== 'sem título' && this.precoPromocao > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = Produto;
|
module.exports = Produto;
|
||||||
28
src/index.js
28
src/index.js
@@ -1,15 +1,15 @@
|
|||||||
const MercadoLivreScraper = require('./infrastructure/MercadoLivreScraper');
|
const MercadoLivreScraper = require('./infrastructure/MercadoLivreScraper');
|
||||||
const AffiliateGenerator = require('./infrastructure/AffiliateGenerator');
|
const AffiliateGenerator = require('./infrastructure/AffiliateGenerator');
|
||||||
const ApiClient = require('./infrastructure/ApiClient');
|
const ApiClient = require('./infrastructure/ApiClient');
|
||||||
const ProcessPromotionsUseCase = require('./application/ProcessPromotionsUseCase');
|
const ProcessPromotionsUseCase = require('./application/ProcessPromotionsUseCase');
|
||||||
|
|
||||||
// 1. Criamos as instâncias de cada ferramenta
|
// 1. Criamos as instâncias de cada ferramenta
|
||||||
const scraper = new MercadoLivreScraper();
|
const scraper = new MercadoLivreScraper();
|
||||||
const affiliateGenerator = new AffiliateGenerator();
|
const affiliateGenerator = new AffiliateGenerator();
|
||||||
const apiClient = new ApiClient();
|
const apiClient = new ApiClient();
|
||||||
|
|
||||||
// 2. Entregamos todas as ferramentas para o Caso de Uso trabalhar
|
// 2. Entregamos todas as ferramentas para o Caso de Uso trabalhar
|
||||||
const useCase = new ProcessPromotionsUseCase(scraper, affiliateGenerator, apiClient);
|
const useCase = new ProcessPromotionsUseCase(scraper, affiliateGenerator, apiClient);
|
||||||
|
|
||||||
// 3. Damos o play no robô
|
// 3. Damos o play no robô
|
||||||
useCase.executar();
|
useCase.executar();
|
||||||
@@ -1,80 +1,80 @@
|
|||||||
const puppeteer = require('puppeteer-extra');
|
const puppeteer = require('puppeteer-extra');
|
||||||
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
|
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
|
||||||
puppeteer.use(StealthPlugin());
|
puppeteer.use(StealthPlugin());
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
class AffiliateGenerator {
|
class AffiliateGenerator {
|
||||||
async converterLista(produtosBrutos) {
|
async converterLista(produtosBrutos) {
|
||||||
console.log('\nIniciando conversão para links de afiliado (Modo Docker/Invisível)...');
|
console.log('\nIniciando conversão para links de afiliado (Modo Docker/Invisível)...');
|
||||||
|
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
headless: false,
|
headless: false,
|
||||||
args: [
|
args: [
|
||||||
'--no-sandbox',
|
'--no-sandbox',
|
||||||
'--disable-setuid-sandbox',
|
'--disable-setuid-sandbox',
|
||||||
'--disable-dev-shm-usage',
|
'--disable-dev-shm-usage',
|
||||||
'--disable-blink-features=AutomationControlled',
|
'--disable-blink-features=AutomationControlled',
|
||||||
'--disable-gpu',
|
'--disable-gpu',
|
||||||
'--window-size=1920,1080'
|
'--window-size=1920,1080'
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
|
|
||||||
// 1. Aquecimento: Acessa a home primeiro para gerar tokens de sessão naturais
|
// 1. Aquecimento: Acessa a home primeiro para gerar tokens de sessão naturais
|
||||||
await page.goto('https://www.mercadolivre.com.br', { waitUntil: 'domcontentloaded' });
|
await page.goto('https://www.mercadolivre.com.br', { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
// 2. Injeta os cookies de autorização
|
// 2. Injeta os cookies de autorização
|
||||||
if (fs.existsSync('./cookies.json')) {
|
if (fs.existsSync('./cookies.json')) {
|
||||||
const cookies = JSON.parse(fs.readFileSync('./cookies.json', 'utf8'));
|
const cookies = JSON.parse(fs.readFileSync('./cookies.json', 'utf8'));
|
||||||
await page.setCookie(...cookies);
|
await page.setCookie(...cookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Acessa o gerador com a sessão já "aquecida"
|
// 3. Acessa o gerador com a sessão já "aquecida"
|
||||||
await page.goto('https://www.mercadolivre.com.br/afiliados/gerador', { waitUntil: 'networkidle2' });
|
await page.goto('https://www.mercadolivre.com.br/afiliados/gerador', { waitUntil: 'networkidle2' });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await page.waitForSelector('textarea#url-0', { timeout: 15000 });
|
await page.waitForSelector('textarea#url-0', { timeout: 15000 });
|
||||||
} catch (erro) {
|
} catch (erro) {
|
||||||
console.log('\n❌ ERRO CRÍTICO: Bloqueio detectado ou cookies inválidos.');
|
console.log('\n❌ ERRO CRÍTICO: Bloqueio detectado ou cookies inválidos.');
|
||||||
await browser.close();
|
await browser.close();
|
||||||
return []; // Retorna array vazio para não quebrar a API com Erro 500
|
return []; // Retorna array vazio para não quebrar a API com Erro 500
|
||||||
}
|
}
|
||||||
|
|
||||||
const produtosConvertidos = [];
|
const produtosConvertidos = [];
|
||||||
|
|
||||||
for (const produto of produtosBrutos) {
|
for (const produto of produtosBrutos) {
|
||||||
try {
|
try {
|
||||||
await page.evaluate(() => { document.querySelector('textarea#url-0').value = ''; });
|
await page.evaluate(() => { document.querySelector('textarea#url-0').value = ''; });
|
||||||
await page.type('textarea#url-0', produto.linkProduto);
|
await page.type('textarea#url-0', produto.linkProduto);
|
||||||
|
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
const spans = Array.from(document.querySelectorAll('span.andes-button__content'));
|
const spans = Array.from(document.querySelectorAll('span.andes-button__content'));
|
||||||
const btnGerar = spans.find(s => s.textContent.trim() === 'Gerar');
|
const btnGerar = spans.find(s => s.textContent.trim() === 'Gerar');
|
||||||
if (btnGerar && btnGerar.parentElement) btnGerar.parentElement.click();
|
if (btnGerar && btnGerar.parentElement) btnGerar.parentElement.click();
|
||||||
});
|
});
|
||||||
|
|
||||||
await new Promise(r => setTimeout(r, 2000)); // Tempo extra para o ML processar
|
await new Promise(r => setTimeout(r, 2000)); // Tempo extra para o ML processar
|
||||||
|
|
||||||
const linkAfiliado = await page.evaluate(() => {
|
const linkAfiliado = await page.evaluate(() => {
|
||||||
const textareas = document.querySelectorAll('textarea[id^="textfield-copyLink"]');
|
const textareas = document.querySelectorAll('textarea[id^="textfield-copyLink"]');
|
||||||
return textareas.length > 0 ? textareas[textareas.length - 1].value : null;
|
return textareas.length > 0 ? textareas[textareas.length - 1].value : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (linkAfiliado && linkAfiliado.includes('meli.la')) {
|
if (linkAfiliado && linkAfiliado.includes('meli.la')) {
|
||||||
produto.linkProduto = linkAfiliado;
|
produto.linkProduto = linkAfiliado;
|
||||||
produtosConvertidos.push(produto); // Só adiciona se a conversão foi um sucesso
|
produtosConvertidos.push(produto); // Só adiciona se a conversão foi um sucesso
|
||||||
console.log(`✅ Convertido: ${produto.nomeProduto.substring(0, 30)}...`);
|
console.log(`✅ Convertido: ${produto.nomeProduto.substring(0, 30)}...`);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Link inválido ou não gerado.");
|
throw new Error("Link inválido ou não gerado.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`⚠️ Descartado (Falha na conversão): ${produto.nomeProduto.substring(0, 30)}...`);
|
console.log(`⚠️ Descartado (Falha na conversão): ${produto.nomeProduto.substring(0, 30)}...`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
return produtosConvertidos;
|
return produtosConvertidos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = AffiliateGenerator;
|
module.exports = AffiliateGenerator;
|
||||||
@@ -1,27 +1,27 @@
|
|||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
|
||||||
class ApiClient {
|
class ApiClient {
|
||||||
constructor(baseURL) {
|
constructor(baseURL) {
|
||||||
// Forçando a URL real da sua API, ignorando o localhost
|
// Forçando a URL real da sua API, ignorando o localhost
|
||||||
this.urlDestino = 'https://duck-api.stackpanel.com.br/duck-api/produto/salvar';
|
this.urlDestino = 'https://duck-api.stackpanel.com.br/duck-api/produto/salvar';
|
||||||
}
|
}
|
||||||
|
|
||||||
async enviarProduto(produto) {
|
async enviarProduto(produto) {
|
||||||
// Montando o body EXATAMENTE no formato que a sua API exige
|
// Montando o body EXATAMENTE no formato que a sua API exige
|
||||||
const payload = {
|
const payload = {
|
||||||
nomeProduto: produto.nomeProduto,
|
nomeProduto: produto.nomeProduto,
|
||||||
descricaoProduto: `Desconto de ${produto.porcentagemPromo}%. Link da oferta: ${produto.linkProduto}`,
|
descricaoProduto: `Desconto de ${produto.porcentagemPromo}%. Link da oferta: ${produto.linkProduto}`,
|
||||||
precoProdutoAtualizado: produto.precoPromocao,
|
precoProdutoAtualizado: produto.precoPromocao,
|
||||||
nomePlataforma: "Mercado Livre"
|
nomePlataforma: "Mercado Livre"
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.post(this.urlDestino, payload);
|
await axios.post(this.urlDestino, payload);
|
||||||
console.log(`✅ Enviado com sucesso: ${produto.nomeProduto}`);
|
console.log(`✅ Enviado com sucesso: ${produto.nomeProduto}`);
|
||||||
} catch (erro) {
|
} catch (erro) {
|
||||||
console.log(`❌ Erro ao enviar ${produto.nomeProduto} | Motivo: ${erro.message}`);
|
console.log(`❌ Erro ao enviar ${produto.nomeProduto} | Motivo: ${erro.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ApiClient;
|
module.exports = ApiClient;
|
||||||
@@ -1,86 +1,86 @@
|
|||||||
const puppeteer = require('puppeteer-extra');
|
const puppeteer = require('puppeteer-extra');
|
||||||
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
|
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
|
||||||
|
|
||||||
// Ativa o modo de camuflagem para evitar detecção
|
// Ativa o modo de camuflagem para evitar detecção
|
||||||
puppeteer.use(StealthPlugin());
|
puppeteer.use(StealthPlugin());
|
||||||
|
|
||||||
class MercadoLivreScraper {
|
class MercadoLivreScraper {
|
||||||
async extrairDados() {
|
async extrairDados() {
|
||||||
console.log('Iniciando extração de promoções do Mercado Livre...');
|
console.log('Iniciando extração de promoções do Mercado Livre...');
|
||||||
|
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
headless: true,
|
headless: true,
|
||||||
args: [
|
args: [
|
||||||
'--no-sandbox',
|
'--no-sandbox',
|
||||||
'--disable-setuid-sandbox',
|
'--disable-setuid-sandbox',
|
||||||
'--disable-blink-features=AutomationControlled'
|
'--disable-blink-features=AutomationControlled'
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
|
|
||||||
// Define um User-Agent e resolução de tela de um usuário real
|
// Define um User-Agent e resolução de tela de um usuário real
|
||||||
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
|
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
|
||||||
await page.setViewport({ width: 1366, height: 768 });
|
await page.setViewport({ width: 1366, height: 768 });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await page.goto('https://www.mercadolivre.com.br/ofertas', { waitUntil: 'networkidle2' });
|
await page.goto('https://www.mercadolivre.com.br/ofertas', { waitUntil: 'networkidle2' });
|
||||||
await page.waitForSelector('.poly-card', { timeout: 15000 });
|
await page.waitForSelector('.poly-card', { timeout: 15000 });
|
||||||
|
|
||||||
const produtos = await page.evaluate(() => {
|
const produtos = await page.evaluate(() => {
|
||||||
const itens = document.querySelectorAll('.poly-card');
|
const itens = document.querySelectorAll('.poly-card');
|
||||||
const resultados = [];
|
const resultados = [];
|
||||||
|
|
||||||
itens.forEach(item => {
|
itens.forEach(item => {
|
||||||
const elementoTitulo = item.querySelector('.poly-component__title') || item.querySelector('h2');
|
const elementoTitulo = item.querySelector('.poly-component__title') || item.querySelector('h2');
|
||||||
const nomeProduto = elementoTitulo ? elementoTitulo.innerText.trim() : 'Sem título';
|
const nomeProduto = elementoTitulo ? elementoTitulo.innerText.trim() : 'Sem título';
|
||||||
|
|
||||||
const elementoMarca = item.querySelector('.poly-component__brand');
|
const elementoMarca = item.querySelector('.poly-component__brand');
|
||||||
const marca = elementoMarca ? elementoMarca.innerText.trim() : '';
|
const marca = elementoMarca ? elementoMarca.innerText.trim() : '';
|
||||||
const tituloFinal = marca ? `${marca} - ${nomeProduto}` : nomeProduto;
|
const tituloFinal = marca ? `${marca} - ${nomeProduto}` : nomeProduto;
|
||||||
|
|
||||||
const elementoLink = item.querySelector('a');
|
const elementoLink = item.querySelector('a');
|
||||||
const linkProduto = elementoLink ? elementoLink.href : 'Sem link';
|
const linkProduto = elementoLink ? elementoLink.href : 'Sem link';
|
||||||
|
|
||||||
const elementoImagem = item.querySelector('img');
|
const elementoImagem = item.querySelector('img');
|
||||||
const imagem = elementoImagem ? (elementoImagem.getAttribute('data-src') || elementoImagem.src) : 'Sem imagem';
|
const imagem = elementoImagem ? (elementoImagem.getAttribute('data-src') || elementoImagem.src) : 'Sem imagem';
|
||||||
|
|
||||||
const elementoPreco = item.querySelector('.andes-money-amount__fraction');
|
const elementoPreco = item.querySelector('.andes-money-amount__fraction');
|
||||||
const precoPromocao = elementoPreco ? parseFloat(elementoPreco.innerText.replace('.', '').replace(',', '.')) : 0;
|
const precoPromocao = elementoPreco ? parseFloat(elementoPreco.innerText.replace('.', '').replace(',', '.')) : 0;
|
||||||
|
|
||||||
const elementoPrecoOriginal = item.querySelector('s .andes-money-amount__fraction') || item.querySelector('.poly-price__old .andes-money-amount__fraction');
|
const elementoPrecoOriginal = item.querySelector('s .andes-money-amount__fraction') || item.querySelector('.poly-price__old .andes-money-amount__fraction');
|
||||||
const precoOriginal = elementoPrecoOriginal ? parseFloat(elementoPrecoOriginal.innerText.replace('.', '').replace(',', '.')) : precoPromocao;
|
const precoOriginal = elementoPrecoOriginal ? parseFloat(elementoPrecoOriginal.innerText.replace('.', '').replace(',', '.')) : precoPromocao;
|
||||||
|
|
||||||
let porcentagemPromo = 0;
|
let porcentagemPromo = 0;
|
||||||
if (precoOriginal > 0 && precoOriginal > precoPromocao) {
|
if (precoOriginal > 0 && precoOriginal > precoPromocao) {
|
||||||
porcentagemPromo = Math.round(((precoOriginal - precoPromocao) / precoOriginal) * 100);
|
porcentagemPromo = Math.round(((precoOriginal - precoPromocao) / precoOriginal) * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
resultados.push({
|
resultados.push({
|
||||||
nomeProduto: tituloFinal,
|
nomeProduto: tituloFinal,
|
||||||
linkProduto: linkProduto,
|
linkProduto: linkProduto,
|
||||||
precoOriginal: precoOriginal,
|
precoOriginal: precoOriginal,
|
||||||
precoPromocao: precoPromocao,
|
precoPromocao: precoPromocao,
|
||||||
porcentagemPromo: porcentagemPromo,
|
porcentagemPromo: porcentagemPromo,
|
||||||
imagem: imagem
|
imagem: imagem
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return resultados;
|
return resultados;
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`\n--- DIAGNÓSTICO FINAL ---`);
|
console.log(`\n--- DIAGNÓSTICO FINAL ---`);
|
||||||
console.log(`O robô enxergou ${produtos.length} cartões na página em modo invisível.`);
|
console.log(`O robô enxergou ${produtos.length} cartões na página em modo invisível.`);
|
||||||
console.log(`-------------------------\n`);
|
console.log(`-------------------------\n`);
|
||||||
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
return produtos;
|
return produtos;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro durante a extração:', error.message);
|
console.error('Erro durante a extração:', error.message);
|
||||||
await browser.close();
|
await browser.close();
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MercadoLivreScraper;
|
module.exports = MercadoLivreScraper;
|
||||||
Reference in New Issue
Block a user