Use Cases (Actualizado: 2/6/2026)

Web scraping seguro con Claude Code: Fetch, Playwright y registros de auditoria

Implementa web scraping seguro con Claude Code: robots.txt, Fetch, Playwright, CSV y auditoria.

Web scraping seguro con Claude Code: Fetch, Playwright y registros de auditoria

Define el limite antes de generar codigo

Web scraping significa leer informacion de paginas web mediante software. Puede servir para vigilar tu propio sitio, crear inventarios de documentacion publica, revisar precios publicados o hacer QA de contenido. Pero una pagina publica no significa que puedas recopilar todo sin condiciones. Claude Code acelera la implementacion, por eso el limite debe quedar escrito antes del primer archivo: solo datos publicos, respeto de terminos y robots.txt, peticiones espaciadas, nada de datos personales sin base legal y un registro de auditoria que permita explicar cada fila.

El orden practico para principiantes es buscar primero una API oficial, RSS, sitemap, exportacion CSV o cualquier canal documentado. Esas fuentes suelen ser mas estables que el HTML y tienen reglas de uso mas claras. Solo pasa a leer HTML cuando el objetivo sea legitimo, el volumen sea bajo y no exista una fuente estructurada mejor.

Este articulo usa Claude Code como asistente de implementacion, no como forma de saltarse controles. No cubre bypass de login, CAPTCHA, protecciones anti-bot, recoleccion masiva de emails ni datos restringidos. Si el flujo toca informacion personal, prospeccion comercial o datos regulados, confirma antes la finalidad, base legal, politica de privacidad, tiempo de retencion, opt-out y requisitos locales.

Trabaja con fuentes oficiales: RFC 9309 para robots.txt, documentacion de Google sobre robots.txt, MDN Fetch API y Playwright Browser contexts.

Flujo recomendado

flowchart TD
  A["Un objetivo claro"] --> B["Revisar API, RSS y sitemap"]
  B --> C["Leer terminos y robots.txt"]
  C --> D{"Basta con HTML estatico?"}
  D -->|Si| E["Fetch pagina por pagina"]
  D -->|No, con permiso| F["Playwright para DOM renderizado"]
  E --> G["CSV con URL y hora"]
  F --> G
  G --> H["Revision humana antes de usar"]

Asi la tarea para Claude Code deja de ser vaga. No pidas “raspa este sitio”; pide “lee una sola pagina de este origin permitido, espera al menos dos segundos, detente si robots.txt lo bloquea y guarda sourceUrl y fetchedAt en CSV”. Cuanto mas concreta sea la frontera, menos probable es que el codigo termine con bucles agresivos, selectores fragiles o datos que no deberian guardarse.

Cuando usar Fetch y cuando Playwright

Usa fetch cuando la informacion necesaria ya esta en la respuesta HTML. Documentacion estatica, articulos, paginas de precios publicos, paginas de estado y comprobaciones basadas en sitemap suelen encajar. Fetch es facil de auditar porque hace una peticion HTTP y devuelve texto. Tambien evita lanzar un navegador completo.

Usa Playwright solo cuando haga falta un navegador real y la pagina sea tuya o este aprobada para automatizacion. Ejemplos razonables son una vista previa local, staging, una QA interna o una prueba de tu propio sitio. La automatizacion de navegador carga scripts, cookies, localStorage, permisos y comportamiento del cliente. Por eso debes aislar los browser contexts y no mezclar sesiones.

Pide a Claude Code que empiece por Fetch. Anade Playwright solo si queda claro que el HTML estatico no resuelve el caso. En la revision, busca sleeps fijos, sesiones autenticadas accidentales, selectores basados en clases visuales, falta de rate limit y ausencia de metadatos de origen.

Casos de uso reales

El primer caso es vigilar tu propio sitio. Puedes comprobar paginas de formacion, productos, formularios, articulos, canonical URLs, titulos y textos de CTA. Como el sitio es tuyo, puedes mantener robots.txt, crear selectores estables y definir una frecuencia respetuosa. Esto conecta bien con operaciones de contenido con AI y auditoria de funnel de contenido: el scraper detecta cambios y el proceso editorial decide que corregir.

El segundo caso es recopilar URLs de documentacion publica. Un equipo puede crear un indice de documentacion oficial, handbook interno o base de conocimiento publica. Muchas veces no hace falta guardar el texto completo. URL, titulo, timestamp de comprobacion y estado bastan para busqueda, revision o plan editorial.

El tercer caso es revisar paginas publicas de precios de competidores con supervision humana. Un monitoreo pequeno puede mostrar cambios de nombres de planes, mensajes de campana o estructura publica. Pero el resultado no debe convertirse automaticamente en material comercial. Los precios dependen de region, impuestos, moneda y promociones. Conserva source URL y hora, y pide revision humana antes de tomar decisiones.

