Web scraping seguro com Claude Code: Fetch, Playwright e logs de auditoria
Implemente web scraping seguro com Claude Code: robots.txt, Fetch, Playwright, CSV e auditoria.
Defina o limite antes de gerar codigo
Web scraping e ler informacoes de paginas web com software. Pode ser util para monitorar seu proprio site, montar inventarios de documentacao publica, revisar precos publicados ou fazer QA de conteudo. Mas uma pagina publica nao significa permissao para coletar tudo. Claude Code acelera a implementacao, por isso o limite precisa estar escrito antes do codigo: apenas dados publicos, respeito aos termos e ao robots.txt, requisicoes com intervalo, nada de dados pessoais sem base adequada e um registro de auditoria para explicar cada linha.
Para iniciantes, a ordem segura e procurar primeiro API oficial, RSS, sitemap, exportacao CSV ou outro acesso documentado. Essas fontes sao mais estaveis que HTML e costumam ter regras de uso mais claras. Leia HTML apenas quando o objetivo for legitimo, o volume for pequeno e nao houver fonte estruturada melhor.
Este artigo usa Claude Code como assistente de implementacao, nao como forma de contornar controles. Nao cobrimos bypass de login, CAPTCHA, protecao anti-bot, coleta massiva de emails ou dados restritos. Se o fluxo envolver dados pessoais, prospeccao comercial ou informacao regulada, confirme finalidade, base legal, politica de privacidade, retencao, opt-out e regras locais antes.
Use fontes oficiais: RFC 9309 para robots.txt, documentacao Google sobre robots.txt, MDN Fetch API e Playwright Browser contexts.
Fluxo recomendado
flowchart TD
A["Um objetivo claro"] --> B["Verificar API, RSS e sitemap"]
B --> C["Ler termos e robots.txt"]
C --> D{"HTML estatico basta?"}
D -->|Sim| E["Fetch pagina por pagina"]
D -->|Nao, autorizado| F["Playwright para DOM renderizado"]
E --> G["CSV com URL e horario"]
F --> G
G --> H["Revisao humana antes do uso"]
Assim a tarefa para Claude Code fica concreta. Em vez de “raspe este site”, peca “acesse uma pagina deste origin permitido, espere pelo menos dois segundos, pare se robots.txt bloquear e grave sourceUrl e fetchedAt no CSV”. Quanto mais clara a fronteira, menor a chance de loops agressivos, seletores frageis ou dados indevidos.
Fetch ou Playwright
Use fetch quando a informacao ja esta na resposta HTML. Documentacao estatica, artigos, paginas publicas de preco, status pages e verificacoes via sitemap geralmente funcionam bem. Fetch e facil de auditar: faz uma requisicao HTTP e retorna texto. Tambem evita abrir um navegador completo.
Use Playwright apenas quando um navegador real for necessario e a pagina for sua ou explicitamente autorizada para automacao. Exemplos: preview local, staging, QA interna ou teste do seu proprio site. Automacao de navegador carrega scripts, cookies, localStorage, permissoes e comportamento de cliente. Separe browser contexts para nao misturar estado entre tarefas.
Peca para Claude Code comecar pela versao Fetch. Adicione Playwright so depois de provar que HTML estatico nao resolve. Na revisao, procure sleeps fixos, sessao logada acidental, seletores baseados em classe visual, falta de rate limit e ausencia de metadados de origem.
Casos de uso
O primeiro caso e monitorar seu proprio site. Confira paginas de treinamento, produtos, formularios, artigos, canonical URLs, titulos e CTA. Como o site e seu, voce pode manter robots.txt, seletores estaveis e frequencia respeitosa. Conecte isso a operacoes de conteudo com AI e auditoria de funil de conteudo: o scraper detecta mudancas e o workflow editorial decide o ajuste.
O segundo caso e coletar URLs de documentacao publica. Uma equipe pode indexar docs oficiais, handbook interno ou base de conhecimento publica. Muitas vezes nao e preciso guardar o texto completo. URL, titulo, horario de verificacao e status sao suficientes para busca, revisao e planejamento editorial.
O terceiro caso e verificar paginas publicas de preco de concorrentes com revisao humana. Um monitoramento pequeno pode mostrar mudancas em nomes de planos, mensagens promocionais ou estrutura publica. Mas o resultado nao deve virar verdade automatica. Precos variam por regiao, impostos, moeda e condicoes. Guarde source URL e horario, depois revise amostras manualmente.
O quarto caso e pesquisa de leads com guardrails. Coletar nome da empresa, site oficial, setor e pagina publica de contato pode ser razoavel em pequena escala. Coletar emails pessoais cegamente e jogar em campanha outbound nao e. Se houver outreach, inclua opt-out, identidade do remetente, lista de supressao e revisao humana. So conecte com automacao de email Claude Code quando a coleta for minima e permitida.
Erros comuns
O erro mais comum e ignorar robots.txt e termos. robots.txt nao substitui analise legal, mas e uma fronteira tecnica publicada pelo site e deve ser respeitada. Termos podem limitar automacao, reutilizacao ou monitoramento comercial.
Outro erro e salvar todo email encontrado. Dado pessoal visivel ainda pode ser dado pessoal. Se voce nao precisa, nao colete. Se precisa, documente finalidade, base, retencao, acesso, exclusao e opt-out.
Sem rate limit, o risco e tecnico e reputacional. Centenas de requisicoes sem pausa podem parecer ataque. Use lotes pequenos, baixa frequencia, User-Agent claro, poucas tentativas e parada em erro.
Seletores frageis geram falhas silenciosas. .card > div:nth-child(2) pode funcionar hoje e quebrar amanha. Prefira HTML semantico, time[datetime], main h1 ou atributos data controlados por voce. Se um seletor obrigatorio faltar, o job deve falhar e registrar diagnostico.
Contornar protecoes nao e recurso. Se Claude Code sugerir CAPTCHA bypass, login wall scraping, identidades rotativas ou burlar rate limit, pare e redesenhe com fonte aprovada.
Tambem nao salve dados sensiveis por padrao. HTML bruto, dados autenticados, tokens, informacoes de clientes e registros pessoais nao devem ir para CSV sem revisao. Para a parte defensiva, leia Claude Code security best practices.
Exemplo Fetch para copiar
Este script roda em Node 18 ou superior. Ele le uma unica pagina permitida, verifica robots.txt de forma conservadora, aplica intervalo, extrai campos basicos e grava CSV com sourceUrl e fetchedAt, alem de 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(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, '"')
.replace(/'/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}`);
No PowerShell: $env:SCRAPE_URL="https://your-domain.example/page"; $env:ALLOWED_ORIGINS="https://your-domain.example"; node scrape-allowed-page.mjs. A saida e simples de proposito: uma linha CSV e um JSON de auditoria.
Playwright apenas para paginas proprias
Este exemplo Playwright verifica seletores renderizados no seu site ou preview local. Nao serve para contornar protecoes 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}`);
Browser contexts isolam cookies, localStorage e permissoes. Nao use uma sessao real autenticada sem aprovacao explicita e base clara para processar os dados.
Prompt para Claude Code
Um prompt mais seguro:
Adicione um scraper de uma unica pagina para um origin em allowlist. Primeiro documente se existe API oficial, RSS ou sitemap. Verifique robots.txt antes de buscar HTML. O CSV deve ter
sourceUrlefetchedAt. Nao colete emails, nomes pessoais, dados autenticados ou secrets. Nao contorne CAPTCHA, login wall, protecao bot ou rate limit. Adicione throttling, pare em path bloqueado e mostrenode --checkpara os arquivos JavaScript.
O objetivo e fazer Claude Code implementar um fluxo auditavel, nao decidir o limite legal por voce. Antes de agendar execucoes, uma pessoa deve revisar diff, URLs alvo, campos salvos, frequencia, exclusao e amostras de saida.
Checklist operacional
- Procurar API, RSS, sitemap ou export antes de HTML.
- Confirmar que a pagina e publica e o uso respeita os termos.
- Respeitar robots.txt e registrar a verificacao.
- Usar allowlist de origin e lotes pequenos.
- Adicionar delay, retries limitados e parada em erro.
- Usar User-Agent claro.
- Salvar source URL, timestamp, metodo e robots status.
- Preferir seletores semanticos ou atributos data proprios.
- Nao salvar por padrao dados pessoais, secrets, sessao ou HTML bruto.
- Revisar amostras manualmente antes de decisoes de negocio.
Se o CSV for aberto em planilhas, considere CSV injection. Todo texto vindo da web deve ser tratado como entrada nao confiavel. Conecte o fluxo a revisao de seguranca, automacao de conteudo e controles de outreach, nao a importacao silenciosa para CRM.
Treinamento e consultoria
O codigo e a parte facil. O dificil e decidir o que nao coletar, como provar que a coleta era permitida, como revisar mudancas e como apagar saidas antigas. ClaudeCodeLab pode transformar isso em regras de CLAUDE.md, checks Playwright, logs CSV e aprovacoes humanas em treinamento e consultoria Claude Code.
Para comecar sozinho, use uma pagina, uma execucao e dados publicos. Nao aumente o volume antes de ter auditoria, comportamento de falha e revisao humana.
Resumo
Web scraping seguro com Claude Code comeca por limites, nao por seletores. Priorize APIs e sitemaps, use Fetch para paginas estaticas permitidas e reserve Playwright para paginas dinamicas proprias ou aprovadas. Registre sempre URL, horario, robots status e User-Agent.
Evite violar termos, coletar emails a cegas, burlar limites, aceitar falhas silenciosas de seletores, contornar protecoes ou despejar dados sensiveis. Claude Code acelera a mecanica, mas o fluxo so esta pronto quando uma pessoa consegue explicar finalidade, fonte, data e exclusao.
Ao testar este fluxo, Masa percebeu que limitar a primeira versao a uma pagina, uma linha CSV e um JSON de auditoria deixou a revisao muito mais simples. sourceUrl e fetchedAt foram especialmente uteis para explicar checks de preco e paginas proprias. Prototipos que comecaram com muitas paginas precisaram ser reescritos porque falhas de seletores e lacunas de politica eram dificeis de rastrear.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Workflow Obsidian para CLAUDE.md com Claude Code
Transforme notas de trabalho do Obsidian em notas operacionais CLAUDE.md para preservar contexto.
Claude Code Revenue CTA Routing: artigos para PDF, Gumroad e consultoria
Um fluxo com Claude Code para levar leitores ao PDF grátis, Gumroad ou consultoria conforme intenção.
Regras de handoff para equipes com Claude Code: evidências, permissões, rollback e receita
Formato prático para entregar trabalho do Claude Code com prova, permissões, rollback, PDF grátis, Gumroad e consultoria.