mcpField
S
scrapfly-crawler

scrapfly-crawler

github.com/scrapflyData & databases5233 installsUpdated 2w ago
Repository

About scrapfly-crawler

scrapfly-crawler is a Python SDK skill for scheduling and managing Scrapfly site crawls. It helps developers discover pages, extract content in bulk, and retrieve crawl artifacts.

Key features

  1. 01Configure crawl depth and page limits
  2. 02Filter URLs by path patterns
  3. 03Extract HTML, Markdown, or text
  4. 04Apply structured extraction rules
  5. 05Retrieve content and WARC/HAR archives
  6. 06Track jobs with polling and webhooks

Use cases

  1. 01Crawl a site or selected sections
  2. 02Collect content across many pages
  3. 03Extract structured data across a site
  4. 04Build LLM.txt from documentation

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/scrapfly/skills --skill scrapfly-crawler

After installing, type/scrapfly-crawlerin Claude Code to confirm it loaded.

Details

From the project README

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

ParameterTypeDefaultDescription
urlstrrequiredStarting URL for the crawl
page_limitintNoneMaximum number of pages to crawl
max_depthintNoneMaximum link traversal depth from start URL
max_durationintNoneMaximum crawl duration in seconds
exclude_pathslist[str]NoneURL path patterns to skip (supports *, max 100). Mutually exclusive with include_only_paths
include_only_pathslist[str]NoneOnly crawl URLs matching these path patterns (supports *, max 100). Mutually exclusive with exclude_paths
ignore_base_path_restrictionboolFalseAllow crawling any path on the same domain, not just the seed URL's base path
follow_external_linksboolFalseFollow links to external domains
allowed_external_domainslist[str]NoneWhitelist of external domains when follow_external_links=True (supports *, max 250)
headersdictNoneCustom HTTP headers as JSON object
delayintNoneDelay between requests in milliseconds (0-15000)
user_agentstrNoneCustom User-Agent string. On proxy_pool="unblocker", "unblocker_ultra" or "auto" the Unblocker stays on and replaces a Chrome User-Agent, any other is sent as is
max_concurrencyintNoneMaximum concurrent crawl requests
rendering_delayintNoneWait time in ms after page load before extraction (0-25000). 0 disables browser rendering
use_sitemapsboolFalseDiscover URLs from sitemap.xml when available
respect_robots_txtboolFalseRespect robots.txt rules and Disallow directives
ignore_no_followboolFalseIgnore rel="nofollow" attributes on links
cacheboolFalseEnable cache layer for crawled pages
cache_ttlintNoneCache time-to-live in seconds (0-604800)
cache_clearboolFalseForce refresh of cached pages
content_formatslist[str]NoneContent formats to extract: html, clean_html, markdown, text
extraction_rulesdictNoneCustom extraction rules for structured data
unblockerboolFalseDeprecated: use proxy_pool ("unblocker_ultra" for the Unblocker). The migration guide covers requests written before the proxy pools. asp is its alias; when both are supplied, asp wins
proxy_poolstrNoneProxy pool of every crawled page: "basic" (datacenter proxies, Unblocker off), "unblocker" (residential proxies and the Unblocker), "unblocker_ultra" (the same, and the only pool a target or an anti-bot protection with a price of its own accepts) or "auto" (the API picks the pool before each request, from the target's profile: the Unblocker on datacenter proxies, or the pool the target needs; accepted on every target, the ones that require unblocker_ultra included, where their price is added). Each pool sets the Unblocker itself. Unset, the SDK sends no proxy_pool. The migration guide covers the requests written before the proxy pools, the deprecated pool names public_datacenter_pool and public_residential_pool included
countrystrNoneProxy country selection (ISO code)
webhook_namestrNoneName of webhook configured in dashboard
webhook_eventslist[str]NoneEvents to subscribe to (e.g., crawler_started, crawler_url_visited, crawler_finished)
max_api_creditintNoneMaximum 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,
    proxy_pool='unblocker',  # residential proxies and the Unblocker on every page
    content_formats=['html'],
)
crawl = Crawl(client, config).crawl().wait()

On a target or an anti-bot protection with a price of its own, basic and unblocker fail each page with ERR::PROXY::UNBLOCKER_ULTRA_REQUIRED (HTTP 422, not billed, not retried; a refused seed URL ends the crawl with seed_url_failed): run the crawl again with proxy_pool='unblocker_ultra'.

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

ClassDescription
ScrapflyClientMain client - handles auth and API calls
CrawlerConfigCrawl job configuration
CrawlHigh-level crawl lifecycle manager (start, wait, read)
CrawlerStartResponseResponse from client.start_crawl()
CrawlerStatusResponseResponse from client.get_crawl_status() - has is_complete, is_failed, progress_pct
CrawlerArtifactResponseResponse from client.get_crawl_artifact() - has get_pages(), iter_responses(), save()
CrawlContentContent for a single URL - has url, content, status_code, duration

Important Notes

  • Use page_limit to control credit spend on large sites
  • Each page is billed at its proxy pool's price: 1 credit on basic, 25 on the Unblocker pools, +5 with browser rendering; unblocker_ultra adds the target's or the protection's own price where it has one; auto is priced as the pool it runs on
  • Use include_only_paths and exclude_paths to focus crawls on relevant sections
  • exclude_paths and include_only_paths are mutually exclusive
  • Crawl jobs are asynchronous - use Crawl.crawl().wait() for simple workflows or poll with client.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_links should be used carefully to avoid crawling the entire web

Comments

Markdown supported