Web Scraping Aman dengan Claude Code: Fetch, Playwright, dan Audit Log
Bangun web scraping aman dengan Claude Code: robots.txt, Fetch, Playwright, CSV, dan audit log.
Tetapkan batas sebelum Claude Code menulis kode
Web scraping berarti membaca informasi dari halaman web dengan software. Ini berguna untuk memantau situs sendiri, mengumpulkan URL dokumentasi publik, mengecek halaman harga yang memang publik, atau melakukan QA konten. Namun halaman publik bukan berarti semua datanya boleh dikumpulkan tanpa batas. Claude Code membuat implementasi sangat cepat, jadi batasnya harus ditulis sejak awal: hanya data publik, hormati terms dan robots.txt, beri jeda antar-request, jangan kumpulkan data pribadi tanpa dasar yang sah, dan simpan audit log.
Urutan aman untuk pemula adalah mencari API resmi, RSS, sitemap, export CSV, atau jalur data terdokumentasi lebih dulu. Sumber seperti itu biasanya lebih stabil daripada HTML dan aturan penggunaannya lebih jelas. HTML scraping menjadi pilihan berikutnya, hanya jika tujuannya sah, volumenya kecil, dan tidak ada sumber terstruktur yang lebih baik.
Artikel ini memakai Claude Code sebagai asisten implementasi, bukan alat untuk melewati kontrol. Login bypass, CAPTCHA bypass, anti-bot evasion, panen email massal, atau pengambilan data terbatas tidak dibahas. Jika workflow menyentuh data pribadi, sales outreach, atau data yang diatur, periksa dulu tujuan, dasar hukum, privacy policy, masa retensi, opt-out, dan kewajiban lokal.
Gunakan referensi resmi: RFC 9309 untuk robots.txt, dokumentasi Google robots.txt, MDN Fetch API, dan Playwright Browser contexts.
Workflow yang disarankan
flowchart TD
A["Satu tujuan jelas"] --> B["Cek API, RSS, sitemap"]
B --> C["Baca terms dan robots.txt"]
C --> D{"HTML statis cukup?"}
D -->|Ya| E["Fetch satu halaman"]
D -->|Tidak, disetujui| F["Playwright untuk DOM render"]
E --> G["CSV berisi URL dan waktu"]
F --> G
G --> H["Review manusia sebelum dipakai"]
Dengan alur ini, instruksi ke Claude Code menjadi konkret. Jangan minta “scrape situs ini”. Minta “ambil satu halaman dari origin yang diizinkan, tunggu minimal dua detik, berhenti jika robots.txt memblokir path, dan tulis sourceUrl serta fetchedAt ke CSV”. Semakin jelas batasnya, semakin kecil risiko muncul loop agresif, selector rapuh, atau data yang tidak seharusnya disimpan.
Kapan memakai Fetch dan Playwright
Gunakan fetch jika informasi yang dibutuhkan sudah ada di response HTML. Dokumentasi statis, artikel, halaman harga publik, status page, dan cek URL dari sitemap biasanya cocok. Fetch mudah diaudit karena hanya membuat HTTP request dan membaca teks. Ia juga lebih ringan daripada membuka browser.
Gunakan Playwright hanya jika browser sungguhan diperlukan dan halaman itu milikmu atau sudah disetujui untuk otomatisasi. Contohnya local preview, staging, QA internal, atau pengecekan situs sendiri. Browser automation memuat scripts, cookies, localStorage, permissions, dan state client-side. Pisahkan browser contexts agar session tidak bercampur.
Minta Claude Code memulai dari versi Fetch. Tambahkan Playwright hanya setelah terbukti HTML statis tidak cukup. Saat review, cari fixed sleep yang berlebihan, session login yang tidak sengaja dipakai, selector berbasis class visual, rate limit yang hilang, dan metadata source yang tidak dicatat.
Use case praktis
Use case pertama adalah memantau situs sendiri. Periksa halaman training, produk, form, artikel, canonical URL, title, dan CTA text. Karena situsnya milikmu, kamu bisa mengatur robots.txt, selector stabil, dan frekuensi yang wajar. Hubungkan ini dengan AI content operations dan content funnel audit: scraper mendeteksi perubahan, workflow konten menentukan perbaikannya.
Use case kedua adalah mengumpulkan URL dokumentasi publik. Tim bisa membuat indeks docs resmi, handbook internal, atau knowledge base publik. Banyak kasus tidak memerlukan penyimpanan full text. URL, title, waktu pengecekan, dan status sudah cukup untuk search, review, atau editorial planning.
Use case ketiga adalah cek halaman harga publik kompetitor dengan review manual. Monitoring kecil bisa mendeteksi perubahan nama plan, pesan campaign, atau struktur halaman. Namun output tidak boleh otomatis menjadi kebenaran bisnis. Harga bisa berbeda karena region, pajak, mata uang, dan syarat promo. Simpan source URL dan timestamp, lalu minta manusia mengecek sampel.
Use case keempat adalah lead research dengan guardrail. Mengumpulkan nama perusahaan, website resmi, industri, dan halaman kontak publik dalam skala kecil bisa masuk akal. Yang tidak aman adalah memanen email personal lalu memasukkannya ke outbound campaign. Jika ada outreach, siapkan opt-out, identitas pengirim, suppression list, dan review manusia. Gabungkan dengan Claude Code email automation hanya setelah proses pengumpulan sah dan minimal.
Kesalahan umum
Kesalahan paling umum adalah mengabaikan robots.txt dan terms. robots.txt bukan analisis hukum penuh, tetapi ia adalah batas teknis yang dipublikasikan situs dan harus dihormati. Terms bisa menambahkan batas untuk otomatisasi, reuse, atau monitoring komersial.
Kesalahan kedua adalah menyimpan semua email yang terlihat. Data personal yang terlihat publik tetap bisa menjadi data personal. Jika tidak perlu, jangan kumpulkan. Jika perlu, dokumentasikan tujuan, dasar, retensi, akses, penghapusan, dan opt-out.
Tanpa rate limit, risikonya teknis dan reputasional. Ratusan request tanpa jeda bisa terlihat seperti serangan. Gunakan batch kecil, frekuensi rendah, User-Agent jelas, retry terbatas, dan stop-on-error.
Selector rapuh menciptakan kegagalan diam-diam. .card > div:nth-child(2) bisa bekerja hari ini dan rusak besok. Pilih HTML semantik, time[datetime], main h1, atau data attribute yang kamu kontrol. Jika selector wajib hilang, job harus gagal dan menulis diagnosis.
Melewati proteksi bukan fitur. Jika Claude Code menyarankan CAPTCHA workaround, login wall scraping, identity rotation, atau bypass rate limit, hentikan dan desain ulang memakai sumber yang disetujui.
Jangan simpan data sensitif secara default. Raw HTML, data authenticated, token, informasi pelanggan, dan record personal tidak boleh masuk CSV tanpa review. Untuk konteks keamanan lebih luas, baca Claude Code security best practices.
Contoh Fetch yang bisa dicopy
Script ini berjalan di Node 18 atau lebih baru. Ia mengambil satu halaman yang diizinkan, mengecek robots.txt secara konservatif, memberi delay, mengambil ringkasan halaman, lalu menulis CSV berisi sourceUrl dan fetchedAt serta JSON audit.
// 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}`);
Contoh PowerShell: $env:SCRAPE_URL="https://your-domain.example/page"; $env:ALLOWED_ORIGINS="https://your-domain.example"; node scrape-allowed-page.mjs. Output dibuat sederhana: satu baris CSV dan satu JSON audit file.
Playwright hanya untuk halaman milik sendiri
Contoh Playwright ini mengecek selector setelah render di situs milikmu atau local preview. Ini bukan alat untuk melewati proteksi situs lain.
// 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 memisahkan cookies, localStorage, dan permissions. Jangan arahkan automation ini ke session login nyata kecuali task disetujui dan pemrosesan datanya jelas.
Prompt untuk Claude Code
Prompt yang lebih aman:
Tambahkan scraper satu halaman untuk origin dalam allowlist. Pertama dokumentasikan apakah ada API resmi, RSS, atau sitemap. Cek robots.txt sebelum mengambil HTML. CSV wajib berisi
sourceUrldanfetchedAt. Jangan kumpulkan email, nama personal, data authenticated, atau secrets. Jangan bypass CAPTCHA, login wall, bot protection, atau rate limit. Tambahkan throttling, berhenti jika path diblokir, dan tampilkan hasilnode --checkuntuk file JavaScript.
Tujuannya adalah menjadikan Claude Code implementer yang bisa diaudit, bukan penentu batas legal. Sebelum dijadwalkan, manusia tetap harus review diff, target URL, field yang disimpan, cadence, proses penghapusan, dan sample output.
Checklist operasional
- Cari API, RSS, sitemap, atau export sebelum HTML.
- Pastikan halaman publik dan penggunaan sesuai terms.
- Hormati robots.txt dan catat hasilnya.
- Pakai origin allowlist dan batch kecil.
- Tambahkan delay, retry terbatas, dan stop-on-error.
- Gunakan User-Agent yang jelas.
- Simpan source URL, timestamp, metode, dan robots status.
- Pilih selector semantik atau data attribute milik sendiri.
- Jangan simpan data personal, secrets, session data, atau raw HTML secara default.
- Review sample secara manual sebelum dipakai untuk keputusan bisnis.
Jika CSV dibuka di spreadsheet, perhatikan CSV injection. Semua teks dari web adalah input tidak tepercaya. Hubungkan proses ini dengan security review, content automation, dan outreach control, bukan silent import ke CRM.
Training dan konsultasi
Kode adalah bagian mudah. Bagian sulit adalah menentukan apa yang tidak dikumpulkan, bagaimana membuktikan collection diizinkan, bagaimana meninjau perubahan, dan bagaimana menghapus output lama. ClaudeCodeLab dapat membantu mengubahnya menjadi aturan CLAUDE.md, Playwright checks, CSV audit logs, dan approval manusia lewat training dan konsultasi Claude Code.
Untuk mulai sendiri, gunakan satu halaman, satu run, dan data publik saja. Jangan naikkan volume sebelum audit trail, failure behavior, dan review step siap.
Ringkasan
Web scraping aman dengan Claude Code dimulai dari batas, bukan selector. Prioritaskan API dan sitemap, gunakan Fetch untuk halaman statis yang diizinkan, dan simpan Playwright untuk halaman dinamis milik sendiri atau yang disetujui. Selalu catat URL, waktu, robots status, dan User-Agent.
Yang harus dihindari juga jelas: jangan abaikan terms, jangan panen email buta, jangan bypass rate limit, jangan biarkan selector gagal diam-diam, jangan lewati proteksi, dan jangan dump data sensitif. Claude Code mempercepat mekanik, tetapi workflow siap dipakai hanya jika manusia bisa menjelaskan tujuan, sumber, waktu, dan jalur penghapusan.
Saat mencoba workflow ini, Masa menemukan bahwa versi awal yang dibatasi ke satu halaman, satu baris CSV, dan satu JSON audit file jauh lebih mudah direview. sourceUrl dan fetchedAt sangat membantu saat menjelaskan cek halaman harga dan konten situs sendiri. Prototype yang langsung mengumpulkan banyak halaman akhirnya perlu ditulis ulang karena kegagalan selector dan celah policy sulit dilacak.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Workflow Obsidian ke CLAUDE.md untuk Claude Code
Ubah catatan kerja Obsidian menjadi operating note CLAUDE.md agar konteks tidak dijelaskan ulang.
Claude Code Revenue CTA Routing: dari artikel ke PDF, Gumroad, dan konsultasi
Workflow Claude Code untuk mengarahkan pembaca ke PDF gratis, Gumroad, atau konsultasi sesuai intent.
Aturan handoff tim Claude Code: bukti review, permission, rollback, dan jalur revenue
Format handoff Claude Code untuk tim: bukti, permission rule, rollback, PDF gratis, Gumroad, dan konsultasi.