Web Scraper
Extracts structured data from any website — handling static HTML, JavaScript-rendered pages, pagination, authentication walls, and rate limiting — and returns clean, structured output ready for downstream processing.
When to Use
- User wants to extract product listings, prices, or reviews from an e-commerce site
- User needs to harvest article text, metadata, or links from a news or blog site
- A dataset must be built by collecting structured records across many pages
- User asks to "scrape", "crawl", "extract data from", or "harvest" a URL
- An automated pipeline needs to pull live data from a website on a schedule
- User needs to monitor a page for changes and capture diffs over time
Process
Analyze the target URL and content type:
- Determine whether the page is static HTML or JavaScript-rendered (check for
<script> tags that load content dynamically)
- Identify the data schema: what fields are needed and where do they appear in the DOM?
- Check for a
robots.txt and note any disallowed paths or crawl delays
Select the extraction strategy:
- Static HTML: Use
requests + BeautifulSoup (Python) or cheerio (Node.js)
- JavaScript-rendered: Use
Playwright or Puppeteer to render the page before parsing
- APIs hiding behind the UI: Inspect network traffic (XHR/fetch calls) — hitting the API directly is faster and more reliable
- Structured data already present: Check for JSON-LD (
<script type="application/ld+json">), Open Graph tags, or schema.org microdata
Handle pagination and navigation:
- Detect pagination pattern: URL query parameter (
?page=2), path segment (/page/2), infinite scroll, or "Load More" button
- For query-parameter pagination: iterate pages until response is empty or a sentinel value is reached
- For infinite scroll: scroll the viewport incrementally with Playwright, waiting for new content to load
Implement rate limiting and politeness:
- Respect
Crawl-delay from robots.txt; default to 1–2 seconds between requests if none specified
- Randomize delay within ±50% of the target to avoid fingerprinting
- Use rotating user-agent strings drawn from a realistic pool
- Back off exponentially on 429 or 503 responses (2s → 4s → 8s → give up after 3 retries)
Extract and normalize fields:
- Use CSS selectors or XPath to target each field precisely
- Strip HTML entities, extra whitespace, and boilerplate text
- Normalize dates to ISO 8601, prices to a
{amount, currency} object, and URLs to absolute form
Handle errors and gaps:
- Log missing fields per record rather than dropping the entire record
- Detect CAPTCHAs or login walls and surface them as explicit errors with remediation advice
- Validate extracted records against the expected schema before returning
Return structured output:
- Default format: JSON array of objects (one per scraped record)
- Include scrape metadata:
source_url, scraped_at (ISO 8601), page_count, record_count
Output Format
{
"metadata": {
"source_url": "https://example.com/products",
"scraped_at": "2025-06-01T14:32:00Z",
"page_count": 5,
"record_count": 120
},
"records": [
{
"title": "Wireless Headphones Pro",
"price": { "amount": 79.99, "currency": "USD" },
"rating": 4.5,
"review_count": 312,
"url": "https://example.com/products/wireless-headphones-pro"
}
]
}
Examples
Example Input
Scrape all product names and prices from https://books.toscrape.com — there are multiple pages.
Example Output
import requests
from bs4 import BeautifulSoup
import time, random, json
from datetime import datetime, timezone
BASE_URL = "https://books.toscrape.com/catalogue/"
records = []
page = 1
while True:
url = f"{BASE_URL}page-{page}.html" if page > 1 else "https://books.toscrape.com/catalogue/"
resp = requests.get(url, headers={"User-Agent": "Mozilla/5.0"})
if resp.status_code == 404:
break
soup = BeautifulSoup(resp.text, "html.parser")
for article in soup.select("article.product_pod"):
records.append({
"title": article.h3.a["title"],
"price": article.select_one(".price_color").text.strip(),
"url": "https://books.toscrape.com/catalogue/" + article.h3.a["href"]
})
page += 1
time.sleep(random.uniform(0.8, 1.5))
print(json.dumps({
"metadata": {"scraped_at": datetime.now(timezone.utc).isoformat(), "record_count": len(records)},
"records": records
}, indent=2))
Boundaries
- Always check
robots.txt before scraping and respect Disallow rules and Crawl-delay.
- Do NOT bypass authentication mechanisms or scrape content behind a login without explicit user confirmation that they are authorized to do so.
- Do NOT extract personally identifiable information (PII) unless the user explicitly needs it and confirms they have legal authority to collect it.
- Do NOT ignore HTTP 429/503 responses — always back off before retrying.
- Do NOT scrape at rates that could constitute a denial-of-service — cap at one request per second by default.
- Flag CAPTCHA walls as an explicit blocker; do NOT attempt to solve CAPTCHAs automatically.
- Remind users that scraping may violate a site's Terms of Service — this is their responsibility to verify.
1---2name: web-scraper3description: Extracts structured data from websites, handling pagination, JavaScript rendering, and rate limiting. Invoke when asked to scrape a website, extract data from web pages, crawl URLs, harvest structured content, or automate web data collection.4---56# Web Scraper78Extracts structured data from any website — handling static HTML, JavaScript-rendered pages, pagination, authentication walls, and rate limiting — and returns clean, structured output ready for downstream processing.910## When to Use1112- User wants to extract product listings, prices, or reviews from an e-commerce site13- User needs to harvest article text, metadata, or links from a news or blog site14- A dataset must be built by collecting structured records across many pages15- User asks to "scrape", "crawl", "extract data from", or "harvest" a URL16- An automated pipeline needs to pull live data from a website on a schedule17- User needs to monitor a page for changes and capture diffs over time1819## Process20211. **Analyze the target URL and content type**:22 - Determine whether the page is static HTML or JavaScript-rendered (check for `<script>` tags that load content dynamically)23 - Identify the data schema: what fields are needed and where do they appear in the DOM?24 - Check for a `robots.txt` and note any disallowed paths or crawl delays25262. **Select the extraction strategy**:27 - **Static HTML**: Use `requests` + `BeautifulSoup` (Python) or `cheerio` (Node.js)28 - **JavaScript-rendered**: Use `Playwright` or `Puppeteer` to render the page before parsing29 - **APIs hiding behind the UI**: Inspect network traffic (XHR/fetch calls) — hitting the API directly is faster and more reliable30 - **Structured data already present**: Check for JSON-LD (`<script type="application/ld+json">`), Open Graph tags, or schema.org microdata31323. **Handle pagination and navigation**:33 - Detect pagination pattern: URL query parameter (`?page=2`), path segment (`/page/2`), infinite scroll, or "Load More" button34 - For query-parameter pagination: iterate pages until response is empty or a sentinel value is reached35 - For infinite scroll: scroll the viewport incrementally with Playwright, waiting for new content to load36374. **Implement rate limiting and politeness**:38 - Respect `Crawl-delay` from `robots.txt`; default to 1–2 seconds between requests if none specified39 - Randomize delay within ±50% of the target to avoid fingerprinting40 - Use rotating user-agent strings drawn from a realistic pool41 - Back off exponentially on 429 or 503 responses (2s → 4s → 8s → give up after 3 retries)42435. **Extract and normalize fields**:44 - Use CSS selectors or XPath to target each field precisely45 - Strip HTML entities, extra whitespace, and boilerplate text46 - Normalize dates to ISO 8601, prices to a `{amount, currency}` object, and URLs to absolute form47486. **Handle errors and gaps**:49 - Log missing fields per record rather than dropping the entire record50 - Detect CAPTCHAs or login walls and surface them as explicit errors with remediation advice51 - Validate extracted records against the expected schema before returning52537. **Return structured output**:54 - Default format: JSON array of objects (one per scraped record)55 - Include scrape metadata: `source_url`, `scraped_at` (ISO 8601), `page_count`, `record_count`5657## Output Format5859```json60{61 "metadata": {62 "source_url": "https://example.com/products",63 "scraped_at": "2025-06-01T14:32:00Z",64 "page_count": 5,65 "record_count": 12066 },67 "records": [68 {69 "title": "Wireless Headphones Pro",70 "price": { "amount": 79.99, "currency": "USD" },71 "rating": 4.5,72 "review_count": 312,73 "url": "https://example.com/products/wireless-headphones-pro"74 }75 ]76}77```7879## Examples8081### Example Input82```83Scrape all product names and prices from https://books.toscrape.com — there are multiple pages.84```8586### Example Output87```python88import requests89from bs4 import BeautifulSoup90import time, random, json91from datetime import datetime, timezone9293BASE_URL = "https://books.toscrape.com/catalogue/"94records = []95page = 19697while True:98 url = f"{BASE_URL}page-{page}.html" if page > 1 else "https://books.toscrape.com/catalogue/"99 resp = requests.get(url, headers={"User-Agent": "Mozilla/5.0"})100 if resp.status_code == 404:101 break102 soup = BeautifulSoup(resp.text, "html.parser")103 for article in soup.select("article.product_pod"):104 records.append({105 "title": article.h3.a["title"],106 "price": article.select_one(".price_color").text.strip(),107 "url": "https://books.toscrape.com/catalogue/" + article.h3.a["href"]108 })109 page += 1110 time.sleep(random.uniform(0.8, 1.5))111112print(json.dumps({113 "metadata": {"scraped_at": datetime.now(timezone.utc).isoformat(), "record_count": len(records)},114 "records": records115}, indent=2))116```117118## Boundaries119120- Always check `robots.txt` before scraping and respect `Disallow` rules and `Crawl-delay`.121- Do NOT bypass authentication mechanisms or scrape content behind a login without explicit user confirmation that they are authorized to do so.122- Do NOT extract personally identifiable information (PII) unless the user explicitly needs it and confirms they have legal authority to collect it.123- Do NOT ignore HTTP 429/503 responses — always back off before retrying.124- Do NOT scrape at rates that could constitute a denial-of-service — cap at one request per second by default.125- Flag CAPTCHA walls as an explicit blocker; do NOT attempt to solve CAPTCHAs automatically.126- Remind users that scraping may violate a site's Terms of Service — this is their responsibility to verify.