Skip to main content
The Batch Scraper API is in public beta, available to every ZenRows account. You need an active ZenRows API key. The ZenRowsBatchClient shown here is separate from the SDK’s synchronous scraping client, ZenRowsClient.
Use this guide to submit batch scraping jobs, track them, and collect results with the official ZenRows Python SDK. It walks through every Batch Scraper API feature, and each section runs on its own.
Prefer raw HTTP, or work in another language? The REST API guide covers the same use cases against the plain REST API. For the complete method reference, see the SDK repository on GitHub.

Install and Authenticate

While the batch client is being published to PyPI, install the SDK from the beta branch:
This git install is temporary. The batch client is not on PyPI yet; once it is published, pip install zenrows will include it.
Python
The client also works as a context manager (with ZenRowsBatchClient(...) as client: ...) when you want connections to close automatically. By default it targets the production Batch Scraper API; pass base_url=... (or set ZENROWS_BATCH_BASE_URL) to point at a different environment.

Quickstart: Submit, Wait, Get Results

This is the standard end-to-end flow. submit_regular returns a JobRef (no GET yet), job.run.wait() blocks until the current run reaches a terminal state and returns a RunHandle, and run.results(...) iterates the per-task results.
Python
job.run.wait() returns a RunHandle whose .data snapshot is ready, so run.results(...) and run.download_to_dir(...) work without re-passing IDs.
Pass progress=True to the waiter for a live progress bar on long batches. For very large submissions that return 202 Accepted, pass wait_for_ingest=True to submit_regular so results pages are complete before you read them.

Configuring the Scrape

zenrows_params sets scraping options for every URL in the job. Values can be strings, booleans, or integers; the SDK converts them to strings before sending.
Python
ZenRows derives the job’s output format from these options: response_type: markdown produces Markdown; autoparse, json_response, or a CSS extractor produce JSON; otherwise the result is HTML. ZenRows records this format on each result. Other common keys include js_instructions, wait, block_resources, json_response, css_extractor, autoparse, outputs, original_status, allowed_status_codes, and session_id. Custom request headers (custom_headers) are not supported yet.

Per-URL Options and Correlating Results

To set a per-URL external_id, metadata, or zenrows_params, pass dictionaries instead of plain strings. Per-URL zenrows_params override the job-level ones when a key appears in both.
Python
ZenRows returns your external_id unchanged on every result, so you can match results back to your own records without tracking the API’s task IDs. It must match ^[A-Za-z0-9._-]+$ and be 128 characters or fewer.

Downloading Content

results() gives you metadata plus a presigned result_url per task; the SDK downloads bodies straight from that URL with no extra API round-trip. Four options, on the current run (job.run.*) or any specific run (client.get_run(job_id, run_id=...)):
Python
Each result_url is a plain presigned URL (no auth header) valid for a short window, so fetch it promptly or re-list the results to get a fresh one.

Open Jobs: Streaming URLs in Batches

When you don’t have every URL up front, open a job, add tasks in batches, and close it. The run stays active and completes once it finishes the final batch.
Python
Setting last_batch=True (or calling job.close()) locks the job, and the API rejects further adds. File-input (CSV) upload is not supported for open jobs; use a closed job for CSV.

Large Inputs: CSV Upload

For lists too large to inline, upload a CSV once and reference it. upload_csv allocates the slot, PUTs your file to the presigned URL, and returns a file_input_id that submit_regular(file_input_id=...) consumes.
Python
Each CSV row becomes a task. ZenRows reads only the url and optional external_id columns, and applies the job-level zenrows_params to all of them. Limits: 50 MB / 100,000 rows.

Listing and Pagination

The SDK offers both single-page calls (list_jobs, list_runs, list_results) and lazy iterators that page for you. The iterators yield loaded handles.
Python

Reruns and Retrying Failures

Each rerun creates a brand-new run on the same job.
Python
retry_failed() is the usual post-run cleanup, a thin shortcut for rerun(status="failed"). You can act on an id you kept without a preceding GET: client.job(job_id).retry_failed().

Scheduled Jobs

Build a schedule with the typed builders and submit a scheduled job. ZenRows stores its URLs as a template, and each fire produces a fresh run.
Python
A job carries two facets, matching the API’s two scopes. job.schedule.* controls the recurring schedule, and job.run.* controls the current run. This disambiguates the API’s two distinct “pause” actions:
Python

Completion Notifications: Webhooks

Attach a webhook at submit time, and ZenRows sends a run.completed event to your HTTPS URL when a run finishes. Set signature: True to receive HMAC-signed deliveries (see Signed Webhooks); it defaults to unsigned. You can also manage the webhook later with client.put_job_webhook(...) / client.delete_job_webhook(...), and probe a receiver with client.test_webhook(...).
Python
ZenRows delivers each event at least once, so your receiver must deduplicate on the X-ZenRows-Event-Id header.
No webhook receiver? Just poll for completion with job.run.wait() or run.wait().

Bulk Export (ZIP)

Bundle an entire run’s result bodies into a single server-side zip: download_all_results starts the export, waits for it, and streams the file to disk.
Python
The server-side zip is capped at 1 GiB per run. For larger runs, or one file per task, use download_to_dir, which fetches bodies client-side with no size limit (slower; tune with concurrency). For manual control, client.start_results_export(...) and client.wait_for_export(...) expose the two steps separately.

Estimating Cost

Estimate usage before submitting with client.estimate_cost. It takes the same body you would submit and runs client-side with no API call. The tiers are 1 (base), 5 (js_render), 10 (premium_proxy), 25 (both), and a dynamic 1 to 25 for mode=auto.
Python
An estimate assumes each URL succeeds once. min == max (est.exact) unless the job uses mode=auto, which yields a per-task range.

Checking Actual Cost

Once a run finishes, read the actual usage it consumed from run.stats.spend ({credits, cost}).
Python
Only successful requests are charged, so realized spend is at most the estimate. Per-task task.spend is indicative and often absent on successful rows, so rely on the run-level rollup for actuals. Your ZenRows account statement remains the authoritative record for billing.

Signed Webhooks: HMAC Keys

Signed deliveries (see Webhooks) need an active HMAC key for your organization. Rotation uses two slots, active plus candidate, so you can roll keys without downtime.
Python
Your receiver verifies the X-Signature: t=<unix>,v1=<hex>,kid=<active> header by recomputing HMAC-SHA256(secret, f"{t}.{raw_body}") and comparing the result to v1, selecting the secret by kid.

Error Handling

Every non-2xx raises BatchAPIError. Its code attribute carries the stable code from the RFC 7807 body, and status_code the HTTP status.
Python
Task-level failures don’t raise exceptions. They appear as failed results with an error object you can read through task.error.code and task.error.detail.

Idempotency

Pass idempotency_key to submit_* and to rerun / retry_failed to guard against accidentally creating duplicate jobs. Each key can be used once: a later call that reuses the same key raises a 409 conflict, whether or not the body is identical, and the API does not store or replay the original result. An idempotency key is therefore not a safe-retry token, so if a call fails midway, check whether the job was created before retrying with a new key.
Python
The full Batch surface (jobs, runs, tasks, results, content, history, file inputs, and HMAC keys) is reachable through methods on ZenRowsBatchClient. See the SDK repository or run help(ZenRowsBatchClient).