raveninsta
Version:
CLI Tool para Instagram - Mapeamento bi-direcional: Username ↔ ID
305 lines (257 loc) • 9.81 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
const puppeteer = require('puppeteer');
const axios = require('axios');
/**
* 🕷️ Scraper - Coleta dados e screenshots de perfis do Instagram
* @class Scraper
* @description Gerencia coleta de dados, captura de screenshots e salvamento de perfis
*/
class Scraper {
/**
* 🏗️ Construtor do Scraper
* @param {string} outputDir - Diretório de saída para os arquivos
*/
constructor(outputDir = './perfis') {
this.outputDir = outputDir;
this.ensureOutputDir();
}
/** 📁 Garantir que diretório de saída existe */
ensureOutputDir() {
try {
fs.ensureDirSync(this.outputDir);
} catch (error) {
console.log('❌ Erro ao criar diretório de saída:', error.message);
}
}
/**
* 🗂️ Obter caminho do perfil
* @param {string} profileId - ID do perfil
* @returns {string} Caminho completo do diretório do perfil
*/
getProfilePath(profileId) {
return path.join(this.outputDir, profileId.toString());
}
/**
* 📄 Gerar relatório formatado do perfil
* @async
* @param {string} profilePath - Caminho do diretório do perfil
* @param {Object} data - Dados do perfil
*/
async generateReport(profilePath, data) {
const reportContent = `
🔍 RELATÓRIO DO PERFIL - Raveninsta
📅 Gerado em: ${new Date().toLocaleString('pt-BR')}
==================================================
🔄 MAPEAMENTO BIDIRECIONAL
------------------------------
🆔 ID: ${data.id}
🔤 Username: @${data.username}
📊 ${data.analysis_count || 1} análises realizadas
👤 DADOS DO PERFIL
------------------------------
👤 Nome: ${data.full_name || 'N/A'}
📌 Biografia: ${data.biography || 'N/A'}
📊 ESTATÍSTICAS
------------------------------
👥 Seguidores: ${data.followers?.toLocaleString() || 'N/A'}
➡️ Seguindo: ${data.following?.toLocaleString() || 'N/A'}
📝 Posts: ${data.posts_count?.toLocaleString() || 'N/A'}
🔒 Privado: ${data.is_private ? 'Sim' : 'Não'}
✅ Verificado: ${data.is_verified ? 'Sim' : 'Não'}
${data.username_history && data.username_history.length > 1 ? `
📝 HISTÓRICO DE USERNAMES
------------------------------
${data.username_history.map((username, index) =>
`${username} ${index === data.username_history.length - 1 ? '🟢 ATUAL' : ''}`
).join(' → ')}
` : ''}
📅 Primeira análise: ${data.first_analysis ? new Date(data.first_analysis).toLocaleString('pt-BR') : 'N/A'}
🔄 Última atualização: ${data.last_updated ? new Date(data.last_updated).toLocaleString('pt-BR') : 'N/A'}
`.trim();
try {
await fs.writeFile(path.join(profilePath, 'relatorio.txt'), reportContent);
} catch (error) {
console.log('❌ Erro ao gerar relatório:', error.message);
}
}
/**
* 💾 Salvar dados do perfil com histórico
* @async
* @param {string} profileId - ID do perfil
* @param {Object} data - Dados atuais do perfil
* @param {Buffer} [screenshotBuffer=null] - Buffer da screenshot
* @returns {Promise<boolean>} True se salvamento foi bem-sucedido
*/
async saveProfileData(profileId, data, screenshotBuffer = null) {
const profilePath = this.getProfilePath(profileId);
try {
await fs.ensureDir(profilePath);
let existingData = {};
const dataFile = path.join(profilePath, 'dados.json');
if (await fs.pathExists(dataFile)) {
existingData = await fs.readJson(dataFile);
}
const updatedData = {
...existingData,
...data,
last_updated: new Date().toISOString(),
analysis_count: (existingData.analysis_count || 0) + 1
};
if (existingData.username && existingData.username !== data.username) {
updatedData.username_history = [
...(existingData.username_history || [existingData.username]),
data.username
];
} else if (!existingData.username_history && data.username) {
updatedData.username_history = [data.username];
}
if (!existingData.first_analysis) {
updatedData.first_analysis = new Date().toISOString();
}
await fs.writeJson(dataFile, updatedData, { spaces: 2 });
if (screenshotBuffer) {
await fs.writeFile(path.join(profilePath, 'perfil.png'), screenshotBuffer);
}
await this.generateReport(profilePath, updatedData);
return true;
} catch (error) {
console.log('❌ Erro ao salvar dados do perfil:', error.message);
return false;
}
}
/**
* 🔍 Obter dados do perfil via API
* @async
* @param {string} identifier - ID ou username do perfil
* @returns {Promise<Object>} Dados formatados do perfil
* @throws {Error} Se perfil não for encontrado
*/
async getProfileData(identifier) {
try {
const { getSessionManager } = require('./session');
const sessionManager = getSessionManager();
const sessionData = await sessionManager.loadSession();
console.log('🔍 Buscando dados do perfil...');
const response = await axios.get(
`https://www.instagram.com/api/v1/users/web_profile_info/?username=${identifier}`,
{
headers: {
'User-Agent': sessionData['User-Agent'],
'Cookie': sessionData.Cookie,
'X-IG-App-ID': '936619743392459',
'X-IG-WWW-Claim': '0'
},
timeout: 15000
}
);
if (response.data && response.data.data && response.data.data.user) {
const user = response.data.data.user;
const profileData = {
id: user.id,
username: user.username,
full_name: user.full_name || '',
biography: user.biography || '',
followers: user.edge_followed_by?.count || 0,
following: user.edge_follow?.count || 0,
posts_count: user.edge_owner_to_timeline_media?.count || 0,
is_private: user.is_private || false,
is_verified: user.is_verified || false,
profile_pic_url: user.profile_pic_url_hd || user.profile_pic_url || ''
};
console.log('✅ Dados do perfil obtidos com sucesso');
return profileData;
} else {
throw new Error('Resposta da API inválida');
}
} catch (error) {
console.log('❌ Erro ao obter dados do perfil:', error.message);
if (/^\d+$/.test(identifier)) {
throw new Error(`Não foi possível encontrar perfil com ID: ${identifier}`);
} else {
throw new Error(`Não foi possível encontrar perfil com username: ${identifier}`);
}
}
}
/**
* 📸 Capturar screenshot do perfil
* @async
* @param {string} identifier - Username do perfil
* @param {Object} sessionData - Dados da sessão autenticada
* @returns {Promise<Buffer|null>} Buffer da imagem ou null em caso de erro
*/
async captureScreenshot(identifier, sessionData) {
let browser;
try {
console.log('📸 Capturando screenshot do perfil...');
browser = await puppeteer.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--window-size=1920,1080'
]
});
const page = await browser.newPage();
await page.setUserAgent(sessionData['User-Agent']);
await page.setCookie(...sessionData.cookies);
await page.goto(`https://www.instagram.com/${identifier}/`, {
waitUntil: 'networkidle0',
timeout: 30000
});
await new Promise(resolve => setTimeout(resolve, 3000));
const screenshot = await page.screenshot({
fullPage: false,
type: 'png'
});
console.log('✅ Screenshot capturada com sucesso');
return screenshot;
} catch (error) {
console.log('❌ Erro ao capturar screenshot:', error.message);
return null;
} finally {
if (browser) {
await browser.close();
}
}
}
/**
* 🎯 Analisar perfil completo
* @async
* @param {string} identifier - ID ou username do perfil
* @param {string} outputDir - Diretório de saída
* @param {boolean} captureScreenshot - Se deve capturar screenshot
* @returns {Promise<Object>} Dados completos do perfil
* @throws {Error} Se análise falhar
*/
async getProfile(identifier, outputDir = './perfis', captureScreenshot = true) {
try {
if (outputDir !== './perfis') {
this.outputDir = outputDir;
this.ensureOutputDir();
}
console.log(`🎯 Analisando: ${identifier}`);
const { getSessionManager } = require('./session');
const sessionManager = getSessionManager();
const sessionData = await sessionManager.loadSession();
const profileData = await this.getProfileData(identifier);
let screenshotBuffer = null;
if (captureScreenshot) {
screenshotBuffer = await this.captureScreenshot(profileData.username, sessionData);
}
const saved = await this.saveProfileData(profileData.id, profileData, screenshotBuffer);
if (saved) {
console.log('\n💾 Dados salvos em:', this.getProfilePath(profileData.id));
console.log(`👤 Perfil: @${profileData.username} (ID: ${profileData.id})`);
console.log(`📊 Seguidores: ${profileData.followers?.toLocaleString() || 'N/A'}`);
console.log(`📝 Posts: ${profileData.posts_count?.toLocaleString() || 'N/A'}`);
}
return profileData;
} catch (error) {
console.log('❌ Erro na análise do perfil:', error.message);
throw error;
}
}
}
module.exports = Scraper;