Write, debug, and tune Playwright specs with locator strategy, trace diagnosis, and CI-aware timeouts.
Browser
Scrapfly Crawler
Try itCrawl entire websites using the Scrapfly Crawler API with the Python SDK
What it does
Use the Scrapfly Crawler API via the Python SDK to schedule and manage site-wide crawl jobs with automatic link discovery, depth control, and structured data extraction.
The skill document
Scrapfly Crawler
Use the Scrapfly Crawler API via the Python SDK to schedule and manage site-wide crawl jobs with automatic link discovery, depth control, and structured data extraction.
When to use
- Crawling entire websites or specific sections
- Discovering all pages/URLs on a site
- Collecting content from multiple pages in bulk
- Extracting structured data at scale across a site
- Downloading site archives (WARC/HAR format)
- Building LLM.txt files from crawled documentation
Setup
pip install scrapfly-sdk
The API key must be provided via environment variable SCRAPFLY_API_KEY or passed directly to the client.
SDK Imports
from scrapfly import ScrapflyClient, CrawlerConfig, Crawl
Additional imports for webhooks:
from scrapfly import (
webhook_from_payload,
CrawlStartedWebhook,
CrawlUrlDiscoveredWebhook,
CrawlUrlFailedWebhook,
CrawlCompletedWebhook,
)
CrawlerConfig Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
url | str | required | Starting URL for the crawl |
page_limit | int | None | Maximum number of pages to crawl |
max_depth | int | None | Maximum link traversal depth from start URL |
max_duration | int | None | Maximum crawl duration in seconds |
exclude_paths | list[str] | None | URL path patterns to skip (supports *, max 100). Mutually exclusive with include_only_paths |
include_only_paths | list[str] | None | Only crawl URLs matching these path patterns (supports *, max 100). Mutually exclusive with exclude_paths |
ignore_base_path_restriction | bool | False | Allow crawling any path on the same domain, not just the seed URL's base path |
follow_external_links | bool | False | Follow links to external domains |
allowed_external_domains | list[str] | None | Whitelist of external domains when follow_external_links=True (supports *, max 250) |
headers | dict | None | Custom HTTP headers as JSON object |
delay | int | None | Delay between requests in milliseconds (0-15000) |
user_agent | str | None | Custom User-Agent string (ignored when asp=True) |
max_concurrency | int | None | Maximum concurrent crawl requests |
rendering_delay | int | None | Wait time in ms after page load before extraction (0-25000). 0 disables browser rendering |
use_sitemaps | bool | False | Discover URLs from sitemap.xml when available |
respect_robots_txt | bool | False | Respect robots.txt rules and Disallow directives |
ignore_no_follow | bool | False | Ignore rel="nofollow" attributes on links |
cache | bool | False | Enable cache layer for crawled pages |
cache_ttl | int | None | Cache time-to-live in seconds (0-604800) |
cache_clear | bool | False | Force refresh of cached pages |
content_formats | list[str] | None | Content formats to extract: html, clean_html, markdown, text |
extraction_rules | dict | None | Custom extraction rules for structured data |
asp | bool | False | Enable Anti Scraping Protection with browser rendering |
proxy_pool | str | None | Proxy pool to use (e.g., public_residential_pool) |
country | str | None | Proxy country selection (ISO code) |
webhook_name | str | None | Name of webhook configured in dashboard |
webhook_events | list[str] | None | Events to subscribe to (e.g., crawler_started, crawler_url_visited, crawler_finished) |
max_api_credit | int | None | Maximum API credits to spend on this crawl |
Webhook Events
"crawler_started"- Crawler execution began"crawler_url_visited"- A URL was successfully crawled"crawler_url_skipped"- URLs were skipped"crawler_url_discovered"- New URLs were discovered"crawler_url_failed"- A URL failed to crawl"crawler_stopped"- Crawler stopped due to failure or limits"crawler_cancelled"- Crawl was manually cancelled"crawler_finished"- Crawler completed successfully
Examples
Quick start: crawl and get results
import os
from scrapfly import ScrapflyClient, CrawlerConfig, Crawl
client = ScrapflyClient(key=os.environ["SCRAPFLY_API_KEY"])
# Create, start, and wait for crawl in one chain
# 2. Create and run crawler
crawl = Crawl(
client,
CrawlerConfig(url='https://web-scraping.dev/products', page_limit=5)
).crawl().wait()
# 3. Get results
pages = crawl.warc().get_pages()
# 4. Process results
print(f"Crawled {len(pages)} pages:")
for page in pages:
print(f" • {page['url']} ({page['status_code']})")
# 5. Access specific URLs
html = crawl.read('https://web-scraping.dev/products')
if html:
print(f"\nMain page is {len(html):,} bytes")
Low-level workflow: start, poll, download
import os
import time
from scrapfly import ScrapflyClient, CrawlerConfig
client = ScrapflyClient(key=os.environ["SCRAPFLY_API_KEY"])
config = CrawlerConfig(
url='https://web-scraping.dev',
page_limit=10,
max_depth=2,
content_formats=['html', 'markdown']
)
# 1. Start crawler
start_response = client.start_crawl(config)
print(f"Crawler started: {start_response.uuid}")
# 2. Poll for status
while True:
status = client.get_crawl_status(start_response.uuid)
print(f"Status: {status.status} - {status.progress_pct:.1f}%")
print(f"Crawled: {status.urls_crawled}/{status.urls_discovered} pages")
if status.is_complete:
break
elif status.is_failed:
print("Crawl failed!")
break
time.sleep(5)
# 3. Download and process results
if status.is_complete:
artifact = client.get_crawl_artifact(start_response.uuid)
pages = artifact.get_pages()
print(f"Downloaded {len(pages)} pages")
for page in pages:
print(f" {page['url']}: {page['status_code']} ({len(page['content'])} bytes)")
# Save to file
artifact.save('crawl_results.warc.gz')
Crawl with path filtering
config = CrawlerConfig(
url='https://web-scraping.dev/',
page_limit=50,
max_depth=5,
include_only_paths=['*/product/*'],
content_formats=['markdown', 'text'],
)
crawl = Crawl(client, config).crawl().wait()
Crawl with anti-bot bypass
config = CrawlerConfig(
url='https://web-scraping.dev',
page_limit=50,
asp=True,
content_formats=['html'],
)
crawl = Crawl(client, config).crawl().wait()
Crawl with extraction rules
config = CrawlerConfig(
url='https://web-scraping.dev/products',
page_limit=100,
max_depth=3,
include_only_paths=['/products/*'],
extraction_rules={
"/products/*": {
"type": "model",
"value": "product",
},
},
)
crawl = Crawl(client, config).crawl().wait()
Read content for specific URLs
# Read HTML (from WARC - fast)
content = crawl.read('https://web-scraping.dev/product/1')
if content:
print(f"URL: {content.url}")
print(f"Status: {content.status_code}")
print(f"Duration: {content.duration}s")
print(content.content)
# Read markdown (from contents API)
content = crawl.read('https://web-scraping.dev/product/1', format='markdown')
if content:
print(content.content)
Iterate URLs matching a pattern
for content in crawl.read_iter(pattern="*/products?page=*", format="markdown"):
print(f"{content.url}: {len(content.content)} chars")
Batch content retrieval
# Get markdown for multiple URLs in a single API call (max 100 per request)
urls = ['https://web-scraping.dev/product/1', 'https://web-scraping.dev/product/2']
contents = crawl.read_batch(urls, formats=['markdown'])
for url, formats_dict in contents.items():
markdown = formats_dict.get('markdown', '')
print(f"{url}: {len(markdown)} chars")
Download WARC/HAR archive
# WARC format (default)
artifact = crawl.warc()
artifact.save('crawl_results.warc.gz')
# HAR format
artifact = crawl.har()
artifact.save('crawl_results.har')
# Iterate through records
for record in artifact.iter_responses():
print(f"{record.url}: {record.status_code}")
Build LLM.txt from crawled documentation
import os
from scrapfly import ScrapflyClient, CrawlerConfig, Crawl
client = ScrapflyClient(key=os.environ["SCRAPFLY_API_KEY"])
config = CrawlerConfig(
url='https://web-scraping.dev/',
include_only_paths=['/docs', '/docs/*'], # only allow doc resources
page_limit=50,
max_depth=5,
follow_external_links=False,
content_formats=['markdown'],
)
crawl = Crawl(client, config).crawl().wait(poll_interval=5, verbose=True)
# Get URLs from WARC
warc_artifact = crawl.warc()
all_urls = [
record.url for record in warc_artifact.iter_responses()
if record.status_code == 200
]
# Retrieve markdown content in batches
all_contents = {}
for i in range(0, len(all_urls), 100):
batch_urls = all_urls[i:i + 100]
batch_contents = crawl.read_batch(batch_urls, formats=['markdown'])
all_contents.update(batch_contents)
# Build llms-full.txt
lines = ["# Documentation\n"]
for url, formats_dict in all_contents.items():
markdown = formats_dict.get('markdown', '').strip()
if markdown:
lines.append(f"---\n\n### {url}\n\n{markdown}\n")
with open("llms-full.txt", "w", encoding="utf-8") as f:
f.write("\n".join(lines))
Crawl statistics
stats = crawl.stats()
print(f"URLs discovered: {stats['urls_discovered']}")
print(f"URLs crawled: {stats['urls_crawled']}")
print(f"Progress: {stats['progress_pct']:.1f}%")
Key SDK Classes
| Class | Description |
|---|---|
ScrapflyClient | Main client - handles auth and API calls |
CrawlerConfig | Crawl job configuration |
Crawl | High-level crawl lifecycle manager (start, wait, read) |
CrawlerStartResponse | Response from client.start_crawl() |
CrawlerStatusResponse | Response from client.get_crawl_status() - has is_complete, is_failed, progress_pct |
CrawlerArtifactResponse | Response from client.get_crawl_artifact() - has get_pages(), iter_responses(), save() |
CrawlContent | Content for a single URL - has url, content, status_code, duration |
Important Notes
- Use
page_limitto control credit spend on large sites - Use
include_only_pathsandexclude_pathsto focus crawls on relevant sections exclude_pathsandinclude_only_pathsare mutually exclusive- Crawl jobs are asynchronous - use
Crawl.crawl().wait()for simple workflows or poll withclient.get_crawl_status()for more control - WARC/HAR artifacts are available for offline processing via
crawl.warc()/crawl.har() read_batch()supports up to 100 URLs per request for efficient bulk content retrieval- Webhook signatures should be verified for security in production
follow_external_linksshould be used carefully to avoid crawling the entire web
Related skills
Fetch raw ad creative, app, ranking, and revenue data from AdMapix as structured JSON.
Stores durable facts in a categorized, plain-markdown vault on disk, alongside your agent's built-in memory.
Generate and edit Draw.io, Mermaid, and Excalidraw diagrams from natural language using a structured JSON spec.
Join a video meeting as an AI bot with voice, avatar, and screenshare across four operating modes.
Find why your productivity system keeps failing, then apply the smallest fix — capacity math, bottleneck routing, durable local notes.
More from scrapfly
Browse all skillsWeb scraping using the Scrapfly Scraper API with the Python SDK
Cross-tool golden rules for an autonomous Scrapfly web agent connected to the Scrapfly MCP server (web_scrape, screenshot, extract, classify_block, the Cloud...
Threshold-based alerting on Scrapfly account metrics. Wire up rules like "alert me when scrape success rate drops below 95% for 10 minutes" and receive notif...
Automate cloud browsers using the Scrapfly Cloud Browser API with Python Playwright
Use the Scrapfly CLI (`scrapfly`) to scrape web pages, capture screenshots, extract structured data with AI, crawl entire sites, and drive a cloud browser ov...
Extract structured data from web content using the Scrapfly Extraction API with the Python SDK