Sicheres Web Scraping mit Claude Code: Fetch, Playwright und Audit-Logs
Sicheres Web Scraping mit Claude Code: robots.txt, Fetch, Playwright, CSV-Ausgabe und Audit-Logs.
Erst Grenzen festlegen, dann Code erzeugen
Web Scraping bedeutet, Informationen aus Webseiten per Software zu lesen. Das kann sinnvoll sein, um die eigene Website zu ueberwachen, oeffentliche Dokumentationsseiten zu inventarisieren, veroeffentlichte Preisangaben manuell zu pruefen oder Content-Qualitaet zu sichern. Eine oeffentliche Seite heisst aber nicht, dass alles beliebig gesammelt werden darf. Claude Code beschleunigt die Implementierung, deshalb muss die Grenze vor dem ersten Code klar sein: nur oeffentliche Daten, Nutzungsbedingungen und robots.txt respektieren, Anfragen drosseln, keine personenbezogenen Daten ohne tragfaehige Grundlage sammeln und jeden Lauf auditierbar protokollieren.
Die sichere Reihenfolge fuer Einsteiger ist: zuerst offizielle API, RSS, Sitemap, CSV-Export oder einen anderen dokumentierten Datenzugang suchen. Diese Quellen sind stabiler als HTML und ihre Nutzungsregeln sind klarer. HTML-Scraping kommt erst danach, wenn der Zweck legitim, der Umfang klein und keine bessere strukturierte Quelle vorhanden ist.
Dieser Artikel nutzt Claude Code als Implementierungshelfer, nicht als Werkzeug zum Umgehen von Grenzen. Login-Bypass, CAPTCHA-Umgehung, Anti-Bot-Bypass, massenhaftes Sammeln von E-Mails oder Zugriff auf eingeschraenkte Daten gehoeren nicht dazu. Wenn personenbezogene Daten, Vertriebskontakte oder regulierte Informationen beruehrt werden, klaeren Sie Zweck, Rechtsgrundlage, Datenschutzhinweis, Aufbewahrungsfrist, Opt-out und lokale Compliance zuerst.
Nutzen Sie offizielle Quellen: RFC 9309 fuer robots.txt, Google robots.txt Dokumentation, MDN Fetch API und Playwright Browser contexts.
Empfohlener Ablauf
flowchart TD
A["Ein klarer Zweck"] --> B["API, RSS und Sitemap pruefen"]
B --> C["Terms und robots.txt lesen"]
C --> D{"Reicht statisches HTML?"}
D -->|Ja| E["Fetch seitenweise verwenden"]
D -->|Nein, erlaubt| F["Playwright fuer gerenderten DOM"]
E --> G["CSV mit URL und Zeit"]
F --> G
G --> H["Menschliche Stichprobe vor Nutzung"]
So wird auch die Aufgabe an Claude Code praezise. Nicht “scrape diese Website”, sondern “hole eine einzelne Seite von diesem erlaubten Origin, warte mindestens zwei Sekunden, stoppe bei robots.txt-Blockade und schreibe sourceUrl und fetchedAt in die CSV”. Je klarer die Grenze, desto seltener entstehen aggressive Loops, bruechige Selektoren oder Daten, die nicht gespeichert werden sollten.
Fetch oder Playwright
Verwenden Sie fetch, wenn die benoetigte Information bereits in der HTML-Antwort steht. Statische Dokumentation, Blogartikel, oeffentliche Preislisten, Statusseiten und Sitemap-basierte Checks passen oft gut. Fetch ist leicht auditierbar: eine HTTP-Anfrage, dann Text. Es ist auch deutlich schlanker als ein Browser.
Playwright ist nur dann sinnvoll, wenn ein echter Browser erforderlich ist und die Seite Ihnen gehoert oder ausdruecklich fuer Automatisierung freigegeben wurde. Beispiele sind lokale Vorschau, Staging, interne QA oder Tests der eigenen Website. Browser-Automatisierung laedt Scripts, Cookies, localStorage, Berechtigungen und Client-Verhalten. Deshalb sollten Browser Contexts getrennt werden.
Lassen Sie Claude Code zuerst die Fetch-Version bauen. Playwright kommt nur dazu, wenn statisches HTML den Fall nicht loest. In der Review achten Sie auf feste Sleeps, versehentliche Login-Sessions, Selektoren auf Basis von Styling-Klassen, fehlendes Rate Limit und fehlende Quellmetadaten.
Praxisfaelle
Der erste Fall ist Monitoring der eigenen Website. Pruefen Sie Trainingsseiten, Produktseiten, Formulare, Artikel, canonical URLs, Titel und CTA-Texte. Weil die Website Ihnen gehoert, koennen Sie robots.txt, stabile Selektoren und eine angemessene Frequenz definieren. Das passt zu AI Content Operations und Content Funnel Audit: Das Script erkennt Veraenderungen, der Content-Prozess entscheidet ueber Korrekturen.
Der zweite Fall ist das Sammeln oeffentlicher Dokumentations-URLs. Ein Team kann offizielle Docs, ein internes Handbook oder eine oeffentliche Knowledge Base indexieren. Oft muss der Volltext nicht gespeichert werden. URL, Titel, Pruefzeit und Status reichen fuer Suche, Review und Redaktionsplanung.
Der dritte Fall ist manuell geprueftes Monitoring oeffentlicher Wettbewerber-Preislisten. Niedrigvolumige Checks koennen Aenderungen an Plan-Namen, Kampagnenhinweisen oder Seitenstruktur zeigen. Die Ausgabe ist aber keine automatische Wahrheit. Preise haengen von Region, Steuer, Waehrung und Bedingungen ab. Speichern Sie source URL und Zeit, und lassen Sie Stichproben menschlich pruefen.
Der vierte Fall ist Lead Research mit Schutzplanken. Firmenname, offizielle Website, Branche und oeffentliche Kontaktseite koennen in kleinem Umfang sinnvoll sein. Persoenliche E-Mail-Adressen blind zu sammeln und in Outbound-Kampagnen zu schicken, ist nicht akzeptabel. Wenn Outreach folgt, brauchen Sie Opt-out, Absenderidentitaet, Sperrlisten und menschliche Kontrolle. Verbinden Sie das erst danach mit Claude Code Email Automation.
Typische Fehler
Der haeufigste Fehler ist, robots.txt und Terms zu ignorieren. robots.txt ersetzt keine Rechtspruefung, ist aber eine veroeffentlichte technische Grenze. Terms koennen Automatisierung, Wiederverwendung oder kommerzielles Monitoring zusaetzlich einschraenken.
Ein weiterer Fehler ist das Speichern jeder gefundenen E-Mail-Adresse. Sichtbare personenbezogene Daten bleiben personenbezogene Daten. Wenn sie nicht noetig sind, sammeln Sie sie nicht. Wenn sie noetig sind, dokumentieren Sie Zweck, Grundlage, Aufbewahrung, Zugriff, Loeschung und Opt-out.
Kein Rate Limit ist ebenfalls riskant. Hunderte Anfragen ohne Pause koennen wie ein Angriff wirken. Kleine Batches, niedrige Frequenz, klarer User-Agent, begrenzte Retries und Stop-on-error sind Pflicht.
Bruechige Selektoren erzeugen leise Datenfehler. .card > div:nth-child(2) kann heute funktionieren und morgen brechen. Bevorzugen Sie semantisches HTML, time[datetime], main h1 oder eigene data-Attribute. Fehlt ein Pflichtselektor, soll der Job fehlschlagen und Diagnose schreiben.
Umgehung von Schutzmechanismen ist kein Feature. Wenn Claude Code CAPTCHA-Bypass, Login-Wall-Scraping, wechselnde Identitaeten oder Rate-Limit-Umgehung vorschlaegt, stoppen Sie und entwerfen Sie den Prozess mit einer erlaubten Quelle neu.
Speichern Sie auch keine sensiblen Rohdaten. Raw HTML, authentifizierte Daten, Tokens, Kundendaten und personenbezogene Datensaetze gehoeren nicht ungeprueft in CSV-Dateien. Fuer die breitere Absicherung lesen Sie Claude Code Security Best Practices.
Kopierbares Fetch-Beispiel
Dieses Script laeuft mit Node 18 oder neuer. Es liest genau eine erlaubte Seite, prueft robots.txt konservativ, drosselt die Anfrage, extrahiert Basisfelder und schreibt eine CSV mit sourceUrl und fetchedAt sowie eine JSON-Auditdatei.
// 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}`);
PowerShell-Beispiel: $env:SCRAPE_URL="https://your-domain.example/page"; $env:ALLOWED_ORIGINS="https://your-domain.example"; node scrape-allowed-page.mjs. Die Ausgabe ist bewusst schlicht: eine CSV-Zeile und eine JSON-Auditdatei.
Playwright nur fuer eigene Seiten
Dieses Playwright-Beispiel prueft gerenderte Selektoren auf der eigenen Website oder in einer lokalen Vorschau. Es ist nicht fuer das Umgehen fremder Schutzmechanismen gedacht.
// 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 trennen Cookies, localStorage und Berechtigungen. Verwenden Sie keine echte Login-Session, wenn Aufgabe und Datenverarbeitung nicht ausdruecklich freigegeben sind.
Prompt fuer Claude Code
Ein sicherer Prompt:
Fuege einen One-Page-Scraper fuer einen allowlisted Origin hinzu. Dokumentiere zuerst, ob offizielle API, RSS oder Sitemap existieren. Pruefe robots.txt vor dem HTML-Fetch. Die CSV muss
sourceUrlundfetchedAtenthalten. Sammle keine E-Mails, Personennamen, authentifizierten Daten oder Secrets. Umgehe keine CAPTCHA, Login Walls, Bot Protection oder Rate Limits. Fuege Throttling und Stop bei blockierten Pfaden hinzu und zeigenode --checkfuer die JavaScript-Dateien.
Claude Code soll auditierbar implementieren, nicht die rechtliche Grenze fuer Sie entscheiden. Vor geplanter Ausfuehrung prueft ein Mensch Diff, Ziel-URLs, gespeicherte Felder, Frequenz, Loeschprozess und Beispielausgabe.
Betriebscheckliste
- API, RSS, Sitemap oder Export vor HTML pruefen.
- Sicherstellen, dass die Seite oeffentlich ist und der Zweck zu den Terms passt.
- robots.txt respektieren und protokollieren.
- Origin-Allowlist und kleine Batches nutzen.
- Delay, begrenzte Retries und Stop-on-error einbauen.
- Klaren User-Agent verwenden.
- Source URL, Fetch-Zeit, Methode und robots status speichern.
- Semantische Selektoren oder eigene data-Attribute bevorzugen.
- Keine personenbezogenen Daten, Secrets, Sessions oder Raw HTML standardmaessig speichern.
- Stichproben vor Business-Entscheidungen menschlich pruefen.
Wenn CSV-Dateien in Tabellenkalkulationen geoeffnet werden, beachten Sie CSV Injection. Web-Inhalte sind nicht vertrauenswuerdig. Verbinden Sie den Prozess mit Security Review, Content Automation und Outreach-Kontrollen statt mit einem stillen CRM-Import.
Training und Beratung
Der Code ist der einfache Teil. Schwieriger ist zu entscheiden, was nicht gesammelt wird, wie die Erlaubnis nachweisbar ist, wie Aenderungen geprueft werden und wie alte Outputs geloescht werden. ClaudeCodeLab kann dies ueber Claude Code Training und Beratung in CLAUDE.md-Regeln, Playwright-Checks, CSV-Auditlogs und menschliche Freigaben uebersetzen.
Allein starten Sie am besten mit einer Seite, einem Lauf und oeffentlichen Daten. Erhoehen Sie die Menge erst, wenn Audit Trail, Fehlerverhalten und Review stehen.
Zusammenfassung
Sicheres Web Scraping mit Claude Code beginnt mit Grenzen, nicht mit Selektoren. Priorisieren Sie APIs und Sitemaps, verwenden Sie Fetch fuer statische erlaubte Seiten und Playwright nur fuer eigene oder genehmigte dynamische Seiten. Protokollieren Sie immer URL, Zeit, robots status und User-Agent.
Vermeiden Sie Terms-Verstoesse, blindes E-Mail-Sammeln, Rate-Limit-Umgehung, stille Selektorfehler, Schutzumgehung und Dumps sensibler Daten. Claude Code beschleunigt die Mechanik, aber produktionsreif ist der Ablauf erst, wenn ein Mensch Zweck, Quelle, Zeitpunkt und Loeschpfad erklaeren kann.
Masa hat diesen Ablauf praktisch getestet und festgestellt, dass eine erste Version mit nur einer Seite, einer CSV-Zeile und einer JSON-Auditdatei die Review stark vereinfacht. sourceUrl und fetchedAt waren besonders hilfreich, um Preischecks und eigene Seitenpruefungen spaeter zu erklaeren. Prototypen mit vielen Seiten mussten dagegen neu geschrieben werden, weil Selektorfehler und Policy-Luecken schwer zu verfolgen waren.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Workflow von Obsidian zu CLAUDE.md
Obsidian-Arbeitsnotizen in CLAUDE.md-Betriebsnotizen verwandeln und Kontext nicht ständig neu erklären.
Claude Code Revenue CTA Routing: Artikel zu PDF, Gumroad und Beratung führen
Ein Claude-Code-Ablauf, der Leser nach Absicht zu Gratis-PDF, Gumroad oder Beratung führt.
Claude-Code-Team-Handoff-Regeln: Belege, Berechtigungen, Rollback und Umsatzpfade
Ein praktisches Claude-Code-Handoff für Review-Belege, Berechtigungen, Rollback, Gratis-PDF, Gumroad und Beratung.