--- name: website-scraping description: Generic playbook for extracting structured data from any website — hotel prices, flight fares, e-commerce listings, real estate, jobs, competitor product catalogues, anything where the goal is to turn one or more URLs into clean records on disk. Use this skill whenever the user mentions scraping, harvesting, extracting, or pulling data from a website; names a specific site or competitor they want data from; asks how to handle JavaScript-rendered pages, Cloudflare blocks, bot detection, Turnstile challenges, or Playwright; wants to monitor prices over time; needs to automate a copy-paste research task; or says things like "just get the data from X" or "I need a script that grabs N from site Y". Covers recon, picking the lightest extraction tool that works, surviving anti-bot defences, and writing clean JSONL output with a run manifest. Scope is scraping only — landing data in a database, scheduling, and downstream pipeline work are explicitly out of scope and live in other tools. version: 1.0.0 author: moonlight-lupin license: MIT platforms: [linux, macos, windows] --- # Website scraping A workflow for turning websites into structured data. Bias toward the **lightest tool that works**: static HTML → JSON-in-page → reverse-engineered API → browser automation, in that order. Skipping recon and reaching straight for Playwright is the single most common way to waste hours. ## When to use This skill applies to any task shaped like "given a URL or a list of URLs, produce structured records". Examples: - Competitor price monitoring (hotels, flights, retail, rentals, student housing, SaaS) - E-commerce product catalogue extraction - Real-estate or job-listing harvesting - Reviews, ratings, availability calendars - One-off "pull this table into a spreadsheet" jobs Out of scope: database persistence, scheduling, dashboards, downstream normalisation against a fixed schema. This skill produces JSONL — what the caller does with it is their problem. ## Workflow Follow these steps in order. Most failures come from skipping step 1. ### 1. Frame the goal before touching code Pin down the answers in writing before opening an editor: - **What records do you want?** One row per *what*? Per product? Per (product × variant × date)? Per (hotel × room-type × check-in)? Be explicit — many sites force you to choose between "one row per listing card" and "one row per price-variant", and the choice cascades through the whole scraper. - **What fields per record?** Required vs nice-to-have. Mark anything the source might not publish on every record (sometimes-missing fields are normal, not a bug). - **Input shape**: a single URL? A list of URLs? A search query you need to execute first? A seed listing page you crawl from? - **Output shape**: this skill recommends **JSONL** (one JSON object per line, caller-defined keys) plus a sidecar manifest file. See "Output convention" below. - **Scale & cadence**: how many records, how often? This one answer drives every downstream cost decision. A one-off harvest of 100 pages and a daily refresh of 10,000 SKUs are different projects: the first just needs a polite `time.sleep`; the second has to justify proxy/managed-service economics (see step 4, Tier 5+). Pin down volume *and* repeat-frequency now, not after you've built a single-shot scraper that can't keep up. - **What's "ground truth"?** Pick one specific item you'll eyeball in the browser end-to-end to verify the scraper matches. If the user hasn't been explicit about any of these, ask. A 30-second clarification saves an hour of wrong-shape extraction. ### 2. Recon the site — pick the lightest tool Before writing the decision tree below, run two cheap sanity checks. Either can make the whole scraper unnecessary. **2.0a — Is the data already one tool-call away?** You may be running inside an agent runtime that already exposes a fetch/scrape/SERP capability — Hermes's `web_search` + `web_extract` tools, a built-in search, or a scraping MCP server (Bright Data, Firecrawl, etc.). For a *one-off, low-volume* job, calling a tool that already exists is the lightest path of all — lighter than writing any code. Check what's available before you open an editor. (For repeatable, version-controlled, or high-volume work, still write a script — you want something you can re-run, diff, and hand off.) **2.0b — Is this a hostile mega-platform?** A handful of sites — Amazon, LinkedIn, Instagram, TikTok, Facebook, YouTube, Zillow, Google Maps, Crunchbase, and similar — invest heavily in defeating DIY scraping and change their internals constantly. For these, hand-rolling a scraper is often poor ROI: it works for a week, then breaks. The lightest tool here may not be `urllib` at all — it's a commercial structured-data product (Bright Data's dataset/Web-Scraper APIs, Apify actors, etc.) that already maintains the extraction for that platform. **Surface this trade-off to the user** ("I can hand-roll this, but for $SITE a maintained data API will be more reliable and probably cheaper than the upkeep — your call") rather than silently grinding on a fragile custom scraper. If they want DIY anyway, proceed — but go in expecting the anti-bot ladder in step 4. If neither shortcut applies, recon normally. Open the target URL in a real browser with DevTools open. Walk this decision tree top to bottom; **stop at the first match**. The earlier you stop, the simpler and more robust the scraper. ``` Is the data you need visible in "View Source" (right-click → View Page Source)? ├─ YES → Static HTML. Use urllib / requests + an HTML parser. Done. │ └─ NO → Is there a