crawlr
An AI-powered, self-healing web scraper for e-commerce price & stock monitoring.
Website ·
PyPI ·
pip install crawlr
Crawlr uses an LLM (or a free offline heuristic) only to generate and repair CSS selectors, then extracts pages deterministically with those cached selectors. When a site changes its layout and the selectors break, Crawlr detects it and regenerates them automatically — so your monitoring never silently dies.
- Runs free & offline — no API key required; add an OpenAI/Anthropic key for higher accuracy.
- Self-healing — survives redesigns that break traditional scrapers.
- Zero-config —
crawlr watch <url>auto-detects the right schema (product, jobs, real estate, news). - Price & stock alerts — webhook (signed), Slack, Discord, Teams, Telegram, ntfy, or email, with throttling.
- General-purpose — e-commerce ships in; add new verticals as simple YAML.
Install
pipx install crawlr # or: pip install crawlr
Quickstart
crawlr watch "https://store.com/product/123" --target 25 # schema auto-detected
crawlr watchlist # current price, movement, stock
crawlr monitor --daemon # keep checking in the background
crawlr serve # optional local dashboard
Omit --schema and Crawlr detects the page type for you (product, product list, jobs,
real estate, news). Pass --schema <name> to override.
Deploy with Docker
docker run -p 8000:8000 -v crawlr-data:/data ghcr.io/ardfaiyaz/crawlr # dashboard on :8000
Or run the dashboard and a background monitor together:
docker compose up -d # then open http://localhost:8000
Build locally with docker build -t crawlr .. Data (SQLite DB, selector cache,
snapshots) persists in the /data volume.
Alert triggers
Pick one per watch with --trigger (or in the dashboard):
| Trigger | Alerts when |
|---|---|
any_change |
any watched field changes |
price_drop |
the price goes down |
price_below |
price is at/below your target |
price_above |
price is at/above your target |
back_in_stock |
the item becomes available |
out_of_stock |
the item sells out |
For richer logic, run crawlr init to create an editable crawlr.rules.yaml that maps
circumstances to actions (and overrides per-watch triggers):
default_action: ignore
rules:
- when: price_drops_below
amount: 25
action: alert
- when: back_in_stock
action: alert
Every option, explained
crawlr watch <url> [options] — start tracking a product page or a store's product list.
| Option | What it does |
|---|---|
--target 500 |
Ping me when the price drops to 500 or below (also sets the trigger to price_below) |
--restock |
Ping me when the item goes from out of stock → in stock |
--trigger <name> |
Choose exactly when to be alerted (see the table below) |
--interval 60 |
Check this page every 60 minutes (default: 60) |
--schema <name> |
Force the page type instead of auto-detecting: product, product_list, jobs, real_estate, news |
Triggers (--trigger ...), in plain English:
| Trigger | Alerts you when… |
|---|---|
any_change |
anything you watch changes (price, stock, title…) |
price_drop |
the price goes down by any amount |
price_below |
the price is at or below your --target |
price_above |
the price is at or above your --target |
back_in_stock |
the item becomes available again |
out_of_stock |
the item sells out |
The commands you'll actually use:
| Command | What it does |
|---|---|
crawlr watch <url> |
Start tracking a product or store page |
crawlr watchlist |
Show everything you track: price, change, stock, status |
crawlr monitor |
Check all due pages once, right now |
crawlr monitor --daemon |
Keep checking forever in the background |
crawlr compare <url> <url> … |
One-off price comparison across several links |
crawlr canvas "<product>" |
Search many stores by product name and compare prices |
crawlr insights <id> |
Price analytics: all-time low/high, average, "deal score" |
crawlr pause <id> / resume <id> |
Temporarily stop / restart tracking one item |
crawlr unwatch <id> |
Stop tracking an item and delete its history |
crawlr serve |
Open the web dashboard at http://localhost:8000 |
crawlr doctor |
Check that your setup works |
crawlr test-alert |
Send a test notification so you know alerts reach you |
crawlr --version |
Print the installed Crawlr version (also -V) |
The
<id>numbers come fromcrawlr watchlistorcrawlr sites.
Recipes
# Track a competitor's price, alert on a 10%+ drop, check hourly
crawlr watch "https://store.com/p/123" --trigger price_drop --interval 60
# Get pinged when something is back in stock
crawlr watch "https://store.com/p/123" --restock
# Compare the same product across three shops
crawlr compare "https://a.com/p" "https://b.com/p" "https://c.com/p"
# Monitor a job board (schema auto-detected)
crawlr watch "https://boards.example/jobs" --interval 720
# Free phone alerts via ntfy.sh
export CRAWLR_ALERT_NTFY="https://ntfy.sh/my-crawlr-alerts"
crawlr monitor --daemon
# Pause / resume / remove a watch
crawlr pause 3 && crawlr resume 3 && crawlr unwatch 3
# Pipe machine-readable output into other tools
crawlr watchlist --json | jq '.[] | {title, price}'
Custom schemas (no code)
Add new verticals by dropping a YAML file into your schema directory:
name: jobs
item_selector: ".job-card"
fields:
- name: title
description: the job title
type: text
required: true
- name: salary
description: annual salary
type: number
Then crawlr scrape <url> --schema jobs. List schemas with crawlr schemas.
Configuration
Crawlr is configured with environment variables. Set them whichever way suits you:
| Where | How |
|---|---|
| macOS / Linux | export CRAWLR_LLM_PROVIDER=openai |
| Windows PowerShell | $env:CRAWLR_LLM_PROVIDER="openai" |
A .env file |
add CRAWLR_LLM_PROVIDER=openai in your project folder (loaded automatically) |
A couple of quick examples:
# Turn on AI mode for higher accuracy (optional — free heuristic is the default)
export CRAWLR_LLM_PROVIDER="openai"
export CRAWLR_LLM_API_KEY="sk-..."
# Get Discord alerts, then verify they arrive
export CRAWLR_ALERT_DISCORD="https://discord.com/api/webhooks/..."
crawlr test-alert
A step-by-step version of every setting — grouped by task, with examples — lives in the website docs.
Full reference:
| Variable | Default | Description |
|---|---|---|
CRAWLR_LLM_PROVIDER |
none |
openai, anthropic, or none (offline heuristic) |
CRAWLR_LLM_API_KEY |
— | API key for the chosen provider |
CRAWLR_DATABASE_URL |
— | postgresql://... to use Postgres instead of SQLite |
CRAWLR_ALERT_WEBHOOK |
— | Webhook URL for change alerts |
CRAWLR_ALERT_SLACK |
— | Slack incoming-webhook URL |
CRAWLR_ALERT_DISCORD |
— | Discord incoming-webhook URL |
CRAWLR_ALERT_TEAMS |
— | Microsoft Teams incoming-webhook URL |
CRAWLR_ALERT_NTFY |
— | ntfy.sh (or self-hosted) topic URL |
CRAWLR_ALERT_TELEGRAM_TOKEN |
— | Telegram bot token (from @BotFather) |
CRAWLR_ALERT_TELEGRAM_CHAT_ID |
— | Telegram chat id to send alerts to |
CRAWLR_WEBHOOK_SECRET |
— | HMAC-signs generic webhook payloads |
CRAWLR_ALERT_THROTTLE_MINUTES |
0 |
Suppress repeat alerts within N minutes |
CRAWLR_PROXIES |
— | Comma-separated proxy URLs to rotate |
CRAWLR_RESPECT_ROBOTS |
true |
Honor robots.txt |
CRAWLR_FX_BASE |
USD |
Currency that compare converts prices into |
CRAWLR_FX_LIVE |
false |
Fetch live FX rates (cached) instead of the pinned table |
CRAWLR_FX_API_URL |
open.er-api.com |
Live FX endpoint ({"rates": {CODE: perUSD}}) |
CRAWLR_FX_RATES |
— | Pinned rate overrides, e.g. EUR=0.92,GBP=0.79 |
CRAWLR_FETCH_PROVIDER |
direct |
Unblocking/render backend: scraperapi, scrapingbee, zyte, custom, or direct |
CRAWLR_FETCH_PROVIDER_KEY |
— | API key for the chosen provider |
CRAWLR_FETCH_PROVIDER_RENDER |
true |
Ask the provider to render JavaScript (costs more credits) |
CRAWLR_CANVAS_RETAILERS |
— | Path to a YAML file adding your own stores to crawlr canvas |
CRAWLR_COUNTRY |
— | Default country for crawlr canvas local stores (ISO code, e.g. ph, us) |
CRAWLR_GEO |
true |
Auto-detect the canvas country from your IP when no country/currency is given |
CRAWLR_GEO_TIMEOUT |
3.0 |
Seconds to wait for the IP-geolocation lookup before falling back |
Step-by-step setup guides
Every integration below follows the same pattern: get the URL/key from the service, set the
variable, then run crawlr test-alert (for alerts) or crawlr doctor (for everything else) to
confirm it works. Set variables with export VAR=value (macOS/Linux), $env:VAR="value"
(Windows PowerShell), or a .env file.
Discord alerts
- Open Discord and go to the server you manage where you want alerts.
- Click the server name → Server Settings.
- Go to Integrations → Webhooks → New Webhook.
- Pick the channel it should post in, optionally rename it, then click Copy Webhook URL.
- Set it and test:
export CRAWLR_ALERT_DISCORD="https://discord.com/api/webhooks/…"
crawlr test-alert
Slack alerts
- Go to api.slack.com/apps → Create New App → From scratch; pick your workspace.
- Open Incoming Webhooks and turn it On.
- Click Add New Webhook to Workspace, choose a channel, and Allow.
- Copy the webhook URL, then set it:
export CRAWLR_ALERT_SLACK="https://hooks.slack.com/services/…"
Telegram alerts
- In Telegram, message @BotFather, send
/newbot, and follow the prompts. Copy the bot token. - Open a chat with your new bot and send it
/start(a bot can't message you until you do). - Message @userinfobot — it replies with your numeric chat id.
- Set both:
export CRAWLR_ALERT_TELEGRAM_TOKEN="123456:ABC-your-token"
export CRAWLR_ALERT_TELEGRAM_CHAT_ID="123456789"
ntfy alerts (free phone push, no account)
- Choose a hard-to-guess topic name, e.g.
my-crawlr-a7f3. Your URL ishttps://ntfy.sh/my-crawlr-a7f3. - Install the ntfy app (iOS/Android) — or open the URL in a browser — and subscribe to that topic.
- Set it:
export CRAWLR_ALERT_NTFY="https://ntfy.sh/my-crawlr-a7f3"
Microsoft Teams alerts
- In Teams, hover the channel → ⋯ → Workflows (older tenants: Connectors → Incoming Webhook).
- Pick the "post to a channel when a webhook request is received" template and create it.
- Copy the generated URL and set it:
export CRAWLR_ALERT_TEAMS="https://…webhook…url…"
Email alerts (SMTP)
- Get your provider's SMTP details. For Gmail: enable 2-step verification, then create an App Password (Google Account → Security → App passwords).
- Set the recipient and server:
export CRAWLR_ALERT_EMAIL_TO="you@example.com"
export CRAWLR_SMTP_HOST="smtp.gmail.com"
export CRAWLR_SMTP_PORT="587"
export CRAWLR_SMTP_USER="you@gmail.com"
export CRAWLR_SMTP_PASSWORD="your-app-password"
Any generic webhook
- Use any endpoint that accepts an HTTP POST with JSON. To try it out, grab a free URL from webhook.site.
- Set it (optionally sign the payloads so your receiver can verify them):
export CRAWLR_ALERT_WEBHOOK="https://webhook.site/your-id"
export CRAWLR_WEBHOOK_SECRET="any-shared-secret" # optional; adds X-Crawlr-Signature
Tip: enable several channels at once, add
CRAWLR_ALERT_THROTTLE_MINUTES=60to avoid repeat pings, and prefer a daily summary withcrawlr digest --send.
AI mode — OpenAI
Crawlr runs free and offline by default; a key only helps on unusual layouts (it runs once per site).
- Go to platform.openai.com and sign in.
- Account menu (top-right) → API keys → Create new secret key → copy it (shown once, starts with
sk-). - Add a payment method under Settings → Billing.
- Set it:
export CRAWLR_LLM_PROVIDER="openai"
export CRAWLR_LLM_API_KEY="sk-..."
AI mode — Anthropic
- Go to console.anthropic.com and sign in.
- Open API keys → Create Key → copy it (starts with
sk-ant-). - Set it:
export CRAWLR_LLM_PROVIDER="anthropic"
export CRAWLR_LLM_API_KEY="sk-ant-..."
Postgres storage (free hosted)
- Create a free project at neon.tech (or supabase.com / railway.app).
- Copy the connection string —
postgresql://user:pass@host:5432/dbname. - Install the extra and set the URL:
pip install "crawlr[postgres]"
export CRAWLR_DATABASE_URL="postgresql://user:pass@host:5432/dbname"
crawlr doctor
Fetch provider (for big marketplaces)
- Sign up at scraperapi.com (or scrapingbee.com / zyte.com); free trial tiers exist.
- Copy the API key from the dashboard.
- Set the backend and key, then verify:
export CRAWLR_FETCH_PROVIDER="scraperapi" # or scrapingbee | zyte | custom
export CRAWLR_FETCH_PROVIDER_KEY="your-key"
crawlr doctor
Currency conversion
crawlr compare / crawlr canvas convert prices to one currency so you can compare fairly.
export CRAWLR_FX_BASE="PHP" # compare everything in pesos
export CRAWLR_FX_LIVE="true" # live rates (cached); else pinned table
export CRAWLR_FX_RATES="EUR=0.92,GBP=0.79" # optional: pin specific rates (units per USD)
crawlr fx --amount 100 --from EUR --to USD # quick one-off conversion
Proxies
- Get one or more proxy URLs from your proxy provider (format
http://user:pass@host:port). - Comma-separate them; Crawlr rotates between them:
export CRAWLR_PROXIES="http://user:pass@host:port,http://host2:port"
Examples
Ready-to-run recipes live in examples/ — watch competitors, use a
custom schema, and call the hosted API from Python.
Development
pip install -e '.[dev]'
pytest # offline test suite
ruff check . # lint
crawlr eval # extraction accuracy gate
Contributing
Contributions are welcome — see CONTRIBUTING.md. Great first contributions: new vertical presets, golden eval cases for sites that extract wrong, and docs/examples.
Demo
A short GIF is the best intro. Generate one with VHS:
vhs docs/demo.tape → docs/demo.gif (see docs/DEMO.md).
Does it work on big marketplaces (Lazada, Shopee, Amazon)?
Short answer: it can, but they're the hardest sites to scrape — expect extra setup, and don't count on 100% reliability.
Crawlr shines on sites with clean HTML or embedded product data (schema.org JSON-LD). Big marketplaces are deliberately hostile to bots, in three ways — here's how to give Crawlr its best shot:
-
They render prices with JavaScript. The price often isn't in the raw HTML; it loads after the page runs its scripts. Install the browser engine so Crawlr can render it:
pip install "crawlr[js]" playwright install chromium
Then pass
--js(e.g.crawlr watch "<url>" --js) or let Crawlr auto-escalate when it detects a JS-only shell. -
They have anti-bot protection (Cloudflare, CAPTCHAs, rate limits). Crawlr detects when it's blocked and skips that run instead of saving a bogus price. The most reliable way through is a fetch provider — a service that renders the page and clears anti-bot for you, returning clean HTML (see Use a fetch provider below). Rotating proxies also help:
export CRAWLR_PROXIES="http://user:pass@host:port,http://host2:port"
-
robots.txt — many marketplaces disallow scraping these pages, and Crawlr honors
robots.txtby default, so it may refuse to fetch. Only override this for sites you're permitted to scrape (see Legal & responsible use below):export CRAWLR_RESPECT_ROBOTS=false
Rule of thumb:
| Site type | How well it works |
|---|---|
| Independent shops, Shopify / WooCommerce, anything with schema.org data | ✅ Works well out of the box |
| A marketplace's official API (if it offers one) | ✅ Most reliable — prefer this |
| Lazada / Shopee / Amazon product pages | ⚠️ Possible with crawlr[js] + proxies; may still get blocked |
| Pages behind a login, or a CAPTCHA on every request | ❌ Not a good fit |
Tip: prove the whole flow works on an easy site first, then point it at the hard one:
crawlr watch "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html" --target 60
crawlr watchlist
Use a fetch provider (recommended for marketplaces)
Rather than fighting anti-bot yourself, route fetches through a rendering/unblocking service (ScraperAPI, ScrapingBee, Zyte, or any similar "URL in → HTML out" API). Crawlr then extracts as usual — no code changes needed:
export CRAWLR_FETCH_PROVIDER=scraperapi # or scrapingbee | zyte | custom
export CRAWLR_FETCH_PROVIDER_KEY=your-key
crawlr doctor # confirms the provider is configured
Most providers have a free tier to start. For any service that isn't built in, use
CRAWLR_FETCH_PROVIDER=custom and point it at the endpoint yourself
(CRAWLR_FETCH_PROVIDER_ENDPOINT, _URL_PARAM, _KEY_PARAM/_KEY_HEADER, _EXTRA,
_RESPONSE/_HTML_PATH). Even more reliable and fully legal: a marketplace's official API.
Canvas — shop a product across many stores
Don't have a link, just a product in mind (say a Wooting 60HE)? crawlr canvas searches
several retailers at once, grabs the best-matching result + price from each, converts everything
into one currency, and ranks them so you can comparison-shop ("canvas") in one command:
crawlr canvas "Wooting 60HE" # global stores
crawlr canvas "MAD60 HE" --country ph # Philippine stores (Lazada, Shopee, Zalora)
crawlr canvas "Wooting 60HE" --to PHP # infers --country ph from the currency
crawlr canvas "Wooting 60HE" --retailers amazon,ebay,newegg --to USD
Shop your local marketplaces
Canvas is location-aware. Pass --country (ISO code) — or set CRAWLR_COUNTRY, or just use a
local currency with --to — and Crawlr searches that country's stores instead of the global set:
| Country | --country |
Local stores it searches |
|---|---|---|
| Philippines | ph |
Lazada PH, Shopee PH, Zalora PH, AliExpress |
| Singapore | sg |
Lazada SG, Shopee SG, Amazon SG, AliExpress |
| Malaysia | my |
Lazada MY, Shopee MY, AliExpress |
| Indonesia | id |
Lazada ID, Shopee ID, Tokopedia, AliExpress |
| Thailand | th |
Lazada TH, Shopee TH, AliExpress |
| Vietnam | vn |
Lazada VN, Shopee VN, Tiki, AliExpress |
| United States | us |
Amazon, eBay, Walmart, Newegg, Best Buy |
| United Kingdom | gb |
Amazon UK, eBay UK, Currys, AliExpress |
| India | in |
Amazon IN, Flipkart, AliExpress |
| Australia | au |
Amazon AU, eBay AU, AliExpress |
| Japan | jp |
Amazon JP, AliExpress |
| Canada | ca |
Amazon CA, eBay CA, Newegg CA |
How the country is chosen (first match wins):
--country ph— the flag you passCRAWLR_COUNTRY=ph— your saved default- the explicit currency you asked for (
--to PHP→ Philippines) - your IP address — Crawlr auto-detects your location (result is cached; no key needed)
- otherwise → global stores
So in most cases you don't have to pass anything — just run crawlr canvas "mad60 he" and, from
the Philippines, it automatically searches Lazada PH, Shopee PH, and Zalora PH. Turn the IP lookup
off with CRAWLR_GEO=false (fully offline). Add your own shops any time via a YAML file
(CRAWLR_CANVAS_RETAILERS).
Heads-up (important): local marketplaces like Lazada and Shopee aggressively block bots, just like Amazon. To get real results from them you must set a fetch provider first (
CRAWLR_FETCH_PROVIDER+ key). Without one, those rows show "blocked or unreachable".
Legal & responsible use
Crawlr is a general-purpose scraping tool, and you are responsible for how you use it. Before monitoring any site, make sure you comply with:
- the target site's Terms of Service and
robots.txt— Crawlr honorsrobots.txtby default, so keepCRAWLR_RESPECT_ROBOTS=trueunless you own the site; - applicable laws such as the CFAA (US) and GDPR/CCPA whenever personal data is involved — prefer public, non-personal data and avoid collecting PII;
- reasonable rate limits — use sensible intervals and delays so you don't overload a site.
Crawlr is meant for legitimate uses such as tracking your own listings, price research, and public-data monitoring. Please don't use it to bypass authentication, paywalls, or anti-abuse protections. The maintainers provide the software "as is" and are not responsible for how it is used (see LICENSE).
License
Crawlr is released under the MIT License — free to use, modify, and distribute, including commercially. See LICENSE.
Building a commercial or hosted product on top of Crawlr? An open-core model works well: keep the CLI and library MIT-licensed (this repo) and offer any hosted service, team features, or premium connectors as a separate paid layer.
Metadata
Release files for crawlr 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| crawlr-0.4.1.tar.gz | 87.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| crawlr-0.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 193.0 kB
Release files / crawlr-0.4.1.tar.gz
| Download URL | crawlr-0.4.1.tar.gz |
|---|---|
| Size | 87.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a83ccfd2022ee5884b7dc897c2d967778cc5e1233c544327c42a5f759db4cd85
|
|
BLAKE2b-256 checksum How to use checksums |
4cbd01e61a6f29b0c85f3a63a48b3ed18b6a05794b9b353257b70955a31af28a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 20, 2026.
Transparency logRelease files / crawlr-0.4.1-py3-none-any.whl
| Download URL | crawlr-0.4.1-py3-none-any.whl |
|---|---|
| Size | 105.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9aa65c3c07eafef77d822fcddea359b5d74e77388e2d6b45176ca8a7a545e4e9
|
|
BLAKE2b-256 checksum How to use checksums |
7eb9a2368db18392ed3532d16ffc534f3a243e95140a35d86615f52968dec42d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 20, 2026.
Transparency log