Como criei um agregador de vagas na gringa pra brasileiros
Como 3 sistemas de ATS me permitiram automatizar 39 empresas de uma vez e o que aprendi sobre web scraping na prática
Na nossa comunidade de assinantes pagos, temos uma lista de 72 empresas que contratam brasileiros numa planilha. O problema: só os links dos sites de vaga.
Pra ver oportunidades, você precisava clicar empresa por empresa. Anotar vagas interessantes. Voltar pra planilha. Repetir.
Semana passada, não tivemos um artigo. Porque eu inventei de tentar automatizar o processo.
Hoje temos 348 vagas atualizadas diariamente, todas filtradas pra brasileiros. Acesse em nagringa.dev/vagas - não-assinantes veem 3 vagas, assinantes têm acesso completo.
Minha motivação era tripla: aprender web scraping, dar mais benefícios aos assinantes, e resolver uma das dúvidas que mais ouço - onde procurar vagas.
✨ O que esperar do artigo
Como automatizar coleta de dados de múltiplas fontes usando padrões em comum
Quando data scraping é a solução certa - spoiler: mais vezes do que você imagina
Como evoluir de script pessoal para produto que roda sozinho todo dia
O problema e por que resolvi automatizar
O problema era claro: checagem manual da planilha levava 1-2 horas. Vi membros da comunidade comentando sobre isso toda semana.
A oportunidade técnica apareceu quando descobri que a maioria das empresas de tech usa apenas 3 ATS principais:
Greenhouse - Brex, Stripe, Coinbase
Ashby - PostHog, Supabase, Deel
Lever - Spotify, WorkOS, Metabase
APIs não eram opção - não tenho vínculo com essas empresas. Mas scraping de dados públicos? Perfeitamente viável.
Em vez de scrapers customizados pra cada empresa, podia focar nesses 3 sistemas e cobrir quase metade das empresas de uma vez.
Três motivações me convenceram:
Aprender web scraping - skill útil que nunca tinha praticado
Dar benefício real aos assinantes - resolver problema que vejo acontecer
Resolver dúvida comum - "onde procurar vagas" é pergunta que ouço sempre
Data scraping virou a solução porque:
Dados estão públicos
Existe padrão claro nos 3 ATS
Resolve problema real da comunidade
APIs não existem pra esse caso
Resultado atual: 39 empresas automatizadas das 72 na planilha original.
Da primeira versão ao sistema automatizado
V1: O script simples
Comecei testando 3 empresas: Stripe (Greenhouse), PostHog (Ashby), Spotify (Lever).
async function scrapeCompany(company: CompanyConfig): Promise<JobListing[]> {
const html = await fetchHTML(company.url);
let jobs: JobListing[] = [];
switch (company.type) {
case 'greenhouse': jobs = scrapeGreenhouse(html, company.name); break;
case 'ashby': jobs = scrapeAshby(html, company.name); break;
case 'lever': jobs = scrapeLever(html, company.name); break;
}
return jobs;
}
Em mais ou menos meia hora, já tinha um protótipo funcionando, graças ao Cursor. Via todas as vagas das 3 empresas estruturadas. Foi quando pensei: "agora preciso ter isso num banco de dados em algum lugar".
O script completo da primeira versão está aqui.
Os desafios reais
Cada ATS tem estrutura diferente:
Greenhouse:
.opening
Ashby:
[data-testid="job-posting"]
Lever:
.posting
Como detectar vagas "Brazilian-friendly"? "Remote" pode ser global ou só América do Norte ou Europa.
Como manter atualizado? Rodar manualmente sempre que lembrava não escalava.
V2: Sistema de produção
Escolhi arquitetura funcional - cada scraper é função pura que recebe HTML e retorna jobs.
const SCRAPER_REGISTRY = {
greenhouse: scrapeGreenhouse,
ashby: scrapeAshby,
lever: scrapeLever,
};
const filterResult = isBrazilianFriendlyJob(job);
if (filterResult.isBrazilianFriendly) {
// Salva no banco
}
Principais evoluções:
Sistema de filtros inteligentes
Mapeamento de departamentos padronizado
Persistência com comparação de mudanças
Cron jobs a cada 24h
Rate limiting respeitoso
Falha no scraping que não quebra todo sistema
Esses filtros foram criados ao perceber algumas palavras chave em comum que existiam em todas as vagas.
Sugestão de imagem: Diagrama em Mermaid mostrando o fluxo do scraper - Input (Company URLs) → Scraper Registry → Filter System → Database → Portal de Vagas
Como funciona o filtro "Brazilian-friendly"
Core técnico mais importante do projeto. Nem toda vaga "remote" aceita brasileiros.
Lógica de inclusão
✅ Incluem automaticamente:
Cidades brasileiras: São Paulo, Rio, BH
Keywords globais: "distributed", "work from home", "anywhere"
Timezone compatibility: GMT-3, America/Sao_Paulo
Regiões amplas: "Americas", "LATAM"
❌ Excluem automaticamente:
Restrições: "US only", "EU only", "visa sponsorship not available"
US remote restrito: "remote (us)" sem timezone mention
Onsite internacional sem opção remota
🎯 Casos especiais:
Empresas que contratam globalmente - PostHog, 37signals, GitLab
Qualquer timezone range que cubra GMT-3
export function isBrazilianFriendlyJob(job: JobListing): FilterResult {
if (hasBrazilianCity(job.location)) {
return { isBrazilianFriendly: true, reasons: ['Brazilian city'] };
}
if (hasRemoteKeywords(job.location) && !hasUSOnlyRestrictions(job.location)) {
return { isBrazilianFriendly: true, reasons: ['Global remote'] };
}
return { isBrazilianFriendly: false, reasons: ['No match'] };
}
Por que funciona: Precisão alta. Candidatos aplicam só pras vagas que realmente os querem.
Eu me lembro, quando estava procurando por vagas, que isso era uma das minhas maiores frustrações.
Remoto? Sim. Mas só se você tiver work authorization nos EUA.
Aí não dá.
Dessa frustração nasceu esse filtro.
Lições técnicas do mundo real
1. Arquitetura funcional foi acerto
Funções puras são melhores que classes pra scraping:
Testável: cada função isolada
Debuggável: fácil rastrear erros
Extensível: novo ATS = nova função
2. Rate limiting é obrigatório
await new Promise(resolve => setTimeout(resolve, 2000));
Delay de 2 segundos entre requests. Headers realistas. Respeitar robots.txt dos job boards sempre que existem.
3. Error handling básico mas funcional
try {
const jobs = await scrapeCompany(company);
} catch (error) {
console.error(`Failed: ${company.name}`, error);
// Continua próxima empresa
}
Não é sofisticado, mas funciona. Falha numa empresa não quebra sistema todo. Consigo fazer a observabilidade dos logs via Axiom, pois tenho um alerta toda vez que o scrape falha.
4. Debugging é metade do trabalho
Sites mudam sem aviso. Adicionei uma flag de debug
nas minhas chamadas. Quando essa flag está ativa, mostro:
Quais seletores encontraram elementos
Por que jobs foram incluídos/excluídos
Detalhes de cada request HTTP
5. O que realmente aprendi
Inconsistência como regra: Sites mudam terça-feira qualquer. Greenhouse troca .opening
pra .job-posting
sem avisar.
Observabilidade necessária: Como saber se 39 scrapers funcionam? Métricas simples - vagas por empresa, diff com execução anterior.
Race conditions sutis: Scraping concorrente parece óbvio até site detectar múltiplas requests do mesmo IP e bloquear.
Data normalization é difícil: "Software Engineer II" vs "SWE 2" - mesma vaga, como normalizar? Mapeamentos manuais que evoluem. O número de alterações que eu fui fazendo aqui até funcionar não é brincadeira.
Debugging de caixa preta: Scraper para de funcionar. Pode ser HTML que mudou, rate limit, geo-block, mil motivos. Desenvolvi intuição pra diagnosticar rápido. E incluo todas essas informações nos logs de falha.
De side project para produto
Decisão rápida
Assim que MVP funcionou, virou produto. Se consegue automatizar 39 empresas, vira feature premium.
Escolhas de produto
3 vagas grátis vs completo pra assinantes: Mostra um pouco do valor + incentivo pra assinar.
Atualização diária: Sistema roda 6h da manhã. Atualiza banco. Remove vagas preenchidas. Adiciona novas.
Interface simples: Lista de vagas, filtros por departamento, links diretos.
Status atual
39 empresas automatizadas de 72 total
348 vagas hoje, dia 18 de junho
Sistema estável há uma semana
Planilha ainda existe pra outros ATS
Sugestão de imagem: Screenshot da interface do portal mostrando os filtros por departamento e a lista de vagas
Como aplicar na sua carreira
1. Identifique problemas da comunidade
Melhores side projects resolvem problemas que você vê. Não precisa ser seu problema pessoal, mas ajuda se você também for usuário.
2. Comece simples
Script de 200 linhas > projeto perfeito que nunca sai. Primeira versão: 3 empresas hardcoded. Funcionou, mostrou valor, e a partir daí, fui adicionando novas empresas.
Isso não vale só pra side projects. Mas também pro seu trabalho.
Se você tem uma ideia de como resolver algo, faça. Não peça permissão. Monte uma PoC. Mostre pra sua equipe, consiga que invistam na sua ideia. Venha com soluções, e não problemas.
3. Documente o processo
Vira conteúdo e expertise. Este artigo existe porque documentei a jornada.
4. Pense em produto cedo
Se resolve problema real, pode virar produto. Automações simples já têm valor suficiente, dependendo do seu produto.
5. Seja consistente
Sistema simples que funciona todo dia > complexo que quebra. 39 empresas perfeitas > 100 falhando.
🌟 Resumo
Data scraping resolve o que APIs não conseguem - agregar dados públicos de múltiplas fontes
Foque em padrões, não casos específicos - 3 ATS cobrem dezenas de empresas
Comece simples e itere - MVP funcional > sistema perfeito imaginário
Resolva problemas da sua comunidade - provavelmente ajuda outras pessoas também
Automação simples pode virar produto real - 39 empresas automatizadas geram valor
Resultado: 348 vagas atualizadas diariamente, filtradas pra brasileiros trabalharem remotamente.
Acesse nagringa.dev/vagas pra ver funcionando.
Se quiser acessar o script da primeira versão, disponibilizei ele num gist.
Obrigado por ter lido até o final! 🙏
Você também pode clicar no botão de curtir ❤️ e compartilhar este artigo com outras pessoas que se interessem. Me ajuda muito!
Perfeito 👌! Parabéns 🎊