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