Ir para o conteúdo

Marketplace ClickBank — captura v2 (API-first)

Status: implementado e verificado em produção (2026-09-10). A API interna não é observável no caminho real (ver "Resultado do primeiro probe"); o DOM com detecção dinâmica de página é o caminho confiável e cobre o catálogo acessível inteiro (~1.233 ofertas).

Contexto

Não existe feed XML público/anônimo do Marketplace ClickBank (verificado em docs/integrations/clickbank.md). O caminho aprovado é a captura autenticada via navegador dedicado (Playwright + perfil persistente). Até a v1, a captura era 100% DOM (role=gridcell), o que trazia:

  • ~25 páginas com scroll virtual e pausas humanas (lento);
  • apenas rank/título/vendor/EPC/CVR/gravity (sem URL, categoria, JV tools, rebill);
  • categoria "Uncategorized" em 100% dos itens → compliance LOW falso;
  • fragilidade a qualquer mudança de layout da SPA.

Arquitetura v2 (API-first com fallback DOM)

  1. Probe (scripts/marketplace-scraper.js): hooks de fetch/XHR já capturavam as respostas da API interna. Agora também gravam metadados sanitizados em meta.apiProbe: { path, params (apenas nomes), itemCount, responseKeys, itemKeys, totalHint }. Nenhum header, token ou payload é exportado.
  2. Captura: discoverApi() navega uma página para descobrir o endpoint de listagem e os parâmetros de paginação; captureViaApi() reexecuta a chamada (mesmos headers de sessão, page size da própria API) — 1 request por página, sem depender do DOM. Falha em qualquer etapa → fallback automático para o caminho DOM (captureViaDom), que permanece intacto.
  3. Cobertura: meta.fieldCoverage conta itens com dado real por campo (category, url, affiliate_tools_url, hoplink_base, vendor_domain, rebill_rate, funnel_type, …). O dialog do Radar exibe essa cobertura antes da importação.
  4. Compliance: categoria ausente/Uncategorized → UNKNOWN (nunca LOW), tanto no servidor quanto no parser do feed XML.
  5. Automação: LaunchAgent local (com.brandvelia.marketplace-capture) roda scripts/marketplace-daily.mjs diariamente às 03:30; capturas complete são auto-importadas via /api/cron/marketplace-capture-import (Bearer CRON_SECRET, hash canônico recomputado, service-role, workspace único). Capturas partial continuam exigindo revisão manual no Radar.

Como executar o spike empírico (uma vez)

  1. Instalar o agente: duplo clique em scripts/install-capture-agent.command.
  2. Fazer login na janela dedicada do ClickBank (primeira vez).
  3. Rodar: pnpm capture:marketplace e, no Radar, Capturar do Marketplace.
  4. Ler o apiProbe no capture_meta do snapshot importado (ou nos arquivos scripts/captures/marketplace-*.json → meta.apiProbe).
  5. Preencher a tabela abaixo com o endpoint real e os campos disponíveis.

Resultado do primeiro probe (2026-09-10)

Item Valor observado
Endpoint de listagem não observável — a SPA não emite request de ofertas após a injeção (dados carregados antes / paginação client-side); meta.apiProbe fica null e o caminho cai no DOM
Parâmetros de paginação #/results?sortField=rank&sortDescending=false&resultsPerPage=50&offset=N (via hash; usado como fallback)
Page size 50 (o valor antigo de 30 observado em outro perfil vinha da preferência da grade)
Total do catálogo (UI) ~1.233 ofertas em 25 páginas; a paginação do MUI anuncia mais páginas (83) mas as páginas 26+ repetem os mesmos itens — o controle de completude mede a passada global e ignora o excedente da UI
Campos ricos (categoria/URL/JV/rebill) indisponíveis na grade (0/1237); categoria/compliance vêm de detectComplianceRisk por termo do nome do produto
Mode efetivo da captura dom com fallback de navegação por hash
Resultado real 1.237 ofertas, status=complete, auto-import via /api/cron/marketplace-capture-import → 1.237 upserts em bv_marketplace_snapshots, 100% vinculadas ao run
Compliance apurado UNKNOWN 834 · MEDIUM 151 · HIGH 15 (nunca LOW sem categoria real)

Falhas reais corrigidas neste ciclo

  1. Tela de login aceita como lista: heurística antiga injetava o scraper na página de login (0 células, run travado). Agora exige gridcells/linhas reais.
  2. Página presumida em 50: o perfil podia renderizar 30/página e a captura truncava em 750. O tamanho vem do texto de paginação/linhas renderizadas.
  3. "Próxima" some na página 25: fallback de navegação por hash avança até o fim do catálogo.
  4. Run travado sem progresso: watchdog de 6 min no driver + state.failed explícito no scraper (erro/lista vazia) em vez de esperar 45 min.
  5. View do PostgREST desatualizada: bv_marketplace_snapshots não expunha marketplace_sync_run_id; migration recria as views e restaura grants.
  6. Log duplicado do agente: removida a escrita dupla (stdout + arquivo).
  7. Auto-import frágil: 3 tentativas com backoff no agente diário.

Contrato de aceite da captura v2

  • Captura completa em ≤ 1/3 do tempo da v1 (API-first) com fallback DOM estável.
  • Top 300 com ≥ 90% de categoria real (quando a API expõe categoria).
  • Nenhum item importado com compliance_status = LOW sem categoria real.
  • Reexecução no mesmo dia não duplica (workspace_id, affiliate_network, identity_key, snapshot_date).
  • Retenção: snapshots com mais de 90 dias são removidos pelo housekeeping.