El cuarto caso es investigacion de leads con guardrails. Recoger nombre de empresa, web oficial, sector o pagina publica de contacto puede ser razonable en listas pequenas. Lo que no es razonable es capturar emails personales a ciegas y mandarlos a una campana outbound. Si luego hay outreach, incluye opt-out, identidad del remitente, lista de supresion y revision humana. Une esto con automatizacion de email con Claude Code solo cuando la recoleccion sea minima y permitida.

Fallos frecuentes

El fallo mas comun es ignorar robots.txt y los terminos. robots.txt no sustituye el analisis legal, pero expresa una frontera tecnica publicada por el sitio y debe respetarse. Los terminos pueden limitar automatizacion, reutilizacion o monitoreo comercial.

Otro fallo es guardar todo email que aparece. Que un dato personal sea visible no significa que puedas usarlo para cualquier finalidad. Si no lo necesitas, no lo recojas. Si lo necesitas, documenta finalidad, base legal, retencion, acceso, borrado y opt-out.

No tener rate limit tambien es peligroso. Un bucle que solicita cientos de paginas sin pausa puede parecer un ataque. Usa lotes pequenos, baja frecuencia, User-Agent claro, pocos reintentos y parada ante errores.

Los selectores fragiles causan fallos silenciosos. .card > div:nth-child(2) puede funcionar hoy y romperse con un cambio visual. Prefiere HTML semantico, time[datetime], main h1 o atributos data controlados por ti. Si falta un selector obligatorio, el job debe fallar y dejar diagnostico.

Evitar protecciones no es una funcionalidad. Si Claude Code propone saltarse CAPTCHA, login walls, proteccion bot, identidades rotadas o limites de frecuencia, detente y redisenalo con una fuente aprobada.

Tampoco guardes datos sensibles por defecto. HTML bruto, datos autenticados, tokens, informacion de clientes o registros personales no deben acabar en CSV sin revision. Para una vision mas amplia, revisa buenas practicas de seguridad con Claude Code.

Scraper Fetch copiable

Este script funciona con Node 18 o superior. Lee una sola pagina permitida, comprueba robots.txt de forma conservadora, aplica espera, extrae campos basicos y guarda CSV con sourceUrl y fetchedAt, ademas de un JSON de auditoria.

// scrape-allowed-page.mjs
import { writeFile } from "node:fs/promises";

const USER_AGENT = "ClaudeCodeLabAuditBot/1.0 (+https://example.com/bot-info)";
const BOT_TOKEN = "ClaudeCodeLabAuditBot";
const targetUrl = new URL(process.env.SCRAPE_URL ?? "https://example.com/");
const allowedOrigins = (process.env.ALLOWED_ORIGINS ?? "https://example.com")
  .split(",")
  .map((value) => new URL(value.trim()).origin);
const delayMs = Number.parseInt(process.env.REQUEST_DELAY_MS ?? "2000", 10);

