Scraping de Datos de Cuenta
Resumen
El usuario inicia sesión con sus propias manos en el navegador Playwright; tú encuentras la fuente de datos que el propio sitio usa en sus páginas y la reproduces dentro de la página (cookies de sesión + encabezados reales), guardando las respuestas sin procesar en disco. Nunca manejes credenciales.
Cuándo Usarlo
- "Obtén mi historial de ", "extrae mis pedidos/viajes/reservas", "usa este repositorio de scraping"
- Repositorios de scraping antiguos (requests + contraseña/OTP): léelos en GitHub solo para nombres de endpoints y selectores. Nunca los ejecutes: las comprobaciones de bots (cuerpo vacío HTTP 202, captchas) los rompen y solicitan la contraseña.
Exportación oficial primero. Google Takeout, "Descarga tus datos", solicitudes GDPR/DPDP, APIs públicas, recibos por correo electrónico. Las exportaciones lentas tardan días: pídele al usuario que solicite una al inicio, luego haz scraping ahora y verifica de forma cruzada por ID cuando llegue.
Flujo de Trabajo
- Ignorar el directorio de salida antes de escribir nada (
data/<servicio>/o su equivalente en el proyecto); confirma congit check-ignore. Los datos sin procesar contienen direcciones, teléfonos, GPS. - Navegar, delegar inicio de sesión. Abre la página de la cuenta; dile al usuario que escriba teléfono/contraseña/OTP en la ventana del navegador y responda cuando termine. Nunca pidas códigos en el chat.
- Descubrir la fuente (
browser-snippets.md§1). Recarga con un listener y clasifica:- JSON XHR/fetch (mejor) — anota el endpoint de la lista, la forma de paginación (
next_url/cursor,page_number, offset = último id,endOfList), y el endpoint de detalle (abre un elemento). - GraphQL — un
POST /graphql; registra el cuerpo completo de la solicitud (operationName, query o hash de consulta persistida, variables). - HTML renderizado por servidor — los datos llegan en la respuesta del
document; obtén las páginas de lista y detalle dentro de la página y analízalas conDOMParser; guarda también el HTML sin procesar para poder re-analizarlo. - Listas particionadas — si la lista está dividida por un filtro (selector de año, rango de fechas, pestañas como archivado/cancelado/digital), lee cada valor de filtro y pagina cada uno.
- JSON XHR/fetch (mejor) — anota el endpoint de la lista, la forma de paginación (
- Autenticación. Prueba primero con
fetch(url, {credentials: 'include'}). Si devuelve 401/403/vacío, copia los encabezados no del navegador de una solicitud en vivo (§2): copia, nunca adivines valores. Registra solo los nombres de los encabezados; nunca imprimas ni guardes valores. - Paginar en segundo plano dentro de la página (§2):
window.__x = {running, pages, error}, 1.5s entre solicitudes, reintentos, sondea con evaluaciones cortas. Cada respuesta: si es una página de inicio de sesión/captcha (la URL contiene signin/captcha, o HTML en lugar del JSON esperado), estableceerrory detente — estas a menudo llegan como 200. - Guardar antes de navegar (§3). Cualquier
goto/recarga borrawindow.browser_evaluatecon una ruta absoluta defilename, devolviendo un objeto (JSON.stringifycodifica dos veces). ~5 MB por archivo está bien; divide por lote más allá de eso. - Solicitudes firmadas/no reproducibles (encabezados de firma de solicitud, cuerpos con nonce, consultas persistentes que rechazan ediciones): visita cada página de detalle y captura pasivamente sus respuestas (§4).
- Construir + verificar (abajo) con un script analizador fuera del directorio de datos, luego una nota de insights. Sigue el diseño existente del repositorio si lo hay (ej.
tools/<servicio>/build_db.py, notas junto a los datos); de lo contrario, pregunta.
Solo lectura. Reproduce solo lo que la propia página solicita para mostrar datos. GraphQL: juzga por operación (query/Get*/List*), nunca mutation. Nunca reordenes, canceles, califiques, archives, elimines.
Verificación — haz todo esto
| Comprobación | Por qué |
|---|---|
| La paginación terminó porque el cursor/lista se agotó, no por un error o redirección de inicio de sesión | Truncamiento silencioso |
| Busca el texto "ver pedidos anteriores en la app" al final | Las vistas web limitan el historial (Swiggy: ~11 meses) |
| No confíes solo en el endpoint de conteo o encabezado del sitio | Blinkit decía 40; la lista tenía 83 |
| Los componentes suman al total por fila (artículos + tarifas − descuentos = pagado) | Detecta desviación del analizador; si el sitio omite líneas para registros antiguos, dilo — no lo fuerces |
| Los precios de los artículos suman al total de artículos | Confusión entre precio por unidad y total de línea |
| IDs únicos; elimina duplicados exactos | Las exportaciones y recibos listan dos veces |
| Verifica de forma cruzada otra fuente por ID (exportación, recibos) | Prueba la cobertura |
| Cada lista/pestaña/filtro fue cubierto (archivado, cancelado, otros años, subservicios) | "Todos los pedidos" a menudo abarca varias listas |
Notas de análisis
- APIs de IU impulsadas por servidor (
snippets,widgets,widget_type,layout): recorre los widgets en orden, recopila los campostextde cada widget, asigna según etiquetas conocidas. Un widget de un solo texto suele ser una etiqueta para el siguiente: crea una lista blanca de nombres de etiquetas o cada campo se desplazará. - Los montos pueden ser en paisa/centavos; "GRATIS" = 0; "-" inicial = descuento; elimina símbolos de moneda y agrupación de comas indias. Mantén las monedas separadas.
- Marcas de tiempo: compras → zona horaria del usuario; viajes/desplazamientos → hora local del registro.
- Mantén los archivos sin procesar fechados (
pedidos-crudos-YYYY-MM-DD.json) junto al SQLite construido.
Git
Confirma solo: la línea de gitignore, el script analizador, cualquier fila de README/índice. Los archivos sin procesar y el SQLite se quedan locales. Prepara rutas explícitas (nunca git add -A en un árbol sucio); confirma solo cuando se te pida.
Errores Comunes
- Un bucle de evaluación síncrono largo — se agota el tiempo; ejecútalo en segundo plano y sondea.
- Devolver datos masivos al chat en lugar de
filename. - Listeners de fragmentos dejados adjuntos — elimínalos con
page.off, o las ejecuciones repetidas acumulan manejadores. - Filtrar el descubrimiento a xhr/fetch en un sitio renderizado con HTML y concluir "no hay fuente de datos".