- main_scraper.py: Main entry point, consolidates data from all sources - scrapers/programathor_scraper.py: Scraper for ProgramaThor - scrapers/geekhunter_scraper.py: Scraper for GeekHunter - requirements.txt: Python dependencies (requests, beautifulsoup4, pandas) - README.md: Documentation with usage instructions - Modular architecture for easy addition of new sites
130 lines
3.3 KiB
Python
130 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
🐴 JobScraper MultiSite - Raspador de Vagas Multi-Plataforma
|
|
=============================================================
|
|
Consolida vagas de emprego de múltiplos sites de tecnologia.
|
|
"""
|
|
|
|
import pandas as pd
|
|
from datetime import datetime
|
|
import os
|
|
|
|
# Imports dos scrapers
|
|
from scrapers.programathor_scraper import scrape_programathor
|
|
from scrapers.geekhunter_scraper import scrape_geekhunter
|
|
|
|
# Configuração: Sites a serem raspados
|
|
SITES = {
|
|
'programathor': scrape_programathor,
|
|
'geekhunter': scrape_geekhunter,
|
|
}
|
|
|
|
# Pasta de output
|
|
OUTPUT_DIR = 'output'
|
|
|
|
|
|
def ensure_output_dir():
|
|
"""Cria a pasta de output se não existir."""
|
|
if not os.path.exists(OUTPUT_DIR):
|
|
os.makedirs(OUTPUT_DIR)
|
|
print(f"📁 Pasta '{OUTPUT_DIR}' criada.")
|
|
|
|
|
|
def run_scrapers(sites_to_run: list = None) -> pd.DataFrame:
|
|
"""
|
|
Executa os scrapers configurados e consolida os resultados.
|
|
|
|
Args:
|
|
sites_to_run: Lista de sites para raspar. Se None, raspa todos.
|
|
|
|
Returns:
|
|
DataFrame consolidado com todas as vagas.
|
|
"""
|
|
if sites_to_run is None:
|
|
sites_to_run = list(SITES.keys())
|
|
|
|
all_vagas = []
|
|
|
|
print("=" * 60)
|
|
print("🐴 JobScraper MultiSite - Iniciando raspagem...")
|
|
print("=" * 60)
|
|
|
|
for site_name in sites_to_run:
|
|
if site_name not in SITES:
|
|
print(f"⚠️ Site '{site_name}' não configurado. Pulando...")
|
|
continue
|
|
|
|
print(f"\n📡 Processando: {site_name.upper()}")
|
|
print("-" * 40)
|
|
|
|
scraper_func = SITES[site_name]
|
|
df = scraper_func()
|
|
|
|
if not df.empty:
|
|
all_vagas.append(df)
|
|
|
|
if all_vagas:
|
|
consolidated_df = pd.concat(all_vagas, ignore_index=True)
|
|
# Remover duplicatas baseado no link
|
|
consolidated_df.drop_duplicates(subset=['link'], inplace=True)
|
|
return consolidated_df
|
|
|
|
return pd.DataFrame()
|
|
|
|
|
|
def save_results(df: pd.DataFrame, filename: str = None) -> str:
|
|
"""
|
|
Salva o DataFrame consolidado em CSV.
|
|
|
|
Args:
|
|
df: DataFrame com as vagas.
|
|
filename: Nome do arquivo (opcional).
|
|
|
|
Returns:
|
|
Caminho do arquivo salvo.
|
|
"""
|
|
ensure_output_dir()
|
|
|
|
if filename is None:
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
filename = f'vagas_consolidadas_{timestamp}.csv'
|
|
|
|
filepath = os.path.join(OUTPUT_DIR, filename)
|
|
df.to_csv(filepath, index=False, encoding='utf-8-sig')
|
|
|
|
return filepath
|
|
|
|
|
|
def main():
|
|
"""Função principal."""
|
|
# Executar scrapers
|
|
df = run_scrapers()
|
|
|
|
print("\n" + "=" * 60)
|
|
|
|
if df.empty:
|
|
print("❌ Nenhuma vaga encontrada.")
|
|
return
|
|
|
|
# Estatísticas
|
|
print(f"📊 Total de vagas coletadas: {len(df)}")
|
|
print(f"📊 Fontes: {df['fonte'].value_counts().to_dict()}")
|
|
|
|
# Salvar resultados
|
|
filepath = save_results(df)
|
|
print(f"\n💾 Vagas salvas em: {filepath}")
|
|
|
|
# Também salvar versão "latest"
|
|
latest_path = save_results(df, 'vagas_consolidadas.csv')
|
|
print(f"💾 Versão atual: {latest_path}")
|
|
|
|
print("=" * 60)
|
|
print("✅ Raspagem concluída com sucesso!")
|
|
|
|
# Preview
|
|
print("\n📋 Preview das vagas:")
|
|
print(df[['titulo', 'empresa', 'fonte']].head(10).to_string(index=False))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|