Extraindo Dados de Contas
Visão Geral
O usuário faz login manualmente no navegador Playwright; você encontra a fonte de dados que o próprio site utiliza para exibir informações e a reproduz dentro da página (cookies de sessão + cabeçalhos reais), salvando as respostas brutas em disco. Nunca manipule credenciais.
Quando Usar
- "Obter meu histórico de <serviço>", "extrair meus pedidos/viagens/reservas", "usar este repositório de scraper"
- Repositórios de scraper antigos (requests + login com senha/OTP): leia-os no GitHub apenas para obter nomes de endpoints e seletores. Nunca os execute — verificações de bot (HTTP 202 corpo vazio, captchas) os quebram, e eles exigem a senha.
Exportação oficial primeiro. Google Takeout, "Baixar seus dados", solicitações GDPR/DPDP, APIs públicas, recibos por e-mail. Exportações lentas levam dias — peça ao usuário para solicitar uma no início, depois extraia agora e cruze por ID quando ela chegar.
Fluxo de Trabalho
- Adicione o diretório de saída ao gitignore antes de escrever qualquer coisa (
data/<servico>/ou equivalente do projeto); confirme comgit check-ignore. Dados brutos contêm endereços, telefones, GPS. - Navegue, entregue o login. Abra a página da conta; diga ao usuário para digitar telefone/senha/OTP na janela do navegador e responder quando terminar. Nunca peça códigos no chat.
- Descubra a fonte (
browser-snippets.md§1). Recarregue com um listener e classifique:- JSON XHR/fetch (melhor) — anote o endpoint da lista, a forma da paginação (
next_url/cursor,page_number, offset = último id,endOfList) e o endpoint de detalhes (abra um item). - GraphQL — um
POST /graphql; registre o corpo completo da requisição (operationName, query ou hash de consulta persistida, variables). - HTML renderizado no servidor — os dados chegam na resposta
document; busque as páginas de lista e detalhes dentro da página e analise comDOMParser; salve o HTML bruto também para poder reanalisar. - Listas particionadas — se a lista for dividida por um filtro (menu suspenso de ano, intervalo de datas, guias como arquivado/cancelado/digital), leia cada valor de filtro e pagine cada um deles.
- JSON XHR/fetch (melhor) — anote o endpoint da lista, a forma da paginação (
- Autenticação. Tente
fetch(url, {credentials: 'include'})primeiro. Se retornar 401/403/vazio, copie os cabeçalhos não-navegador de uma requisição ativa (§2) — copie, nunca adivinhe valores. Registre apenas os nomes dos cabeçalhos; nunca imprima ou salve valores. - Paginação em segundo plano dentro da página (§2):
window.__x = {running, pages, error}, 1,5s entre requisições, repetições, verifique com avaliações curtas. Cada resposta: se for uma página de login/captcha (URL contém signin/captcha, ou HTML em vez do JSON esperado), definaerrore pare — muitas vezes essas chegam como 200. - Salve antes de navegar (§3). Qualquer
goto/recarregamento limpawindow. Usebrowser_evaluatecom um caminho defilenameabsoluto, retornando um objeto (JSON.stringifyfaz dupla codificação). ~5 MB por arquivo é aceitável; divida por lote além disso. - Requisições assinadas/não reproduzíveis (cabeçalhos com assinatura de requisição, corpos com nonce, consultas persistentes que rejeitam edições): visite cada página de detalhes e capture passivamente suas respostas (§4).
- Construa + verifique (abaixo) com um script de parser fora do diretório de dados, depois uma nota de insights. Siga o layout existente do repositório, se houver (ex.:
tools/<servico>/build_db.py, notas ao lado dos dados); caso contrário, pergunte.
Somente leitura. Reproduza apenas o que a própria página solicita para exibir dados. GraphQL: julgue pela operação (query/Get*/List*), nunca mutation. Nunca reordene, cancele, avalie, arquive ou exclua.
Verificação — faça todas estas
| Verificação | Por que |
|---|---|
| A paginação terminou porque o cursor/lista acabou, não por erro ou redirecionamento de login | Truncamento silencioso |
| Procure por texto "ver pedidos mais antigos no aplicativo" no final | Visualizações na web limitam o histórico (Swiggy: ~11 meses) |
| Não confie apenas no endpoint de contagem ou cabeçalho do site | Blinkit disse 40; a lista tinha 83 |
| Componentes somam ao total por linha (itens + taxas − descontos = pago) | Detecta desvio do parser; se o site omitir linhas para registros antigos, diga — não force |
| Preços dos itens somam ao total do item | Confusão entre preço por unidade vs. total da linha |
| IDs únicos; deduplique duplicatas exatas | Exportações e recibos listam duplicado |
| Cruze outra fonte por ID (exportação, recibos) | Comprova cobertura |
| Cada lista/guia/filtro foi coberto (arquivado, cancelado, outros anos, sub-serviços) | "Todos os pedidos" muitas vezes abrange várias listas |
Notas de Parsing
- APIs de UI orientadas a servidor (
snippets,widgets,widget_type,layout): percorra os widgets em ordem, colete os campostextde cada widget, mapeie por rótulos conhecidos. Um widget de texto único geralmente é um rótulo para o próximo — use uma lista branca de nomes de rótulos ou todos os campos se deslocam. - Valores podem estar em centavos; "FREE" = 0; "-" à esquerda = desconto; remova símbolos de moeda e agrupamento de vírgulas indiano. Mantenha moedas separadas.
- Timestamps: compras → fuso horário do usuário; viagens/deslocamentos → hora local do registro.
- Mantenha arquivos brutos datados (
orders-raw-YYYY-MM-DD.json) ao lado do SQLite construído.
Git
Commit apenas: a linha do gitignore, o script do parser, qualquer README/linha de índice. Arquivos brutos e o SQLite permanecem locais. Faça stage de caminhos explícitos (nunca git add -A em uma árvore suja); commit apenas quando solicitado.
Erros Comuns
- Um único loop de avaliação síncrono longo — estoura tempo limite; execute em segundo plano e verifique.
- Retornar dados em massa para o chat em vez de
filename. - Listeners de snippet deixados anexados — use
page.offneles, ou execuções repetidas acumulam manipuladores. - Filtrar a descoberta apenas para xhr/fetch em um site renderizado em HTML e concluir "não há fonte de dados".