Use this skill when you need to extract structured data from websites — handling dynamic JavaScript content, pagination, login flows, or when you want Claude to intelligently identify the data to extract.
62
73%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Fix and improve this skill with Tessl
tessl review fix ./external/goose-recipes/web-scraper/SKILL.mdPlaywright-powered web scraper with Claude for intelligent data extraction. Handles JavaScript-heavy sites, pagination, and variable page structures. Respects robots.txt and rate limits.
pip install playwright anthropic
playwright install chromiumimport anthropic
import asyncio
from playwright.async_api import async_playwright
client = anthropic.Anthropic()
async def scrape_page(url: str, data_description: str) -> dict:
"""Scrape a page and use Claude to extract structured data."""
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
# Rate limit: be a good citizen
await asyncio.sleep(1)
await page.goto(url, wait_until="networkidle")
html = await page.content()
await browser.close()
# Let Claude extract the structured data
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=2048,
system="Extract structured data from HTML. Return only the requested data as JSON.",
messages=[{
"role": "user",
"content": f"""Extract: {data_description}
HTML (truncated to relevant sections):
{html[:8000]}
Return as JSON."""
}],
)
import json
try:
return json.loads(response.content[0].text)
except json.JSONDecodeError:
return {"raw": response.content[0].text}async def scrape_all_pages(base_url: str, data_description: str,
max_pages: int = 10) -> list[dict]:
all_results = []
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
current_url = base_url
pages_scraped = 0
while current_url and pages_scraped < max_pages:
await asyncio.sleep(1) # respect rate limits
await page.goto(current_url, wait_until="networkidle")
html = await page.content()
# Extract data from this page
page_data = extract_from_html(html, data_description)
all_results.extend(page_data if isinstance(page_data, list) else [page_data])
# Find next page link
next_link = await page.query_selector("a[rel='next'], .pagination .next, [aria-label='Next']")
if next_link:
current_url = await next_link.get_attribute("href")
if current_url and not current_url.startswith("http"):
from urllib.parse import urljoin
current_url = urljoin(base_url, current_url)
else:
current_url = None
pages_scraped += 1
await browser.close()
return all_resultsasync def extract_from_screenshot(url: str, question: str) -> str:
"""Use Claude's vision to extract from a screenshot."""
import base64
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page(viewport={"width": 1280, "height": 900})
await page.goto(url)
screenshot = await page.screenshot(full_page=False)
await browser.close()
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
messages=[{
"role": "user",
"content": [
{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": base64.b64encode(screenshot).decode()}},
{"type": "text", "text": question}
]
}],
)
return response.content[0].text| Approach | Best for |
|---|---|
| HTML extraction | Text content, tables, links |
| Screenshot + vision | Visual layouts, charts, images |
| Paginated scraper | Multi-page catalogs, search results |
| Rate limit | 1 req/sec minimum — be polite |
32f0050
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.