
Data Scraping & Sync
Scheduled competitor price collection into Postgres, with proxy rotation, schema validation, and an alert when a selector silently stops matching.
Python · Playwright · ScrapingBee · Supabase · Docker
View GitHub Repo: MIT licensed. The repository is the skeleton; the gated bundle adds the working parsers and the deploy files.
Copy Docker Compose: Redis holds the per-target rate limit and the last-seen hash, so a restart does not re-scrape everything.
Someone checks four competitor sites every Monday and pastes prices into a spreadsheet. The obvious fix is a scraper, and the obvious scraper works for three weeks, then a site ships a redesign and it starts writing nulls that nobody notices until the sheet is useless.
Everything this blueprint calls. Check you have them before you start - the usual reason a build stalls halfway is a key that takes a day to get approved.
Handles rendering and proxy rotation as one call. Running your own browser fleet is a second project, not a step in this one.
Free tier: 1,000 credits
Stores an observation history rather than the current price, which is what makes trend queries possible later.
Free tier: Two projects, 500MB each
Scheduled scraping wants a long-running host. Serverless timeouts and cold starts fight this workload.
Free tier: From about $4.50 a month
Only if you need it
Only for targets that block datacentre ranges outright. Most do not - try without first.
Some links above are partner links and we earn a commission if you sign up through them. It costs you nothing extra, and it does not decide what goes in a blueprint - the self-hosted option is recommended wherever it is genuinely the better call.
Monthly, at the volumes this blueprint was tested against. Worth comparing against what the manual version of this work costs you in hours.
| Component | Cost | At what volume |
|---|---|---|
| ScrapingBee | ~$29/mo | Freelance plan, roughly 4 targets checked hourly |
| Hetzner CX22 | $4.59/mo | Container plus Redis |
| Supabase | $0 - $25/mo | Free until the history table gets large |
The files
The repository shows the structure. This bundle is the part that takes the day: real parsers, the validation schema, and the alerting that catches a silent break.
Four working site parsers, written against live markup
.env template with every key the container reads
Pydantic schemas and the SQL for the history table
The selector-drift alert, which is the reason this keeps working
The decisions that matter, in the order you will meet them. Everything here is a thing that broke in testing before it was a rule.
Each site gets its own module returning the same typed record. Shared "smart" extraction across sites looks elegant for two targets and becomes unmaintainable at five, because the exception handling for one site leaks into all of them.
from decimal import Decimal
from pydantic import BaseModel, field_validator
class Observation(BaseModel):
sku: str
price: Decimal
currency: str
in_stock: bool
source_url: str
@field_validator("price")
@classmethod
def sane_price(cls, v: Decimal) -> Decimal:
# A selector that stops matching returns 0, not an exception.
# Without this the pipeline writes zeroes for a week in silence.
if v <= 0 or v > Decimal("1000000"):
raise ValueError(f"implausible price: {v}")
return vEvery extraction goes through the schema. A site redesign then surfaces as a validation error with a URL attached, rather than as a column of zeroes discovered a fortnight later by someone building a chart.
A scraper rarely fails loudly. It fails by matching nothing. Track the share of fields successfully extracted per target per run, and page someone when it drops below 90 percent of the trailing average.
ratio = extracted / max(attempted, 1)
if ratio < 0.9 * trailing_average(target, days=7):
alert(
f"{target}: extraction dropped to {ratio:.0%} "
f"({extracted}/{attempted}) - selectors likely stale"
)Append a row per check with a timestamp. Updating one row in place makes today cheap and makes every historical question - when did they drop the price, how often do they change it - impossible to answer.
Randomised delays per target, exponential backoff on 429 and 503, and a hard daily cap per host. Politeness here is also self-interest: the fastest way to lose a data source is to make yourself expensive to serve.
The difference between a demo and something you can leave running is entirely in this table. Every row is a failure the blueprint handles explicitly rather than hoping about.
| Failure | What happens |
|---|---|
| Target returns 403 | Retry once through a residential exit, then mark the run degraded and alert. No silent skip. |
| Selector matches nothing | Validation rejects the record, the run is logged as a partial, and the drift alert fires on the trend. |
| Postgres unreachable | Observations buffer to Redis and flush on the next successful connection. |
| Container restarts mid-run | Per-target last-success timestamp in Redis, so the schedule resumes rather than restarting the sweep. |
One row written per observation.
{
"target": "competitor-a",
"sku": "NW-4410-BLK",
"price": "289.00",
"currency": "USD",
"in_stock": true,
"source_url": "https://example.com/p/nw-4410",
"observed_at": "2026-07-30T06:14:02Z",
"extraction_ratio": 1.0,
"via": "scrapingbee:us"
}Done-For-You
Public catalogue pages are the easy case. Logged-in pricing, regional variants, and sites with active bot defences are a different piece of work, and one worth scoping properly.
A TechZapp sprint is a fixed-scope, fixed-price week. A senior engineer builds it in your environment, hands over the repository and the runbook, and you own every part of it afterwards. No platform of ours to keep paying for.
Parsers written and tested against your actual targets, however awkward
Deployed with monitoring, drift alerts, and a dashboard your team can read
A written note on the legal and robots position for each target before we start
Sprint pricing
One sprint per four targets
Fixed scope agreed before we start
Built in your environment, not ours
Repository, infrastructure, and runbook handed over
Two weeks of support after handover included
Scope This SprintTell us what you are integrating with. We reply within one business day, and we will say plainly if the blueprint above already covers it.