1.The pipeline has six stages, not one scraper loop
A dependable architecture is easiest to reason about as a flow: scheduler to URL queue to fetch workers to content validator to parser to storage. The validator also branches rejected responses into a dead-letter queue. Metrics and logs observe every stage rather than sitting only around the HTTP request.
The scheduler decides what is due. The queue absorbs bursts and makes retries explicit. Fetch workers retrieve pages without knowing the downstream database schema. The validator checks whether the response contains the expected page, not merely an HTTP 200. The parser turns accepted content into typed records. Storage writes those records idempotently so rerunning a job updates the same entity instead of duplicating it.
Keep discovery separate from extraction. A crawler may discover product URLs, while extraction jobs read those URLs later with different concurrency and freshness rules. The web scraping versus web crawling guide explains that boundary in detail.
- Scheduler: emits jobs with URL, source, due time, and parser version
- Queue: buffers work, records attempts, and prevents one slow domain from blocking the batch
- Fetch workers: use direct HTTP, Web Unlocker, or a browser according to the target
- Validator: checks status, content type, required markers, and obvious challenge pages
- Parser and normalizer: produces typed fields plus provenance
- Storage and dead-letter queue: persists good records and preserves failed evidence
2.Treat fetching and validation as separate decisions
Transport success is not data success. A target can return HTTP 200 with a login page, consent wall, JavaScript shell, challenge interstitial, or an empty product grid. Validate at least one source-specific marker before parsing: a product identifier, listing container, article heading, or another element that must exist on a genuine page.
Use direct HTTP for open, server-rendered pages. Use Web Unlocker when a target needs managed proxy routing or rendering, and use a persistent browser only when later steps depend on earlier session state. The web scraping API guide covers request modes and output formats; the JavaScript rendering guide covers the browser decision.
The example below sends a safe demonstration URL through the current OmniScrape endpoint, keeps the API key in an environment variable, validates the response contract, and returns a small event that the parser stage can consume.
123456789101112131415161718192021222324252627282930313233343536373839import os
import requests
API_URL = "https://api.omniscrape.io/v1/scrape"
def fetch_job(job: dict) -> dict:
response = requests.post(
API_URL,
headers={"X-API-Key": os.environ["OMNISCRAPE_KEY"]},
json={
"url": job["url"],
"mode": "auto",
"output_format": "html",
"enable_solver": True,
},
timeout=120,
)
response.raise_for_status()
body = response.json()
if not body.get("success"):
raise ValueError("scrape completed without usable content")
html = body["data"]["content"]
if job["required_marker"] not in html:
raise ValueError("required page marker is missing")
return {
"job_id": job["job_id"],
"url": job["url"],
"html": html,
"method_used": body.get("metadata", {}).get("method_used"),
}
event = fetch_job({
"job_id": "example-home-v1",
"url": "https://example.com/",
"required_marker": "Example Domain",
})
3.Retry transient failures without creating a retry storm
Retry only failures that can plausibly change. Timeouts, HTTP 429, and temporary 502, 503, or 504 responses may recover. Invalid credentials, malformed payloads, exhausted balance, authorization failures, and stable parser errors require a configuration change or human action. Repeating them wastes capacity and hides the real incident.
Honor Retry-After when it is present. The HTTP Retry-After reference documents both delay-seconds and HTTP-date forms. Otherwise use capped exponential backoff with random jitter. Jitter spreads worker wake-ups so every failed job does not hit the service again at the same instant. AWS recommends exponential backoff, jitter, and a maximum retry count in its retry-control guidance.
After the attempt budget is exhausted, move the job to a dead-letter queue with the response status, error class, validator result, parser version, and a pointer to retained evidence. A dead-letter queue is a review surface, not a permanent storage tier. Alert on its growth and build a replay command that requires an explicit parser or policy version.
4.Control concurrency per domain and across the whole pipeline
Global worker count and per-domain concurrency solve different problems. A global cap protects your API plan and infrastructure. A per-domain cap respects the target's capacity and prevents one large source from monopolizing the queue. Use separate queue partitions or keyed semaphores when one pipeline covers many domains.
Backpressure must travel upstream. If parsers or storage cannot keep up, fetch workers should slow down instead of filling memory with HTML. Queue depth, oldest-job age, worker utilization, validation failure rate, and storage latency together show where the bottleneck lives. A 429 spike means reduce concurrency and inspect Retry-After; it is not a signal to add more workers.
5.Store raw evidence, normalized records, and operational events separately
Normalized records serve the product or analysis. Raw HTML or structured responses support debugging and reprocessing. Operational events explain what happened to each job. Mixing all three in one table makes retention, access control, and schema evolution unnecessarily difficult.
Measure useful outcomes: jobs completed, records passing validation, data freshness, dead-letter rate, retry attempts, latency by stage, and cost per accepted record. HTTP success rate alone misses empty or incorrect data. If you use OmniScrape, log response metadata alongside the job so retrieval choices remain visible in your own observability system.
6.Production implementation checklist
Ship the smallest complete pipeline before optimizing throughput. One source with strong validation and replayable failures teaches more than a high-volume loop that silently writes bad rows.
- Job schema has a stable ID, deadline, attempt count, source, locale, and parser version
- Fetch workers have explicit connection and request timeouts
- Retries are limited to transient failures and use Retry-After or capped backoff with jitter
- Global and per-domain concurrency are bounded
- Validation checks required data markers, not only HTTP status
- Writes are idempotent and retain source URL, timestamps, and parser version
- Dead-letter jobs keep diagnostic evidence and have an explicit replay workflow
- Dashboards track accepted records, freshness, retries, queue age, and failures by stage
- Retention, robots directives, website terms, applicable law, and data rights have been reviewed
7.Start with the fetch layer you can observe
If your current bottleneck is unreliable page retrieval, test the Web Unlocker product against a representative set of authorized URLs and keep the rest of the pipeline unchanged. Start with mode auto, validate the returned content, and record the method used. The existing httpx scraping guide provides an async Python implementation when you are ready to add a bounded worker pool.
Collect only data you are authorized to use. Respect applicable law, website terms, privacy and data rights, robots directives, and published rate limits. Prefer an official API or licensed feed when it meets the requirement.
Frequently asked questions
What is a web scraping data pipeline?
It is the complete system that schedules URLs, queues jobs, retrieves pages, validates content, parses fields, stores records, and routes failures for review. The scraper or API call is only the fetch stage.
Should a scraping pipeline store raw HTML?
Store raw responses when they are necessary for debugging or lawful reprocessing, but keep them separate from normalized records, apply access controls, and define an expiry policy. Do not retain sensitive data by default.
Which scraping failures should be retried?
Retry timeouts and temporary 429 or 5xx failures with a strict attempt limit, Retry-After support, exponential backoff, and jitter. Do not automatically retry invalid credentials, malformed requests, authorization failures, or deterministic parser errors.
How do I know whether a scrape succeeded?
Require both transport success and content validation. Check the response contract, then verify source-specific markers or required fields before the parser writes a record. A 200 response alone is not enough.
When should the pipeline use Web Unlocker instead of a browser?
Use Web Unlocker for independent page retrieval that needs managed rendering or proxy routing. Use a persistent browser when the workflow must preserve state across multiple dependent interactions, such as a multi-step authorized session.
Related guides
- Web Scraping API: Endpoint, Modes, Output Formats & Integration Patterns
- Web Scraping vs Web Crawling: Architecture, Patterns, and When to Use Each
- HTTPX Web Scraping: Async Python with OmniScrape
- Scrape JavaScript-Rendered Pages: SPAs, Hydration, and Hidden APIs
- Web Scraping Without Getting Blocked
- Scrape HTML Tables to JSON: A Reliable Workflow