Skip to main content
A scraper is a Python class that knows how to find downloadable files on a public URL. You give it a URL; it returns a list of files with their download links, filenames, and dates. The base class handles the actual downloading — you only write the discovery logic.
Always test a scraper with test=True before running it for real. Test mode runs your discover_files() method and shows you what files it found, without downloading anything.

The SimpleScraper interface

Every scraper extends SimpleScraper and implements one method: discover_files(). It returns a list of dicts, each with four required keys: Dates must be in one of these ISO formats: YYYY, YYYY-MM, YYYY-MM-DD, or YYYY-QN (for example, "2024-Q1"). Non-ISO dates are rejected at discovery time.
The class name should follow the pattern {ScraperName}Scraper — for example, KaufmanHallScraper.

Capability tiers

Choose the simplest approach that works for your source. Simpler scrapers are faster, cheaper, and more reliable.

Direct HTTP

Use get_html() or get_json() to fetch pages directly. Works for most public data portals where files are linked in standard HTML.

AI browser automation

Use self.browser_task() to describe what you want to find. An AI agent navigates the site and returns structured data. Good for dynamic or JS-heavy sites without writing selectors.

Playwright browser

Set needs_browser = True to get self.page, a Playwright sync Page. Use when you need precise, deterministic browser control — clicking tabs, filling forms, waiting for elements.

Tier 1: Direct HTTP

The default — no extra configuration needed. get_html() returns a BeautifulSoup object; get_json() fetches and parses JSON.

Tier 2: AI browser automation

Call self.browser_task() with a plain-language description of what to find. Pass an output_schema to get back structured JSON. This works in any scraper — you don’t need needs_browser = True.

Tier 3: Playwright browser

Set needs_browser = True to get self.page — a Playwright sync Page that starts on self.url with bot protection already cleared. Use this when you need to click through tabs, wait for dynamic content, or interact with forms.

Available in the scraper namespace

These are available without importing anything: You can also define SCRAPER_HEADERS on your class to add or override request headers for every request this scraper makes (merged with DEFAULT_HEADERS):
Available for import: polars (use instead of pandas — pandas is not installed), requests, lxml, beautifulsoup4, pdfplumber, html5lib, xmltodict, tenacity, curl_cffi, openpyxl, xlrd, chardet, python-dateutil, and all Python standard library modules.

Testing and running a scraper

Always test before running for real.
1

Create a workspace

2

Test your scraper code

Test mode runs discover_files() and shows you the files found — no downloads happen.
The output shows how many files were found and previews the first five with their filenames, dates, and URLs.
3

Save the scraper code

Once the test output looks correct, save the code:
4

Run the scraper to download files

The output reports how many files were discovered, downloaded, grouped, and — for CSVs — column-mapped. For PDFs, it reports how many detection jobs were enqueued.
If a site blocks automated requests (Akamai, Cloudflare WAF), scraper_run may time out. In that case, use scraper_upload_urls to generate presigned upload URLs and provide the files manually, then confirm with scraper_confirm_uploads.

Render HTML pages as PDF

Some data sources only publish HTML tables with no downloadable files. Set render_as_pdf = True on your scraper and all discovered URLs will be rendered to PDF by a headless browser and fed into the extraction pipeline.