if (!allowedOrigins.includes(targetUrl.origin)) {
  throw new Error(`Blocked by allowlist: ${targetUrl.origin}`);
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function fetchText(url, accept) {
  await sleep(delayMs);
  return fetch(url, {
    headers: {
      "user-agent": USER_AGENT,
      accept,
    },
  });
}

async function loadRobots(origin) {
  const robotsUrl = new URL("/robots.txt", origin);
  const response = await fetchText(robotsUrl, "text/plain");
  if (response.status === 404) {
    return { url: robotsUrl.toString(), status: response.status, text: null };
  }
  if (!response.ok) {
    throw new Error(`robots.txt check failed: HTTP ${response.status}`);
  }
  return {
    url: robotsUrl.toString(),
    status: response.status,
    text: await response.text(),
  };
}

function parseRobots(text) {
  const groups = [];
  let agents = [];
  let rules = [];

  function commit() {
    if (agents.length > 0) {
      groups.push({ agents, rules });
    }
    agents = [];
    rules = [];
  }

  for (const rawLine of text.split(/\r?\n/)) {
    const cleaned = rawLine.split("#")[0].trim();
    if (!cleaned) continue;
    const separator = cleaned.indexOf(":");
    if (separator === -1) continue;

    const field = cleaned.slice(0, separator).trim().toLowerCase();
    const value = cleaned.slice(separator + 1).trim();

    if (field === "user-agent") {
      if (rules.length > 0) commit();
      agents.push(value.toLowerCase());
      continue;
    }

    if ((field === "allow" || field === "disallow") && agents.length > 0) {
      rules.push({ type: field, path: value });
    }
  }

  commit();
  return groups;
}

function escapeRegExp(value) {
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function pathMatches(pattern, path) {
  if (!pattern) return false;
  const exact = pattern.endsWith("$");
  const normalized = exact ? pattern.slice(0, -1) : pattern;
  const source = `^${escapeRegExp(normalized).replace(/\\\*/g, ".*")}${exact ? "$" : ""}`;
  return new RegExp(source).test(path);
}

function isAllowedByRobots(robotsText, url) {
  if (robotsText === null) {
    return process.env.ALLOW_WITHOUT_ROBOTS === "true";
  }

  const groups = parseRobots(robotsText);
  const bot = BOT_TOKEN.toLowerCase();
  const exactGroups = groups.filter((group) =>
    group.agents.some((agent) => agent !== "*" && bot.includes(agent)),
  );
  const fallbackGroups = groups.filter((group) => group.agents.includes("*"));
  const selectedGroups = exactGroups.length > 0 ? exactGroups : fallbackGroups;
  const rules = selectedGroups.flatMap((group) => group.rules);
  const targetPath = `${url.pathname}${url.search}`;
  let winner = null;

  for (const rule of rules) {
    if (!pathMatches(rule.path, targetPath)) continue;
    const length = rule.path.replace(/[*$]/g, "").length;
    if (!winner || length > winner.length || (length === winner.length && rule.type === "allow")) {
      winner = { type: rule.type, length };
    }
  }

  return winner ? winner.type === "allow" : true;
}

function normalizeText(value) {
  return value
    .replace(/<script[\s\S]*?<\/script>/gi, " ")
    .replace(/<style[\s\S]*?<\/style>/gi, " ")
    .replace(/<[^>]*>/g, " ")
    .replace(/&amp;/g, "&")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'")
    .replace(/\s+/g, " ")
    .trim();
}

function firstMatch(html, pattern) {
  const match = html.match(pattern);
  return match ? normalizeText(match[1]) : "";
}

function extractPageSummary(html) {
  const metaMatch =
    html.match(/<meta\s+[^>]*name=["']description["'][^>]*content=["']([^"']*)["'][^>]*>/i) ??
    html.match(/<meta\s+[^>]*content=["']([^"']*)["'][^>]*name=["']description["'][^>]*>/i);

  return {
    title: firstMatch(html, /<title[^>]*>([\s\S]*?)<\/title>/i),
    h1: firstMatch(html, /<h1[^>]*>([\s\S]*?)<\/h1>/i),
    metaDescription: metaMatch ? normalizeText(metaMatch[1]) : "",
    linkCount: [...html.matchAll(/<a\s+[^>]*href=["'][^"']+["']/gi)].length,
  };
}

function csvEscape(value) {
  const text = String(value ?? "");
  return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
}

const robots = await loadRobots(targetUrl.origin);
if (!isAllowedByRobots(robots.text, targetUrl)) {
  throw new Error(`Blocked by robots.txt: ${targetUrl.toString()}`);
}

const response = await fetchText(targetUrl, "text/html");
if (!response.ok) {
  throw new Error(`Page fetch failed: HTTP ${response.status}`);
}

const html = await response.text();
const fetchedAt = new Date().toISOString();
const row = {
  sourceUrl: targetUrl.toString(),
  fetchedAt,
  ...extractPageSummary(html),
};
const headers = ["sourceUrl", "fetchedAt", "title", "h1", "metaDescription", "linkCount"];
const csv = [headers.join(","), headers.map((header) => csvEscape(row[header])).join(",")].join("\n");

await writeFile("scrape-output.csv", `${csv}\n`, "utf8");
await writeFile(
  "scrape-audit.json",
  JSON.stringify(
    {
      checkedAt: fetchedAt,
      userAgent: USER_AGENT,
      robotsUrl: robots.url,
      robotsStatus: robots.status,
      allowedOrigins,
      sourceUrl: row.sourceUrl,
    },
    null,
    2,
  ),
  "utf8",
);

console.log(`Saved scrape-output.csv for ${row.sourceUrl}`);

En PowerShell puedes ejecutarlo asi, cambiando el dominio por uno propio o autorizado: $env:SCRAPE_URL="https://your-domain.example/page"; $env:ALLOWED_ORIGINS="https://your-domain.example"; node scrape-allowed-page.mjs. La salida es deliberadamente sencilla: una fila CSV y un archivo JSON de auditoria.

Playwright solo para paginas propias

Este ejemplo de Playwright comprueba selectores renderizados en una pagina propia o local. No sirve para evitar protecciones externas.

// check-own-site-selectors.mjs
import { writeFile } from "node:fs/promises";
import { chromium } from "playwright";

const target = process.env.LOCAL_PREVIEW_URL ?? "http://127.0.0.1:4321/blog/claude-code-web-scraping/";
const allowedPrefixes = [
  "http://127.0.0.1:",
  "http://localhost:",
  "https://claudecodelab.com/",
];

if (!allowedPrefixes.some((prefix) => target.startsWith(prefix))) {
  throw new Error(`Playwright check is limited to owned or local pages: ${target}`);
}

const browser = await chromium.launch();
const context = await browser.newContext({
  userAgent: "ClaudeCodeLabAuditBot/1.0 local-preview-check",
});
const page = await context.newPage();

await page.goto(target, { waitUntil: "domcontentloaded" });

const checks = [
  { name: "article title", selector: "main h1, article h1" },
  { name: "updated date", selector: "time, [data-updated-date]" },
  { name: "main article", selector: "main article, article" },
];
const results = [];

for (const check of checks) {
  const locator = page.locator(check.selector);
  const count = await locator.count();
  const firstText = count > 0 ? ((await locator.first().textContent()) ?? "").trim().slice(0, 120) : "";
  results.push({ ...check, count, firstText });
}

await writeFile(
  "selector-audit.json",
  JSON.stringify({ target, checkedAt: new Date().toISOString(), results }, null, 2),
  "utf8",
);

await context.close();
await browser.close();

const missing = results.filter((result) => result.count === 0);
if (missing.length > 0) {
  throw new Error(`Missing selectors: ${missing.map((result) => result.name).join(", ")}`);
}

console.log(`Saved selector-audit.json for ${target}`);

Los browser contexts separan cookies, localStorage y permisos. No apuntes esta automatizacion a una sesion real autenticada salvo que el caso este aprobado y los datos puedan procesarse.

Prompt para Claude Code

Un prompt mas seguro seria:

Agrega un scraper de una sola pagina para un origin en allowlist. Primero documenta si existe API oficial, RSS o sitemap. Comprueba robots.txt antes de leer HTML. Guarda sourceUrl y fetchedAt en CSV. No recojas emails, nombres personales, datos autenticados ni secretos. No esquives CAPTCHA, login walls, protecciones bot ni rate limits. Agrega throttling, detente si el path esta bloqueado y muestra node --check para los archivos JavaScript.

La idea es que Claude Code implemente de forma auditable, no que decida el limite legal por ti. Antes de programar ejecuciones periodicas, una persona debe revisar diff, URLs objetivo, campos guardados, cadencia, proceso de borrado y ejemplos de salida.

Checklist operativo

  • Buscar API, RSS, sitemap o export antes de HTML.
  • Confirmar que la pagina es publica y el uso encaja con los terminos.
  • Respetar robots.txt y registrar la comprobacion.
  • Usar allowlist de origin y lotes pequenos.
  • Anadir delay, reintentos limitados y parada ante errores.
  • Usar un User-Agent claro.
  • Guardar source URL, timestamp, metodo y robots status.
  • Preferir selectores semanticos o atributos data propios.
  • No guardar por defecto datos personales, secretos, sesiones ni HTML bruto.
  • Revisar muestras manualmente antes de usar el resultado.

Si el CSV se abre en hojas de calculo, considera CSV injection. Todo texto venido de la web debe tratarse como entrada no confiable. Integra este flujo con controles de seguridad, automatizacion de contenido y outreach, no con una escritura silenciosa al CRM.

Formacion y consultoria

El codigo es la parte facil. Lo dificil es decidir que no recopilar, como demostrar que la recopilacion estaba permitida, como revisar cambios y como borrar salidas antiguas. ClaudeCodeLab puede ayudar a convertir esto en reglas de CLAUDE.md, pruebas Playwright, logs CSV y aprobaciones humanas mediante formacion y consultoria de Claude Code.

Para empezar solo, usa una pagina, una ejecucion y datos publicos. No aumentes el volumen hasta tener auditoria, comportamiento ante fallos y revision humana.

Resumen

El web scraping seguro con Claude Code empieza por limites, no por selectores. Prioriza API y sitemaps; usa Fetch para paginas estaticas permitidas; reserva Playwright para paginas dinamicas propias o aprobadas. Registra siempre URL, hora, robots status y User-Agent.

Tambien debes evitar ignorar terminos, capturar emails a ciegas, saltarte limites, aceptar fallos silenciosos de selectores, esquivar protecciones o volcar datos sensibles. Claude Code acelera la mecanica, pero el flujo solo es util en produccion cuando una persona puede explicar finalidad, fuente, fecha y borrado.

Al probar este flujo, Masa vio que limitar la primera version a una pagina, una fila CSV y un JSON de auditoria hacia la revision mucho mas sencilla. sourceUrl y fetchedAt fueron especialmente utiles para comprobar paginas de precios y contenido propio. Los prototipos que empezaron con muchas paginas acabaron reescritos porque los fallos de selectores y las omisiones de politica eran dificiles de rastrear.

#Claude Code #web scraping #Fetch API #Playwright #robots.txt
Gratis

PDF gratis: cheatsheet de Claude Code

Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.

Cuidamos tus datos y no enviamos spam.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.