chore: remove scraper/email-legacy e organiza documentação em docs/context
This commit is contained in:
parent
1a527add23
commit
a754b4eba8
17 changed files with 0 additions and 742 deletions
|
|
@ -1,7 +0,0 @@
|
|||
# Ass Email - GoHorse Jobs
|
||||
|
||||
This directory handles the email templating and dispatchers, specifically configured for notification processing and mail tasks.
|
||||
|
||||
## 🚨 AI Rules Warning 🚨
|
||||
Before making ANY changes to infrastructure deployment manifests or Kubernetes files, please refer to the project-wide AI Rules located at `.agent/rules.md`.
|
||||
**Do NOT touch `k3s` or `k8s` files.** Do not alter raw RSA/base64 authentication keys.
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Assinatura de Email - Exemplo</title>
|
||||
</head>
|
||||
|
||||
<body style="font-family: Arial, sans-serif; background-color: #f5f5f5; padding: 40px;">
|
||||
<div
|
||||
style="background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); max-width: 600px; margin: 0 auto;">
|
||||
<h2 style="color: #333; margin-bottom: 20px; font-size: 14px;">Exemplo Preenchido</h2>
|
||||
|
||||
<!-- Assinatura Exemplo -->
|
||||
<table cellpadding="0" cellspacing="0" border="0"
|
||||
style="font-family: Arial, Helvetica, sans-serif; font-size: 14px; color: #333333; line-height: 1.4;">
|
||||
<tr>
|
||||
<td style="vertical-align: top; padding-right: 20px; border-right: 3px solid #FF6B00;">
|
||||
<!-- Logo Moobz -->
|
||||
<img src="https://via.placeholder.com/120x60/FF6B00/FFFFFF?text=MOOBZ" alt="Moobz"
|
||||
style="width: 120px; height: auto; display: block;" />
|
||||
</td>
|
||||
<td style="vertical-align: top; padding-left: 20px;">
|
||||
<!-- Nome e Cargo -->
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="font-size: 18px; font-weight: bold; color: #333333; padding-bottom: 2px;">
|
||||
João Silva
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-size: 13px; color: #FF6B00; font-weight: 600; padding-bottom: 12px;">
|
||||
Desenvolvedor Full Stack
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Informações de Contato -->
|
||||
<tr>
|
||||
<td style="padding-bottom: 4px;">
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="width: 20px; vertical-align: middle;">
|
||||
📞
|
||||
</td>
|
||||
<td style="font-size: 13px; color: #555555; vertical-align: middle;">
|
||||
+55 (11) 99999-9999
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-bottom: 4px;">
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="width: 20px; vertical-align: middle;">
|
||||
📍
|
||||
</td>
|
||||
<td style="font-size: 13px; color: #555555; vertical-align: middle;">
|
||||
Av. Paulista, 1000 - São Paulo, SP
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-bottom: 4px;">
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="width: 20px; vertical-align: middle;">
|
||||
✉️
|
||||
</td>
|
||||
<td style="font-size: 13px; vertical-align: middle;">
|
||||
<a href="mailto:joao.silva@moobz.com.br"
|
||||
style="color: #FF6B00; text-decoration: none;">joao.silva@moobz.com.br</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-top: 8px;">
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="width: 20px; vertical-align: middle;">
|
||||
📸
|
||||
</td>
|
||||
<td style="font-size: 13px; vertical-align: middle;">
|
||||
<a href="https://instagram.com/moobz.oficial"
|
||||
style="color: #FF6B00; text-decoration: none;">@moobz.oficial</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Assinatura de Email - Moobz</title>
|
||||
<style>
|
||||
/* Estilos apenas para visualização no navegador - não serão aplicados no email */
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.preview-container {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h2 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.divider {
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.instructions {
|
||||
background: #f9f9f9;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-top: 20px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="preview-container">
|
||||
<h2>Prévia da Assinatura de Email</h2>
|
||||
|
||||
<!-- ========== COPIE A PARTIR DAQUI ========== -->
|
||||
<table cellpadding="0" cellspacing="0" border="0" style="font-family: Arial, Helvetica, sans-serif; font-size: 14px; color: #333333; line-height: 1.4;">
|
||||
<tr>
|
||||
<td style="vertical-align: top; padding-right: 20px; border-right: 3px solid #FF6B00;">
|
||||
<!-- Logo Moobz -->
|
||||
<img src="https://via.placeholder.com/120x60/FF6B00/FFFFFF?text=MOOBZ" alt="Moobz" style="width: 120px; height: auto; display: block;" />
|
||||
</td>
|
||||
<td style="vertical-align: top; padding-left: 20px;">
|
||||
<!-- Nome e Cargo -->
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="font-size: 18px; font-weight: bold; color: #333333; padding-bottom: 2px;">
|
||||
{{NOME_SOBRENOME}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-size: 13px; color: #FF6B00; font-weight: 600; padding-bottom: 12px;">
|
||||
{{CARGO}}
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Informações de Contato -->
|
||||
<tr>
|
||||
<td style="padding-bottom: 4px;">
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="width: 20px; vertical-align: middle;">
|
||||
<img src="https://cdn-icons-png.flaticon.com/16/724/724664.png" alt="Telefone" style="width: 14px; height: 14px;" />
|
||||
</td>
|
||||
<td style="font-size: 13px; color: #555555; vertical-align: middle;">
|
||||
{{TELEFONE}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-bottom: 4px;">
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="width: 20px; vertical-align: middle;">
|
||||
<img src="https://cdn-icons-png.flaticon.com/16/684/684809.png" alt="Endereço" style="width: 14px; height: 14px;" />
|
||||
</td>
|
||||
<td style="font-size: 13px; color: #555555; vertical-align: middle;">
|
||||
{{ENDERECO}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-bottom: 4px;">
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="width: 20px; vertical-align: middle;">
|
||||
<img src="https://cdn-icons-png.flaticon.com/16/732/732200.png" alt="Email" style="width: 14px; height: 14px;" />
|
||||
</td>
|
||||
<td style="font-size: 13px; vertical-align: middle;">
|
||||
<a href="mailto:{{EMAIL}}" style="color: #FF6B00; text-decoration: none;">{{EMAIL}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-top: 8px;">
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="width: 20px; vertical-align: middle;">
|
||||
<img src="https://cdn-icons-png.flaticon.com/16/2111/2111463.png" alt="Instagram" style="width: 14px; height: 14px;" />
|
||||
</td>
|
||||
<td style="font-size: 13px; vertical-align: middle;">
|
||||
<a href="https://instagram.com/{{INSTAGRAM}}" style="color: #FF6B00; text-decoration: none;">@{{INSTAGRAM}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- ========== COPIE ATÉ AQUI ========== -->
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="instructions">
|
||||
<strong>Como usar:</strong><br><br>
|
||||
1. Substitua os campos entre <code>{{CHAVES}}</code> pelos seus dados:<br>
|
||||
• <code>{{NOME_SOBRENOME}}</code> → Seu nome completo<br>
|
||||
• <code>{{CARGO}}</code> → Seu cargo<br>
|
||||
• <code>{{TELEFONE}}</code> → Seu telefone<br>
|
||||
• <code>{{ENDERECO}}</code> → Endereço da empresa<br>
|
||||
• <code>{{EMAIL}}</code> → Seu email<br>
|
||||
• <code>{{INSTAGRAM}}</code> → Usuário do Instagram (sem @)<br><br>
|
||||
2. Copie o HTML e cole nas configurações de assinatura do seu email.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 696 KiB |
|
|
@ -1,13 +0,0 @@
|
|||
# Python scraper .dockerignore
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
.venv/
|
||||
venv/
|
||||
.env
|
||||
.git/
|
||||
.gitignore
|
||||
README.md
|
||||
*.md
|
||||
output/*.csv
|
||||
.DS_Store
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
# =============================================================================
|
||||
# GoHorse Jobs Scraper - Environment Variables Example
|
||||
# =============================================================================
|
||||
# Copy this file to .env and update the values
|
||||
|
||||
# Output Configuration
|
||||
OUTPUT_DIR=./output
|
||||
OUTPUT_FORMAT=csv
|
||||
|
||||
# API Configuration (to send scraped data to backend)
|
||||
BACKEND_API_URL=http://localhost:8521/api/v1
|
||||
API_TOKEN=your-api-token-here
|
||||
|
||||
# Scraping Configuration
|
||||
SCRAPE_DELAY_SECONDS=2
|
||||
MAX_PAGES_PER_SITE=10
|
||||
USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=INFO
|
||||
30
job-scraper-multisite/.gitignore
vendored
30
job-scraper-multisite/.gitignore
vendored
|
|
@ -1,30 +0,0 @@
|
|||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Output (keep .gitkeep)
|
||||
output/*.csv
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Distribution
|
||||
dist/
|
||||
build/
|
||||
*.egg-info/
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
# =============================================================================
|
||||
# GoHorse Jobs Scraper - Python Dockerfile
|
||||
# =============================================================================
|
||||
|
||||
FROM mirror.gcr.io/library/python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy source
|
||||
COPY . .
|
||||
|
||||
# Security: Run as non-root
|
||||
RUN useradd -m -u 1001 scraper && \
|
||||
chown -R scraper:scraper /app
|
||||
|
||||
USER scraper
|
||||
|
||||
# Create output directory
|
||||
RUN mkdir -p /app/output
|
||||
|
||||
CMD ["python", "main_scraper.py"]
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
# 🐴 JobScraper MultiSite
|
||||
|
||||
Raspador de vagas de emprego multi-plataforma para sites de tecnologia brasileiros.
|
||||
|
||||
## 📁 Estrutura do Projeto
|
||||
|
||||
```
|
||||
JobScraper_MultiSite/
|
||||
├── main_scraper.py # Arquivo principal
|
||||
├── scrapers/ # Módulos de raspagem por site
|
||||
│ ├── __init__.py
|
||||
│ ├── programathor_scraper.py
|
||||
│ └── geekhunter_scraper.py
|
||||
├── output/ # Arquivos CSV gerados
|
||||
│ └── vagas_consolidadas.csv
|
||||
├── requirements.txt
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 🚀 Instalação
|
||||
|
||||
```bash
|
||||
# Criar ambiente virtual (recomendado)
|
||||
python -m venv venv
|
||||
source venv/bin/activate # Linux/Mac
|
||||
# ou: venv\Scripts\activate # Windows
|
||||
|
||||
# Instalar dependências
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Ou instalar manualmente:
|
||||
pip install requests beautifulsoup4 pandas
|
||||
```
|
||||
|
||||
## ▶️ Execução
|
||||
|
||||
```bash
|
||||
# Executar raspagem de todos os sites
|
||||
python main_scraper.py
|
||||
|
||||
# Testar um scraper individual
|
||||
python -m scrapers.programathor_scraper
|
||||
```
|
||||
|
||||
## 📊 Output
|
||||
|
||||
Os resultados são salvos na pasta `output/`:
|
||||
- `vagas_consolidadas.csv` - Versão mais recente
|
||||
- `vagas_consolidadas_YYYYMMDD_HHMMSS.csv` - Versões com timestamp
|
||||
|
||||
### Campos extraídos:
|
||||
| Campo | Descrição |
|
||||
|-------------|------------------------------|
|
||||
| titulo | Título da vaga |
|
||||
| empresa | Nome da empresa |
|
||||
| localizacao | Localização/Modalidade |
|
||||
| link | URL da vaga |
|
||||
| fonte | Site de origem |
|
||||
|
||||
## ➕ Adicionando Novos Sites
|
||||
|
||||
1. Crie um novo arquivo em `scrapers/` (ex: `novosite_scraper.py`)
|
||||
2. Implemente a função `scrape_novosite()` seguindo o padrão existente
|
||||
3. Adicione ao dicionário `SITES` em `main_scraper.py`:
|
||||
|
||||
```python
|
||||
from scrapers.novosite_scraper import scrape_novosite
|
||||
|
||||
SITES = {
|
||||
'programathor': scrape_programathor,
|
||||
'geekhunter': scrape_geekhunter,
|
||||
'novosite': scrape_novosite, # Novo!
|
||||
}
|
||||
```
|
||||
|
||||
## ⚠️ Boas Práticas Anti-Bloqueio
|
||||
|
||||
- ✅ Sempre use `time.sleep()` entre requisições (mínimo 2s)
|
||||
- ✅ Use headers que simulem um navegador real
|
||||
- ✅ Não faça muitas requisições em sequência rápida
|
||||
- ✅ Respeite o `robots.txt` de cada site
|
||||
- ✅ Considere usar proxies para grandes volumes
|
||||
|
||||
## 📝 Licença
|
||||
|
||||
Uso educacional. Respeite os Termos de Serviço de cada site.
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
#!/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()
|
||||
|
|
@ -1 +0,0 @@
|
|||
# Keep this folder in git
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
requests>=2.28.0
|
||||
beautifulsoup4>=4.11.0
|
||||
pandas>=1.5.0
|
||||
lxml>=4.9.0
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# Scrapers package
|
||||
# Each site has its own scraper module
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
"""
|
||||
Scraper para GeekHunter - https://www.geekhunter.com.br/vagas
|
||||
"""
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import pandas as pd
|
||||
import time
|
||||
|
||||
# Headers para simular navegador e evitar bloqueios
|
||||
HEADERS = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Accept-Language': 'pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||
}
|
||||
|
||||
def scrape_geekhunter(delay: float = 2.0) -> pd.DataFrame:
|
||||
"""
|
||||
Raspa vagas do site GeekHunter.
|
||||
|
||||
Args:
|
||||
delay: Tempo de espera antes da requisição (anti-bloqueio)
|
||||
|
||||
Returns:
|
||||
DataFrame com colunas: titulo, empresa, localizacao, link
|
||||
"""
|
||||
url = "https://www.geekhunter.com.br/vagas"
|
||||
vagas = []
|
||||
|
||||
try:
|
||||
# Delay anti-bloqueio
|
||||
time.sleep(delay)
|
||||
|
||||
print(f"🔍 Raspando vagas de: {url}")
|
||||
response = requests.get(url, headers=HEADERS, timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
|
||||
# Encontrar cards de vagas (ajustar seletores conforme estrutura do site)
|
||||
job_cards = soup.select('.job-card') or soup.select('[class*="job"]') or soup.select('article')
|
||||
|
||||
for card in job_cards:
|
||||
try:
|
||||
# Extrair título
|
||||
titulo_elem = card.select_one('h2') or card.select_one('h3') or card.select_one('.title')
|
||||
titulo = titulo_elem.get_text(strip=True) if titulo_elem else "N/A"
|
||||
|
||||
# Extrair empresa
|
||||
empresa_elem = card.select_one('.company') or card.select_one('[class*="company"]')
|
||||
empresa = empresa_elem.get_text(strip=True) if empresa_elem else "N/A"
|
||||
|
||||
# Extrair localização
|
||||
loc_elem = card.select_one('.location') or card.select_one('[class*="location"]')
|
||||
localizacao = loc_elem.get_text(strip=True) if loc_elem else "Remoto"
|
||||
|
||||
# Extrair link
|
||||
link_elem = card.select_one('a[href*="/vagas/"]') or card.select_one('a')
|
||||
if link_elem:
|
||||
href = link_elem.get('href', '')
|
||||
link = f"https://www.geekhunter.com.br{href}" if href.startswith('/') else href
|
||||
else:
|
||||
link = url
|
||||
|
||||
vagas.append({
|
||||
'titulo': titulo,
|
||||
'empresa': empresa,
|
||||
'localizacao': localizacao,
|
||||
'link': link,
|
||||
'fonte': 'GeekHunter'
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"⚠️ Erro ao processar card: {e}")
|
||||
continue
|
||||
|
||||
print(f"✅ {len(vagas)} vagas encontradas no GeekHunter")
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"❌ Erro na requisição ao GeekHunter: {e}")
|
||||
except Exception as e:
|
||||
print(f"❌ Erro inesperado no GeekHunter: {e}")
|
||||
|
||||
return pd.DataFrame(vagas)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Teste individual do scraper
|
||||
df = scrape_geekhunter()
|
||||
print(df.head())
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
"""
|
||||
Scraper para ProgramaThor - https://programathor.com.br/jobs
|
||||
"""
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import pandas as pd
|
||||
import time
|
||||
|
||||
# Headers para simular navegador e evitar bloqueios
|
||||
HEADERS = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||
'Accept-Language': 'pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||
}
|
||||
|
||||
def scrape_programathor(delay: float = 2.0) -> pd.DataFrame:
|
||||
"""
|
||||
Raspa vagas do site ProgramaThor.
|
||||
|
||||
Args:
|
||||
delay: Tempo de espera antes da requisição (anti-bloqueio)
|
||||
|
||||
Returns:
|
||||
DataFrame com colunas: titulo, empresa, localizacao, link
|
||||
"""
|
||||
url = "https://programathor.com.br/jobs"
|
||||
vagas = []
|
||||
|
||||
try:
|
||||
# Delay anti-bloqueio
|
||||
time.sleep(delay)
|
||||
|
||||
print(f"🔍 Raspando vagas de: {url}")
|
||||
response = requests.get(url, headers=HEADERS, timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
|
||||
# Encontrar cards de vagas (ajustar seletores conforme estrutura do site)
|
||||
job_cards = soup.select('.cell-list')
|
||||
|
||||
for card in job_cards:
|
||||
try:
|
||||
# Extrair título
|
||||
titulo_elem = card.select_one('h3') or card.select_one('.title')
|
||||
titulo = titulo_elem.get_text(strip=True) if titulo_elem else "N/A"
|
||||
|
||||
# Extrair empresa
|
||||
empresa_elem = card.select_one('.company-name') or card.select_one('h4')
|
||||
empresa = empresa_elem.get_text(strip=True) if empresa_elem else "N/A"
|
||||
|
||||
# Extrair localização
|
||||
loc_elem = card.select_one('.location') or card.select_one('.info')
|
||||
localizacao = loc_elem.get_text(strip=True) if loc_elem else "Remoto"
|
||||
|
||||
# Extrair link
|
||||
link_elem = card.select_one('a[href*="/jobs/"]')
|
||||
if link_elem:
|
||||
href = link_elem.get('href', '')
|
||||
link = f"https://programathor.com.br{href}" if href.startswith('/') else href
|
||||
else:
|
||||
link = url
|
||||
|
||||
vagas.append({
|
||||
'titulo': titulo,
|
||||
'empresa': empresa,
|
||||
'localizacao': localizacao,
|
||||
'link': link,
|
||||
'fonte': 'ProgramaThor'
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"⚠️ Erro ao processar card: {e}")
|
||||
continue
|
||||
|
||||
print(f"✅ {len(vagas)} vagas encontradas no ProgramaThor")
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"❌ Erro na requisição ao ProgramaThor: {e}")
|
||||
except Exception as e:
|
||||
print(f"❌ Erro inesperado no ProgramaThor: {e}")
|
||||
|
||||
return pd.DataFrame(vagas)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Teste individual do scraper
|
||||
df = scrape_programathor()
|
||||
print(df.head())
|
||||
Loading…
Reference in a new issue