Este documento descreve o scraper implementado em Go localizado em scraper-go/.
O scraper Go concentra a coleta e normalização de vagas. Ele recebe configurações de busca do backend, executa fontes externas habilitadas, aplica deduplicação e classificação local, persiste vagas no Valkey e publica índices para a API consultar com baixa latência.
Abaixo há exemplos simplificados do payload esperado internamente e de como cada adaptador normalmente formata/retorna vagas.
- LinkedIn
- Comportamento: consulta o endpoint público
jobs-gueste faz parsing HTML comgoquery. - Exemplo (Job individual retornado pelo adaptador):
- Comportamento: consulta o endpoint público
{
"id": "24a1b2c3d4e5f6a7b8c9d0e1",
"title": "Frontend Engineer",
"company": "Empresa X",
"location": "São Paulo, SP",
"url": "https://www.linkedin.com/jobs/view/123456789/",
"source": "LinkedIn",
"sources": ["LinkedIn"],
"keyword": "react",
"keywords": ["react"]
}- Adzuna
- Comportamento: usa API oficial (quando
ADZUNA_APP_ID/ADZUNA_APP_KEYconfigurados) e retorna JSON com campos estruturados. - Operação: suporta
SearchBatchcom slot rotativo de keywords e limite padrão de 5 páginas por keyword. - Exemplo (Job adaptado):
- Comportamento: usa API oficial (quando
{
"id": "adzuna-987654",
"title": "Desenvolvedor Backend",
"company": "ACME",
"location": "Remoto",
"url": "https://www.adzuna.com/descricao/987654",
"source": "Adzuna",
"sources": ["Adzuna"],
"keyword": "node",
"keywords": ["node"]
}- Jooble
- Comportamento: integra com Jooble API quando
JOOBLE_API_KEYpresente; pode usar Redis para controle de cota. - Operação: usa slot rotativo, cota diária e cadência de 12h para proteger a integração.
- Exemplo (Job adaptado):
- Comportamento: integra com Jooble API quando
{
"id": "jooble-13579",
"title": "Data Engineer",
"company": "Empresa Y",
"location": "Curitiba, PR",
"url": "https://jooble.org/vaga/13579",
"source": "Jooble",
"sources": ["Jooble"],
"keyword": "data",
"keywords": ["data"]
}- Greenhouse / Lever / TheMuse
- Comportamento: adaptadores per-company que fazem scraping/parsing da vaga ou consomem endpoints públicos por empresa.
- Exemplo (job adaptado genérico):
{
"id": "greenhouse-abc123",
"title": "Product Manager",
"company": "Startup Z",
"location": "Los Angeles, CA",
"url": "https://boards.greenhouse.io/companyz/jobs/321",
"source": "Greenhouse",
"sources": ["Greenhouse"],
"keyword": "product",
"keywords": ["product"]
}O board_token é o trecho final da URL pública da Greenhouse. Por exemplo:
https://job-boards.greenhouse.io/reddit→ tokenreddithttps://job-boards.greenhouse.io/gitlab→ tokengitlab
Para validar tokens ou testar nomes de empresas, use:
cd scraper-go
go run ./cmd/greenhouse-discover -names Reddit,GitLabSaída esperada:
OK gitlab 186 vagas https://job-boards.greenhouse.io/gitlab
OK reddit 195 vagas https://job-boards.greenhouse.io/reddit
Também é possível validar o JSON atual:
cd scraper-go
go run ./cmd/greenhouse-discover -file internal/interfaces/greenhouseCompanies.jsonTokens com OK podem entrar em internal/interfaces/greenhouseCompanies.json.
Tokens com MISS status=404 devem ser removidos ou substituídos.
Esses exemplos ilustram o contrato interno entre adaptadores e pipeline: o pipeline espera domain.Job com campos normalizados (URL limpa, título/empresa/local, Source e StableID calculável). O jobstore.StableID deriva o ID a partir de título+empresa+local ou URL.
Criei uma especificação OpenAPI mínima em openapi.yaml descrevendo os endpoints HTTP públicos do serviço Go (/scrape, /api/keywords). Ela pode ser usada para gerar clientes ou documentação interativa.
Arquivo: scraper-go/openapi.yaml (no repositório)
O scraper é um serviço HTTP em Go que consulta múltiplas fontes de vagas (LinkedIn, Adzuna, Greenhouse, TheMuse, Lever, Jooble, etc.), agrega os resultados, remove duplicatas e persiste/retorna as vagas via cache e índice Redis/Valkey. Ele foi projetado para ser usado internamente pelo backend Node.js, que delega buscas ao serviço Go.
Componentes principais:
cmd/server— inicializador e ponto de entrada.internal/domain— modelos centrais do scraper (Job,ScrapeRequest,ScrapeResponse, classificação).internal/ports— contratos internos da aplicação, como fontes de vagas, repositórios, cache e métricas.internal/adapters— composição de adapters concretos e registry das fontes habilitadas.internal/adapters/<fonte>— implementação isolada de cada fonte externa (gupy,inhire,greenhouse,lever,jooble,linkedin,themuse,adzuna).internal/adapters/adapterutil— helpers compartilhados entre adapters concretos.internal/pipeline— camada de aplicação do pipeline de scraping, orquestra execução concorrente e indexação.internal/jobstore— persistência em Redis (valkey) para jobs, índices e IDs estáveis.internal/cache— abstração de cache com implementação Redis e memória (fallback).internal/dedup— regras para deduplicação/merge de vagas.internal/keywords— carregamento e persistência de keywords (configuração).internal/inflight— deduplicador de requisições concorrentes (singleflight).internal/cronjob— scheduler de scraping em background e execução manual.internal/metrics— métricas Prometheus por fonte/execução.cmd/greenhouse-discover— utilitário Go para validar tokens de empresas Greenhouse antes de atualizarinternal/interfaces/greenhouseCompanies.json.
O scraper evolui para uma arquitetura hexagonal de forma incremental. O domínio fica em internal/domain, as portas em internal/ports e os adapters concretos ficam em subpastas de internal/adapters.
A fronteira principal é a porta ports.JobSource: cada fonte externa implementa SourceName, Search e, opcionalmente, SearchBatch. O pipeline recebe uma lista de ports.JobSource já montada pelo servidor/registry, evitando que a camada de aplicação conheça diretamente as implementações concretas.
Desenho atual:
cmd/server
monta cache, Valkey, stores, scheduler e adapters
internal/domain
modelos centrais do scraper
internal/ports
contratos que a aplicação consome
internal/pipeline
orquestra scraping, dedupe, classificação e indexação
internal/adapters
registry e implementações concretas por fonte
Ainda há pontos a evoluir: pipeline e cronjob continuam recebendo *redis.Client em fluxos de indexação/cadência, e métricas Prometheus ainda são chamadas diretamente. Os próximos passos naturais são criar adapters outbound para Valkey e Prometheus por trás de portas específicas, reduzindo ainda mais o acoplamento de infraestrutura.
Requisitos: Go >= 1.26, Redis (opcional, mas recomendado)
Construir e executar:
cd scraper-go
go build ./cmd/server
./serverRodar em desenvolvimento (carregando .env):
cd scraper-go
go run ./cmd/serverDocker: há um Dockerfile em scraper-go/. No Docker Compose, configure VALKEY_URL=redis://valkey:6379/0 no .env da raiz para que o scraper acesse o Valkey pelo nome do serviço na rede Docker.
No Compose da raiz, a porta 8081 fica exposta apenas na rede interna vagas-net; ela não é publicada no host. O backend acessa o serviço por http://scraper-go:8081. Para testes locais diretos, execute o binário fora do Compose ou use um override de desenvolvimento que publique a porta somente em interface confiável.
O scraper possui um orçamento global e limites por provider controlados pelo scheduler do pipeline:
SCRAPER_MAX_CONCURRENCY: teto global. Padrão interno12.SCRAPER_PROVIDER_MAX_CONCURRENCY: limite padrão por provider. Padrão interno2.SCRAPER_PROVIDER_CONCURRENCY_OVERRIDES: overrides separados por vírgula no formatoprovider=limite, por exemplolinkedin=1,gupy=3.- IDs aceitos nos overrides:
linkedin,adzuna,themuse,gupy,inhire,jooble,greenhouseelever. - Limites devem ser inteiros positivos e não podem superar o teto global. Provider desconhecido, entrada malformada ou duplicada impede o startup.
- Valores inválidos explícitos nas variáveis numéricas (
"",0, negativo ou não numérico) fazem a aplicação falhar no startup, sem fallback silencioso; overrides vazios significam que nenhum provider foi sobrescrito. - Cron e
POST /admin/scrapeusam o limite global configurado. POST /scrapepreserva o contrato atual: quandomaxConcurrencynão é informado, ou vem como0/negativo, usa o limite global; quando vem positivo abaixo do teto, usa o valor solicitado; quando vem acima do teto, usa o teto global.- A concorrência global efetiva continua fazendo parte da chave de cache. Limites por provider são parâmetros operacionais e não alteram a chave.
- Cada tarefa adquire primeiro o permit do provider e depois o permit global; o limite efetivo do provider é sempre o menor entre seu limite configurado e o global da requisição.
- O lock distribuído abaixo impede que duas execuções mantenham orçamentos independentes ao mesmo tempo.
Todas as origens que iniciam adapters compartilham o lock scraper:run:lock no Valkey:
- cron (
source=cron); - disparo administrativo (
source=admin_manual); - cache miss de
POST /scrape(source=public_endpoint).
Cache hits de POST /scrape não executam adapters e, por isso, não adquirem o lock. A aquisição usa SET ... NX PX com token aleatório por execução. Renovação e liberação usam scripts Lua que comparam o token; não existe DEL incondicional. O estado informativo fica no hash scraper:run:state, com runId, source, startedAt e lockExpiresAt, e possui o mesmo TTL do lock.
Configuração:
SCRAPER_RUN_LOCK_TTL: padrão120s;SCRAPER_RUN_LOCK_RENEW_INTERVAL: padrão30s, obrigatoriamente positivo e menor que o TTL.
No Compose, ambas usam substituição ${VAR-default} (não :-), no mesmo padrão fail-fast de SCRAPER_MAX_CONCURRENCY: variável ausente aplica o default; valor explícito vazio ou inválido chega ao parser Go e impede a inicialização.
O mecanismo é fail-closed: se o Valkey não confirmar a aquisição, nenhum adapter é iniciado. Erros temporários de renovação são tolerados até a margem segura; perda confirmada do token ou ausência de confirmação antes dessa margem cancela o contexto da execução. A liberação ocorre no encerramento e só remove chaves pertencentes ao token atual; em crash abrupto, o TTL é a proteção final. No graceful shutdown, o scheduler deixa de aceitar novos disparos e aguarda as execuções ativas liberarem o lock antes do processo encerrar.
O token proprietário nunca é gravado no estado operacional nem nos logs. Um runId independente identifica a execução para observabilidade sem expor a credencial usada pelos scripts de renovação e liberação.
Depois da coleta, o pipeline processa vagas em etapas com filas limitadas:
coleta → normalização → deduplicação → classificação → persistência → indexação.
O catálogo de vagas coletadas vive no Valkey. Os documentos scraper:job:{id} são a fonte da verdade da execução; o PostgreSQL do backend permanece para usuários e saved_jobs. A indexação invertida (scraper:jobs:keyword:* e chaves estruturadas) só recebe IDs confirmados por SaveBatch.
Persistência: um MULTI/EXEC por lote, com upsert. Em conflito pelo ID estável, atualizam-se descrição, URL, salário, datas, modalidade, localização, classificação, fontes e keywords; o ID não muda e campos vazios na nova coleta não apagam dados já persistidos. Falha no lote impede a indexação daquele lote. Retry limitado (3 tentativas) vale só para erros transitórios.
Indexação: cada lote grava em chaves isoladas :next (ou :next:{runId}) em pipelines de SCRAPER_INDEX_BATCH_SIZE vagas. Os lotes da mesma execução se acumulam nessas chaves; ao concluir com sucesso, um RENAME publica atomicamente os conjuntos finais. Assim os índices invertidos representam o estado atual da execução, sem apagar lotes já processados no meio da corrida e sem deixar associações obsoletas nas chaves reconstruídas. Se a indexação falhar após o persist, ReindexPersistedJobs relê os documentos persistidos e reconstrói o lote em :next, sem repetir a coleta externa. Falha da execução descarta as chaves :next e mantém os índices vivos da corrida anterior.
Deduplicação usa as chaves já existentes no domínio (identificador externo, title|company|location e URL canônica) em um mapa de chaves da execução. A janela pendente guarda vagas completas só até o lote de classificação; o restante da execução guarda o ID persistido por chave. Duplicatas tardias após o flush são mescladas com o documento já persistido (URL, descrição, fontes e keywords) e voltam a ser gravadas e indexadas.
A capacidade da fila entre coleta e processamento é 2 * max(lotes), sem variável de ambiente extra.
No Docker Compose de produção, o serviço scraper-go também define:
GOMAXPROCS=2: limita a quantidade de threads do Go executando código simultaneamente.GOMEMLIMIT=1500MiB: define uma meta de memória para o runtime e influencia o garbage collector.mem_limit: 2g: limite externo do container.GOMEMLIMITnão substitui esse limite; ele fica abaixo de2gpara preservar margem operacional.cpus: 1.5: limita o container a 1,5 CPU.
O serviço expõe endpoints HTTP (implementação em cmd/server e arquivos associados). Principais rotas:
- POST
/scrape— body JSON comScrapeRequestpara disparar uma busca em todas as fontes configuradas. RetornaScrapeResponsecomjobs,total,cachedAtefromCache. - GET
/health— verifica se o scraper está online e qual cache está em uso. - GET
/metrics— métricas Prometheus. - GET
/api/keywords— retorna as keywords atualmente carregadas. - POST
/api/keywords— atualiza/persiste as keywords (aceitakeywords: string[]). - POST
/admin/scrape— dispara uma execução manual em background; retorna409comSCRAPER_ALREADY_RUNNINGse já houver execução e503comSCRAPER_RUN_LOCK_UNAVAILABLEse o Valkey não confirmar a aquisição. - GET
/admin/scrape/status— informa se existe uma execução em andamento. - GET
/admin/jobs/count— retorna a quantidade de vagas persistidas no Valkey. - GET
/admin/jobs— lista uma amostra das vagas persistidas no Valkey; aceitalimit.
Exemplo de ScrapeRequest (JSON):
{
"keywords": ["react", "node"],
"searchLocation": "Brasil",
"searchGeoId": "106057199",
"searchLanguage": "pt",
"jobTypes": "C,F",
"timeFilter": "r604800",
"remoteOnly": true,
"resultsPerPage": 25,
"maxPagesPerKeyword": 5,
"waitBetweenSearchesMs": 3000,
"pageTimeoutMs": 15000,
"maxConcurrency": 50
}Exemplo de ScrapeResponse (JSON):
{
"jobs": [
{
"id": "...",
"title": "Frontend Engineer",
"company": "Empresa X",
"location": "São Paulo, SP",
"url": "https://...",
"source": "LinkedIn",
"sources": ["LinkedIn"],
"keyword": "react",
"keywords": ["react"]
}
],
"total": 1,
"cachedAt": "2026-05-26T10:00:00Z",
"fromCache": false
}Observação: os endpoints acima refletem a implementação atual em
scraper-go/cmd/server.
Fluxo principal:
- Recebe
ScrapeRequestcom keywords e configuração. - Verifica cache (
internal/cache). Se encontrado, retorna resultado cacheado. - Caso contrário, executa
pipeline.ScrapeAllSourcesque:- Recebe a lista de fontes já montada pelo servidor/registry.
- Valida o ID, o modo de descoberta e a interface declarada por cada fonte antes de iniciar workers.
- Produz tarefas sob demanda em round-robin para uma fila limitada, consumida por um conjunto fixo de workers.
- Cria uma tarefa por keyword no modo
keyword, uma tarefa com o conjunto controlado no modobatche uma tarefa por catálogo/instância no modocatalog. - Cada adaptador realiza requisições HTTP específicas, parseia HTML/JSON quando necessário e retorna
domain.Job. - Agrega resultados e aplica deduplicação (
dedup.DedupeJobs). - Classifica vagas por família, tecnologias e senioridade antes da indexação.
- Persiste vagas novas no
jobstore(Redis) e atualiza índices invertidos (funçãoIndexJobsInValkey). - Escreve resultado no cache para próximas requisições.
Concorrência e resiliência:
- Orçamento global e por provider aplicado pelo pipeline; adapters não criam fan-out concorrente independente.
- Fila de tarefas limitada a duas vezes o teto global e workers fixos evitam materializar
adapters × keywordsou abrir uma goroutine por tarefa. - O produtor round-robin evita que um provider com muitas instâncias monopolize a fila.
- Cancelamento interrompe produção, espera por permits, paginação, retries e requisições HTTP; tarefas novas não começam após a perda do contexto/lock.
- Tratamento de status 429 com backoff; aborta apenas a keyword afetada em caso de falhas persistentes.
- Uso de
inflightpara evitar que múltiplas requisições idênticas disparem scrapes simultâneos. - Slots rotativos reduzem o número de keywords/queries por rodada em fontes caras, preservando cobertura progressiva em execuções futuras.
Cada adaptador em internal/adapters/<fonte> implementa ports.JobSource e declara ProviderID e DiscoveryMode. Fontes batch implementam ports.BatchJobSource; fontes catalog implementam ports.CatalogJobSource. Fontes legadas sem capacidade explícita usam o fallback keyword.
Implementações incluem:
internal/adapters/linkedin— busca via endpoint públicojobs-guestdo LinkedIn; parsing comgoquery.internal/adapters/adzuna— integra via API Adzuna (se configurada comADZUNA_APP_ID/ADZUNA_APP_KEY).internal/adapters/jooble— integra com Jooble API (seJOOBLE_API_KEYconfigurada); pode usar Redis para quota.internal/adapters/greenhouse,internal/adapters/lever,internal/adapters/themuse— adaptadores por empresa/plataforma com parsing/integração próprios.internal/adapters/gupy,internal/adapters/inhire— adaptadores para fontes usadas no fluxo atual de vagas.
- LinkedIn: sempre habilitado;
SearchBatchseleciona um slot rotativo de keywords por execução. Defaults atuais: 5 páginas por keyword e 30 keywords por rodada. - Adzuna: habilitado quando
ADZUNA_APP_IDeADZUNA_APP_KEYexistem;SearchBatchusa slot rotativo. Defaults atuais: 5 páginas por keyword e 30 keywords por rodada. - Gupy: habilitado com
GUPY_ENABLED=true; usa queries expandidas, descoberta por termos amplos e sweep opcional. O default atual limita o sweep a offset 10000 e processa 60 queries por rodada. - Jooble: habilitado com
JOOBLE_API_KEY; usa cota diária, slot rotativo e cadência de 12h. - Greenhouse: habilitado com
GREENHOUSE_ENABLED=true; cria um catálogo por empresa listada eminternal/interfaces/greenhouseCompanies.json, consulta cada catálogo uma vez e agrega todas as keywords correspondentes sem duplicar a vaga. - Lever: habilitado com
LEVER_ENABLED=true; cria catálogos por empresa a partir deinternal/interfaces/leverCompanies.json. - The Muse: consulta o catálogo uma vez por execução e filtra todas as keywords localmente.
- InHire: habilitado com
INHIRE_ENABLED=true; consulta tenants serialmente e só enriquece detalhes quandoINHIRE_ENRICH_DETAILS=true.
Boas práticas nos adaptadores:
- Normalização de campos (URL, título, empresa, localização).
- Derivação de
IDestável viajobstore.StableID(título+empresa+local ou URL normalizada). - Dedupe local antes de retornar ao pipeline.
jobstore.SaveBatchpersiste vagas no Redis com TTL e mantém um índice global (scraper:jobs:index).pipeline.IndexJobsInValkeycria índices invertidos por keyword e sub-termos (scraper:jobs:keyword:<term>), além de manter TTL para índices.- A classificação local também gera índices estruturados:
scraper:jobs:family:<family>,scraper:jobs:technology:<technology>escraper:jobs:seniority:<seniority>. - Filtros estruturados de localização, modelo, contrato e senioridade continuam em chaves como
scraper:jobs:country:<value>,scraper:jobs:model:<value>escraper:jobs:contract:<value>. jobstore.StableIDgarante IDs determinísticos para permitir identificação e deduplicação entre execuções.
- O backend publica keywords criadas por usuários na lista Valkey
scraper:keywords:pending(backend/src/lib/kwsync.ts, vialPush) quandoPOST /keywordsé chamado. internal/kwsync(scraper-go/internal/kwsync/kwsync.go) implementa umConsumerque faz polling dessa mesma chave a cada 30s e persiste as keywords novas no armazenamento local de keywords do scraper.- Controlado pela variável
KWSYNC_ENABLED(padrãofalse) em ambos os lados — quando desabilitado, o backend recusaPOST /keywordscom403e o consumidor Go não roda.
- Cache é abstraído por
internal/cachecom implementações Redis (NewRedisCache) e memória (fallback para testes). - Chave de cache do scraper é construída por
pipeline.BuildCacheKey(consistente com os parâmetros de busca).
- Há testes e fixtures (ex.:
internal/keywords/keywords.test.json) para validar normalização. - Recomenda-se executar
go test ./...dentro descraper-go. - Para validar a configuração final do Compose, execute na raiz:
docker compose \
-f docker-compose.infra.yml \
-f docker-compose.yml \
-f docker-compose.migrate.yml \
configConfirme no serviço scraper-go os equivalentes de SCRAPER_MAX_CONCURRENCY=12, SCRAPER_PROVIDER_MAX_CONCURRENCY=2, SCRAPER_PROVIDER_CONCURRENCY_OVERRIDES="", SCRAPER_CLASSIFICATION_BATCH_SIZE=100, SCRAPER_PERSIST_BATCH_SIZE=100, SCRAPER_INDEX_BATCH_SIZE=250, GOMAXPROCS=2, GOMEMLIMIT=1500MiB, cpus: 1.5 e mem_limit: 2g.
VALKEY_URL— conexão Redis/Valkey. Em Docker Compose, useredis://valkey:6379/0; em execução local fora do Docker, use uma URL acessível pelo host, por exemploredis://localhost:6379/0.SCRAPER_MAX_CONCURRENCY— teto global de concorrência por execução. Padrão:12. Configuração explícita inválida impede a inicialização.SCRAPER_PROVIDER_MAX_CONCURRENCY— limite padrão por provider. Padrão:2. Deve ser positivo e não pode superarSCRAPER_MAX_CONCURRENCY.SCRAPER_PROVIDER_CONCURRENCY_OVERRIDES— lista opcionalprovider=limite, separada por vírgulas. Vazio significa nenhum override; entrada inválida, duplicada, desconhecida ou acima do teto global impede a inicialização.SCRAPER_RUN_LOCK_TTL— duração do lock distribuído. Padrão:120s. Variável ausente usa o default; valor explícito vazio ou inválido impede a inicialização. No Compose, usa${SCRAPER_RUN_LOCK_TTL-120s}(mesmo padrão fail-fast deSCRAPER_MAX_CONCURRENCY).SCRAPER_RUN_LOCK_RENEW_INTERVAL— intervalo de renovação. Padrão:30s; deve ser menor queSCRAPER_RUN_LOCK_TTL. Variável ausente usa o default; valor explícito vazio ou inválido impede a inicialização. No Compose, usa${SCRAPER_RUN_LOCK_RENEW_INTERVAL-30s}.SCRAPER_CLASSIFICATION_BATCH_SIZE— tamanho do lote de classificação. Padrão:100. Mínimo1, máximo1000. Valor explícito inválido impede a inicialização.SCRAPER_PERSIST_BATCH_SIZE— tamanho do lote de persistência no Valkey (scraper:job:*). Padrão:100. Mínimo1, máximo1000.SCRAPER_INDEX_BATCH_SIZE— tamanho do lote de indexação invertida. Padrão:250. Mínimo1, máximo2500.GOMAXPROCS— limite efetivo de threads executando código Go simultaneamente. Valor inicial no Compose:2.GOMEMLIMIT— meta de memória do runtime/GC. Valor inicial no Compose:1500MiB; não substituimem_limitdo container.KWSYNC_ENABLED— padrãofalse. Liga/desliga o consumidor da filascraper:keywords:pending(ver seção "Sincronização de keywords com o backend").JOOBLE_API_KEY— Jooble integration.ADZUNA_APP_ID/ADZUNA_APP_KEY— Adzuna API.LINKEDIN_KEYWORD_SLOT_SIZE— quantidade máxima de keywords do LinkedIn por execução quando a busca vier com uma lista grande. Padrão:30.ADZUNA_KEYWORD_SLOT_SIZE— quantidade máxima de keywords do Adzuna por execução. Padrão:30.GUPY_ENABLED— habilita o adapter Gupy.GUPY_RAW_DISCOVERY_ENABLED— adiciona queries amplas de tecnologia na Gupy.GUPY_FULL_SWEEP_ENABLED/GUPY_FULL_REMOTE_SWEEP_ENABLED— controla sweeps amplos na Gupy.GUPY_QUERY_LIMIT— limita quantas queries expandidas da Gupy rodam por execução. Padrão:60.INHIRE_ENABLED,INHIRE_TENANTS_FILE,INHIRE_ENRICH_DETAILS,INHIRE_DETAILS_MODE,INHIRE_DETAILS_TIMEOUT_MS— controlam fonte e enriquecimento InHire.INHIRE_DETAILS_CONCURRENCYfoi removida; detalhes seguem o orçamento do provider.GREENHOUSE_ENABLED,GREENHOUSE_COMPANIES_FILE— controlam fonte Greenhouse.LEVER_ENABLED,LEVER_COMPANIES_FILE,LEVER_INCLUDE_ALL_JOBS— controlam fonte Lever.- Configurações de logging, quota e performance podem ser definidas via
.env.
- Projetado para rodar frequentemente; use caching e indexação para reduzir chamadas repetidas.
- Monitorar erros 429 e ajustar
WaitBetweenSearchesMse os limites globais/por provider. - Verifique logs estruturados (slog JSON) e
/metricspara métricas de sucesso/falhas por adaptador. - O log
scraper concurrency budgetregistra uma vez por execução o teto global, o default por provider e os overrides;scraper provider execution summaryregistra modo, tarefas produzidas/concluídas/canceladas, erros, timeouts e duração agregada. - Logs
scraper run lock acquired,scraper execution skipped,scraper run lock lostescraper run lock releasedidentificamsourceerun_id.
Durante uma execução controlada:
docker exec vagas-valkey valkey-cli GET scraper:run:lock
docker exec vagas-valkey valkey-cli PTTL scraper:run:lock
docker exec vagas-valkey valkey-cli HGETALL scraper:run:stateUma segunda execução manual deve retornar conflito sem iniciar adapters. Após o término, as duas chaves devem desaparecer. Nunca remova a chave manualmente apenas porque ela existe: primeiro confirme que não há processo correspondente ativo e registre valor e TTL.