Prompt
How do I build a managed dataset layer for recurring web data pulls?
Latest observation
A good managed dataset layer for recurring web data pulls is a thin platform between “raw web sources” and “downstream users/apps” that handles collection, normalization, versioning, quality, access, and refresh.
Here’s a practical way to build it.
1) Define the contract first
Before tools, decide what a “dataset” means in your system:
- Source: which site/API/page set it comes from
- Schema: columns, types, semantics
- Refresh policy: hourly, daily, weekly, event-driven
- Freshness SLA: “data no older than 24h”
- Quality rules: uniqueness, completeness, ranges, dedupe
- Lineage: when it was fetched, from where, by which job
- Access model: internal only, team-level, public, paid
This contract becomes the product surface of the dataset layer.
2) Use a layered architecture
A common pattern is:
A. Ingestion layer
Pull data from web sources:
- Official APIs if possible
- Scraping/crawling when needed
- Headless browser for JS-heavy pages
- Incremental fetching where possible
Store:
- raw HTML/JSON/files
- request metadata
- fetch timestamp
- source URL
- checksum/hash
B. Raw landing zone
Keep immutable, append-only raw data:
- object storage like S3/GCS/Azure Blob
- partitioned by source/date
- no business logic here
Example structure:
raw/
source=acme_products/
dt=2026-09-24/
part-0001.json.gz
part-0002.json.gz
C. Normalization/transform layer
Convert raw web data into structured tables:
- parse HTML/JSON
- clean text
- standardize dates/currencies/units
- deduplicate entities
- map fields to canonical schema
Use dbt, Spark, DuckDB, pandas, or SQL transforms.
D. Curated dataset layer
This is the managed layer:
- validated tables
- documented schemas
- versioned snapshots
- stable interfaces for consumers
Often exposed as:
- warehouse tables
- Parquet/Delta/Iceberg tables
- an internal API
- query endpoints
3) Make it dataset-centric, not job-centric
Instead of “a scraper job,” treat each source as a managed asset with metadata.
For each dataset, track:
dataset_idownersource_type(api, scrape, crawl)scheduleschema_versionlast_successful_runfreshnessrow_counterror_ratequality_checksdownstream_dependencies
This allows operations like:
- “show all datasets stale by > 12h”
- “rebuild only affected datasets”
- “notify consumers when schema changes”
4) Design for idempotency and incremental updates
Recurring pulls need safe reruns.
Best practices:
- Each run has a unique
run_id - Store fetched payloads with content hash
- Upserts based on stable keys
- Partition by source and collection date
- Maintain a watermark:
updated_sincelast_seen_cursorpage_token
If a run fails halfway, it should be safely replayable without duplicate rows.
5) Build strong metadata and lineage
This is what makes it “managed.”
Track per record or batch:
- source URL
- fetch timestamp
- parser version
- transform version
- job/run id
- raw file path
- hash/checksum
- HTTP status / scrape status
You can store this in:
- a metadata table
- OpenMetadata/DataHub/Amundsen
- custom Postgres tables
Example metadata table columns:
dataset_id
run_id
source_url
fetched_at
raw_object_path
parser_version
transform_version
status
row_count
error_message
6) Add data quality checks
At minimum, validate:
- schema matches expected types
- primary keys are unique
- required fields are not null
- value ranges are valid
- no sudden volume drops/spikes
- no excessive duplicates
Tools:
- Great Expectations
- Soda
- dbt tests
- custom SQL assertions
Run checks:
- before publishing curated data
- after each pull
- with alerting on failure
7) Version the dataset
Web sources change. Your users need reproducibility.
Ways to version:
- Snapshot by date: daily full extracts
- Partitioned history: keep all versions by
effective_date - SCD2 for slowly changing entities
- Schema versioning: explicit breaking-change control
Recommended:
- immutable raw history
- curated “latest” table
- snapshot tables for reproducibility
Example:
products_rawproducts_latestproducts_snapshot_daily
8) Choose storage and compute pragmatically
Small/medium scale
- Raw: S3/GCS
- Transform: Python + pandas or DuckDB
- Curated: Postgres or BigQuery/Snowflake
- Metadata: Postgres
Larger scale
- Raw: object storage
- Transform: Spark / Ray / distributed SQL
- Curated: Delta Lake / Iceberg / warehouse
- Metadata: dedicated catalog + orchestration
9) Orchestrate the pipeline
Use a scheduler/orchestrator for recurring pulls:
- Airflow
- Dagster
- Prefect
- Temporal
- cron for very small setups
Pipeline stages:
- fetch
- store raw
- parse/normalize
- validate
- publish curated
- update metadata
- notify/alert
Make each step observable and restartable.
10) Handle website-specific realities
Web pulls are fragile. Plan for:
- rate limits
- login/session expiration
- bot detection
- HTML structure changes
- pagination quirks
- timezone and locale issues
- retries with backoff
- caching and polite crawl delays
For heavily changing pages, consider:
- robust selectors
- schema drift detection
- fallback parsers
- contract tests against sample pages
11) Expose the managed layer cleanly
Consumers should not touch scraper code or raw HTML.
Give them:
- stable tables/views
- documented columns
- freshness timestamps
- sample queries
- row-level descriptions
- clear deprecation notices for schema changes
Useful interfaces:
- SQL views
- REST/GraphQL API
- file exports
- scheduled dumps
12) Add observability and alerting
Monitor:
- job success/failure rate
- latency
- freshness lag
- row counts
- parse error counts
- duplicate rates
- source downtime
Alert on:
- missed runs
- sudden volume change
- schema mismatch
- quality failures
- HTML parsing exceptions
13) A simple reference architecture
For many teams, this is enough:
- Prefect/Airflow to orchestrate
- Python scrapers/APIs to fetch
- S3 for raw immutable storage
- DuckDB/dbt/Spark for transforms
- BigQuery/Snowflake/Postgres for curated tables
- Great Expectations for validation
- Postgres for dataset metadata
- Slack/email for alerts
14) Minimal implementation pattern
If you want a lightweight version:
- Fetch data
- Save raw payload to object storage with run metadata
- Parse into structured records
- Validate
- Upsert into curated table
- Record run status and metrics
- Publish a “latest” view and optional daily snapshots
That alone gets you a managed dataset layer.
15) Common pitfalls
Avoid:
- writing directly into the final table without raw retention
- no source metadata
- no schema versioning
- no idempotency
- mixing scrape logic and business transformations
- no quality gates
- no alerting
- relying on HTML structure stability
16) Recommended “first version” blueprint
If you’re starting from scratch:
- Storage: S3 + Postgres
- Orchestration: Dagster or Prefect
- Transform: Python + dbt
- Validation: dbt tests + Great Expectations
- Metadata: Postgres tables
- Access: warehouse tables/views
This is usually enough to support multiple recurring web datasets safely.
If you want, I can also give you:
- a concrete folder/schema design,
- a sample Postgres metadata model, or
- a reference implementation in Python + Airflow/Prefect.