crawlr 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,25 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+
9
+ # Virtual environments
10
+ .venv/
11
+ venv/
12
+
13
+ # Crawlr runtime data (SQLite DB, selector cache, snapshots)
14
+ .crawlr/
15
+
16
+ # Tooling caches
17
+ .pytest_cache/
18
+ .ruff_cache/
19
+ .mypy_cache/
20
+
21
+ # Env / secrets
22
+ .env
23
+
24
+ # OS
25
+ .DS_Store
crawlr-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ard Tree
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
crawlr-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,326 @@
1
+ Metadata-Version: 2.4
2
+ Name: crawlr
3
+ Version: 0.1.0
4
+ Summary: Crawlr: an AI-powered, self-healing web scraper with an e-commerce price-intelligence vertical
5
+ Project-URL: Homepage, https://github.com/ardfaiyaz/crawlr
6
+ Project-URL: Repository, https://github.com/ardfaiyaz/crawlr
7
+ Project-URL: Issues, https://github.com/ardfaiyaz/crawlr/issues
8
+ Author: Ard Tree
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Ard Tree
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: cli,ecommerce,llm,price-intelligence,price-monitoring,scraper,self-healing,web-scraping
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Environment :: Console
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: OS Independent
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
42
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
43
+ Requires-Python: >=3.10
44
+ Requires-Dist: fastapi>=0.111
45
+ Requires-Dist: httpx>=0.27
46
+ Requires-Dist: jinja2>=3.1
47
+ Requires-Dist: pydantic>=2.7
48
+ Requires-Dist: python-dotenv>=1.0
49
+ Requires-Dist: python-multipart>=0.0.9
50
+ Requires-Dist: pyyaml>=6.0
51
+ Requires-Dist: rich>=13.7
52
+ Requires-Dist: selectolax>=0.3.21
53
+ Requires-Dist: tenacity>=8.3
54
+ Requires-Dist: typer>=0.12
55
+ Requires-Dist: uvicorn>=0.30
56
+ Provides-Extra: dev
57
+ Requires-Dist: mypy>=1.10; extra == 'dev'
58
+ Requires-Dist: pytest>=8.2; extra == 'dev'
59
+ Requires-Dist: ruff>=0.4; extra == 'dev'
60
+ Provides-Extra: js
61
+ Requires-Dist: playwright>=1.44; extra == 'js'
62
+ Provides-Extra: postgres
63
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
64
+ Description-Content-Type: text/markdown
65
+
66
+ <p align="center">
67
+ <img src="crawlr-icon-logo.png" alt="crawlr" width="120" />
68
+ </p>
69
+
70
+ <h1 align="center">crawlr</h1>
71
+
72
+ <p align="center">
73
+ <strong>Crawlr</strong> is an AI-powered, <strong>self-healing</strong> web scraper with an
74
+ e-commerce / price-intelligence vertical.
75
+ </p>
76
+
77
+ ## Why Crawlr is different
78
+
79
+ Most "AI scrapers" pipe every page through an LLM — slow, expensive, and
80
+ non-deterministic. Crawlr uses the LLM (or an offline heuristic) **only to
81
+ generate and repair CSS selectors**, then extracts pages deterministically with
82
+ those cached selectors. When a site changes its markup and the selectors break,
83
+ Crawlr detects it and **regenerates the selectors automatically** (self-healing).
84
+
85
+ - **Cheap & fast:** deterministic extraction on every run; the model runs once per site/schema and only again on breakage.
86
+ - **Resilient:** self-healing survives layout changes that break traditional scrapers.
87
+ - **General-purpose core, vertical products:** the same engine powers any schema; e-commerce ships out of the box.
88
+ - **Runs offline:** works with zero API keys via a heuristic selector generator; add an OpenAI/Anthropic key for higher accuracy.
89
+ - **Continuous monitoring:** schedule scrapes, store time-series snapshots, and detect price/stock changes.
90
+
91
+ ## Features
92
+
93
+ - **Self-healing extraction** with LLM or offline heuristic selector generation.
94
+ - **Alerting** on changes via webhook, Slack, and email, with threshold rules (e.g. only price drops above N%).
95
+ - **Validation & confidence scoring** per run, surfaced in the CLI and dashboard.
96
+ - **LLM cost guardrails:** per-run call budget, content-hash cache to avoid re-billing identical pages, and spend accounting.
97
+ - **Anti-bot resilience:** proxy rotation, robots.txt compliance, randomized delay jitter, and optional User-Agent rotation.
98
+ - **Concurrent monitoring** of many sites via a bounded async runner.
99
+ - **Built-in scheduler daemon** (`crawlr monitor --daemon`) — no external cron required.
100
+ - **User-defined schemas** in YAML/JSON — add new verticals (jobs, real estate, leads) without code.
101
+ - **Dashboard** with add-site form, run-now buttons, health indicators, and price-history sparklines.
102
+ - **Pluggable storage:** SQLite by default, Postgres via `CRAWLR_DATABASE_URL`; Docker + docker-compose included.
103
+
104
+ ## Watchlist — the easy way
105
+
106
+ Track a competitor's price and stock in one command:
107
+
108
+ ```bash
109
+ crawlr watch "https://store.com/product/123" # track price + stock
110
+ crawlr watch "https://store.com/product/123" --target 25 # alert at/below $25
111
+ crawlr watch "https://store.com/product/123" --restock # alert when back in stock
112
+ crawlr watchlist # see current price, movement, stock
113
+ crawlr monitor --daemon # keep checking in the background
114
+ ```
115
+
116
+ Or use the **dashboard** (`crawlr serve`) — a black‑and‑white, iOS‑styled watchlist: paste a
117
+ product URL, pick a **trigger** from the dropdown (the filter for when you want to be alerted),
118
+ optionally set a target price, and click **Watch**.
119
+
120
+ ### Trigger filter
121
+
122
+ Choose per watch (CLI `--trigger` or the dashboard dropdown):
123
+
124
+ | Trigger | Alerts when |
125
+ |---------|-------------|
126
+ | `any_change` | any watched field changes |
127
+ | `price_drop` | the price goes down |
128
+ | `price_below` | price is at/below your target |
129
+ | `price_above` | price is at/above your target |
130
+ | `back_in_stock` | the item becomes available |
131
+ | `out_of_stock` | the item sells out |
132
+
133
+ ### Rules template — "what happens in different circumstances"
134
+
135
+ For richer logic across many situations, create an editable rules file:
136
+
137
+ ```bash
138
+ crawlr init # writes crawlr.rules.yaml
139
+ ```
140
+
141
+ ```yaml
142
+ default_action: ignore
143
+ rules:
144
+ - when: price_drops_below
145
+ amount: 25
146
+ action: alert
147
+ - when: back_in_stock
148
+ action: alert
149
+ - when: price_increases
150
+ action: ignore
151
+ ```
152
+
153
+ When `crawlr.rules.yaml` exists it takes precedence over per‑watch triggers, giving you a single
154
+ place to describe exactly what should happen in each circumstance.
155
+
156
+ ## Architecture
157
+
158
+ ```
159
+ fetch (static -> auto JS, proxies, robots, jitter) -> simplify DOM -> selector cache?
160
+ |-- hit -> deterministic extract --(broken?)--> self-heal
161
+ |-- miss -> LLM/heuristic generate selectors -> cache -> extract
162
+ |
163
+ validate + confidence score -> store run (SQLite/Postgres)
164
+ |
165
+ diff vs previous -> log changes -> alert sinks
166
+ ```
167
+
168
+ Modules:
169
+
170
+ | Module | Responsibility |
171
+ |--------|----------------|
172
+ | `fetcher.py` | Static HTTP + retries, proxy rotation, robots.txt, jitter, auto-escalation to Playwright |
173
+ | `simplifier.py` | Reduce HTML to a compact outline for the LLM (80–95% fewer tokens) |
174
+ | `llm.py` | Pluggable OpenAI/Anthropic selector generation + offline heuristic fallback |
175
+ | `usage.py` | LLM call budget + token/spend accounting |
176
+ | `extractor.py` | Self-healing deterministic extraction core |
177
+ | `validate.py` | Schema validation + confidence scoring |
178
+ | `selector_cache.py` | Selector cache keyed by host+schema and by page-content hash |
179
+ | `db.py` | SQLite/Postgres connection + dialect abstraction |
180
+ | `storage.py` | Sites, runs, records (time series), change log |
181
+ | `monitor.py` | Change detection + sync/async runners |
182
+ | `scheduler.py` | Polling daemon that runs due sites |
183
+ | `alerts.py` | Webhook / Slack / email sinks + threshold rules |
184
+ | `schemas.py` | Unified registry: built-in verticals + user YAML/JSON schemas |
185
+ | `verticals/ecommerce.py` | Ready-made `product` and `product_list` schemas |
186
+ | `cli.py` / `api.py` | Typer CLI + FastAPI dashboard |
187
+
188
+ ## Install
189
+
190
+ Install the CLI in one line — no clone needed:
191
+
192
+ ```bash
193
+ pipx install "git+https://github.com/ardfaiyaz/crawlr.git"
194
+ # once published to PyPI:
195
+ pipx install crawlr # or: pip install crawlr
196
+ ```
197
+
198
+ For development (from a clone):
199
+
200
+ ```bash
201
+ python -m venv .venv && source .venv/bin/activate
202
+ pip install -e '.[dev]'
203
+ # optional extras
204
+ pip install -e '.[js]' && playwright install chromium # JS rendering
205
+ pip install -e '.[postgres]' # Postgres backend
206
+ ```
207
+
208
+ ## Usage
209
+
210
+ ```bash
211
+ # One-off scrape (prints confidence + validity + LLM spend)
212
+ crawlr scrape https://example-store.com/search?q=laptop --schema product_list
213
+
214
+ # Monitor sites
215
+ crawlr add https://example-store.com/product/123 --schema product --interval 30
216
+ crawlr monitor # run all due sites once (concurrently)
217
+ crawlr monitor --daemon --poll 60 # run continuously (built-in scheduler)
218
+
219
+ # Schemas
220
+ crawlr schemas # list built-in + user schemas
221
+ crawlr validate-schema ./my-schema.yaml
222
+
223
+ # Inspect + dashboard
224
+ crawlr sites
225
+ crawlr changes
226
+ crawlr serve # http://127.0.0.1:8000
227
+ ```
228
+
229
+ ### Defining a custom schema (no code)
230
+
231
+ Drop a YAML file into `CRAWLR_SCHEMA_DIR` (default `<data dir>/schemas`):
232
+
233
+ ```yaml
234
+ name: jobs
235
+ item_selector: ".job-card"
236
+ fields:
237
+ - name: title
238
+ description: the job title
239
+ type: text
240
+ required: true
241
+ - name: salary
242
+ description: annual salary
243
+ type: number
244
+ ```
245
+
246
+ Then `crawlr scrape <url> --schema jobs`.
247
+
248
+ ## Configuration reference
249
+
250
+ | Variable | Default | Description |
251
+ |----------|---------|-------------|
252
+ | `CRAWLR_DATA_DIR` | `./.crawlr` | SQLite DB, selector cache, schemas |
253
+ | `CRAWLR_DATABASE_URL` | — | `postgresql://...` to use Postgres instead of SQLite |
254
+ | `CRAWLR_SCHEMA_DIR` | `<data>/schemas` | Directory scanned for user YAML/JSON schemas |
255
+ | `CRAWLR_LLM_PROVIDER` | `none` | `openai`, `anthropic`, or `none` (heuristic) |
256
+ | `CRAWLR_LLM_API_KEY` | — | API key for the chosen provider |
257
+ | `CRAWLR_LLM_MODEL` | provider default | Model override |
258
+ | `CRAWLR_LLM_MAX_CALLS` | `2` | Max LLM calls per scrape (cost guardrail) |
259
+ | `CRAWLR_PROXIES` | — | Comma-separated proxy URLs to rotate through |
260
+ | `CRAWLR_RESPECT_ROBOTS` | `true` | Honor robots.txt |
261
+ | `CRAWLR_JITTER` | `0.75` | Max random extra delay (s) per request |
262
+ | `CRAWLR_ROTATE_UA` | `false` | Rotate realistic User-Agent strings |
263
+ | `CRAWLR_ALERT_WEBHOOK` | — | Generic webhook URL for change alerts |
264
+ | `CRAWLR_ALERT_SLACK` | — | Slack incoming-webhook URL |
265
+ | `CRAWLR_ALERT_EMAIL_TO` | — | Comma-separated recipient emails |
266
+ | `CRAWLR_SMTP_HOST` / `_PORT` / `_USER` / `_PASSWORD` / `_FROM` | — | SMTP settings for email alerts |
267
+ | `CRAWLR_ALERT_MIN_DROP` | `0.0` | Only alert on price drops ≥ this fraction (0.1 = 10%) |
268
+
269
+ ## Docker
270
+
271
+ ```bash
272
+ # Dashboard + Postgres + background scheduler
273
+ docker compose up --build
274
+ # Dashboard at http://localhost:8000
275
+ ```
276
+
277
+ ## Development
278
+
279
+ ```bash
280
+ pip install -e '.[dev]'
281
+ pytest # offline test suite (extraction, self-heal, validation, alerts, schemas, async, dashboard)
282
+ ruff check . # lint
283
+ ```
284
+
285
+ The test suite runs fully offline (no network, no LLM key): `fetch` is
286
+ monkeypatched with local fixtures and selector generation uses the heuristic
287
+ path. The same portable SQL is exercised on SQLite in CI and on Postgres in
288
+ production.
289
+
290
+ ## Releasing to PyPI
291
+
292
+ Publishing is automated via GitHub Actions using **PyPI Trusted Publishing**
293
+ (OIDC — no API tokens to manage). One-time setup:
294
+
295
+ 1. On PyPI, create the project (or add a *pending publisher*) under
296
+ **Publishing → GitHub**, with: owner `ardfaiyaz`, repo `crawlr`,
297
+ workflow `publish.yml`, environment `pypi`.
298
+ 2. Then cut a release by pushing a version tag:
299
+
300
+ ```bash
301
+ git tag v0.1.0
302
+ git push origin v0.1.0
303
+ ```
304
+
305
+ The workflow builds the sdist + wheel, runs `twine check`, and publishes to PyPI.
306
+
307
+ > Note: confirm the `crawlr` name is available on PyPI first. If it's taken,
308
+ > change `name` in `pyproject.toml` (the CLI command can stay `crawlr`).
309
+
310
+ ## Website &amp; docs
311
+
312
+ Crawlr is a **command-line product**. The `web/` directory is a single static landing page
313
+ (hero, features, how to use it, and an FAQ) — it deploys free on Vercel:
314
+
315
+ 1. Import the repo at [vercel.com/new](https://vercel.com/new).
316
+ 2. Set **Root Directory** to `web`.
317
+ 3. Framework preset: **Other** (no build step — it's static HTML/CSS).
318
+ 4. Deploy. You get a public URL like `https://crawlr.vercel.app`.
319
+
320
+ Locally you can preview it with any static server, e.g. `python -m http.server -d web 3000`.
321
+
322
+ ## Roadmap
323
+
324
+ - Additional verticals shipped as YAML presets
325
+ - Richer dashboard charts and filtering
326
+ - Distributed workers / task queue for very large fleets
crawlr-0.1.0/README.md ADDED
@@ -0,0 +1,261 @@
1
+ <p align="center">
2
+ <img src="crawlr-icon-logo.png" alt="crawlr" width="120" />
3
+ </p>
4
+
5
+ <h1 align="center">crawlr</h1>
6
+
7
+ <p align="center">
8
+ <strong>Crawlr</strong> is an AI-powered, <strong>self-healing</strong> web scraper with an
9
+ e-commerce / price-intelligence vertical.
10
+ </p>
11
+
12
+ ## Why Crawlr is different
13
+
14
+ Most "AI scrapers" pipe every page through an LLM — slow, expensive, and
15
+ non-deterministic. Crawlr uses the LLM (or an offline heuristic) **only to
16
+ generate and repair CSS selectors**, then extracts pages deterministically with
17
+ those cached selectors. When a site changes its markup and the selectors break,
18
+ Crawlr detects it and **regenerates the selectors automatically** (self-healing).
19
+
20
+ - **Cheap & fast:** deterministic extraction on every run; the model runs once per site/schema and only again on breakage.
21
+ - **Resilient:** self-healing survives layout changes that break traditional scrapers.
22
+ - **General-purpose core, vertical products:** the same engine powers any schema; e-commerce ships out of the box.
23
+ - **Runs offline:** works with zero API keys via a heuristic selector generator; add an OpenAI/Anthropic key for higher accuracy.
24
+ - **Continuous monitoring:** schedule scrapes, store time-series snapshots, and detect price/stock changes.
25
+
26
+ ## Features
27
+
28
+ - **Self-healing extraction** with LLM or offline heuristic selector generation.
29
+ - **Alerting** on changes via webhook, Slack, and email, with threshold rules (e.g. only price drops above N%).
30
+ - **Validation & confidence scoring** per run, surfaced in the CLI and dashboard.
31
+ - **LLM cost guardrails:** per-run call budget, content-hash cache to avoid re-billing identical pages, and spend accounting.
32
+ - **Anti-bot resilience:** proxy rotation, robots.txt compliance, randomized delay jitter, and optional User-Agent rotation.
33
+ - **Concurrent monitoring** of many sites via a bounded async runner.
34
+ - **Built-in scheduler daemon** (`crawlr monitor --daemon`) — no external cron required.
35
+ - **User-defined schemas** in YAML/JSON — add new verticals (jobs, real estate, leads) without code.
36
+ - **Dashboard** with add-site form, run-now buttons, health indicators, and price-history sparklines.
37
+ - **Pluggable storage:** SQLite by default, Postgres via `CRAWLR_DATABASE_URL`; Docker + docker-compose included.
38
+
39
+ ## Watchlist — the easy way
40
+
41
+ Track a competitor's price and stock in one command:
42
+
43
+ ```bash
44
+ crawlr watch "https://store.com/product/123" # track price + stock
45
+ crawlr watch "https://store.com/product/123" --target 25 # alert at/below $25
46
+ crawlr watch "https://store.com/product/123" --restock # alert when back in stock
47
+ crawlr watchlist # see current price, movement, stock
48
+ crawlr monitor --daemon # keep checking in the background
49
+ ```
50
+
51
+ Or use the **dashboard** (`crawlr serve`) — a black‑and‑white, iOS‑styled watchlist: paste a
52
+ product URL, pick a **trigger** from the dropdown (the filter for when you want to be alerted),
53
+ optionally set a target price, and click **Watch**.
54
+
55
+ ### Trigger filter
56
+
57
+ Choose per watch (CLI `--trigger` or the dashboard dropdown):
58
+
59
+ | Trigger | Alerts when |
60
+ |---------|-------------|
61
+ | `any_change` | any watched field changes |
62
+ | `price_drop` | the price goes down |
63
+ | `price_below` | price is at/below your target |
64
+ | `price_above` | price is at/above your target |
65
+ | `back_in_stock` | the item becomes available |
66
+ | `out_of_stock` | the item sells out |
67
+
68
+ ### Rules template — "what happens in different circumstances"
69
+
70
+ For richer logic across many situations, create an editable rules file:
71
+
72
+ ```bash
73
+ crawlr init # writes crawlr.rules.yaml
74
+ ```
75
+
76
+ ```yaml
77
+ default_action: ignore
78
+ rules:
79
+ - when: price_drops_below
80
+ amount: 25
81
+ action: alert
82
+ - when: back_in_stock
83
+ action: alert
84
+ - when: price_increases
85
+ action: ignore
86
+ ```
87
+
88
+ When `crawlr.rules.yaml` exists it takes precedence over per‑watch triggers, giving you a single
89
+ place to describe exactly what should happen in each circumstance.
90
+
91
+ ## Architecture
92
+
93
+ ```
94
+ fetch (static -> auto JS, proxies, robots, jitter) -> simplify DOM -> selector cache?
95
+ |-- hit -> deterministic extract --(broken?)--> self-heal
96
+ |-- miss -> LLM/heuristic generate selectors -> cache -> extract
97
+ |
98
+ validate + confidence score -> store run (SQLite/Postgres)
99
+ |
100
+ diff vs previous -> log changes -> alert sinks
101
+ ```
102
+
103
+ Modules:
104
+
105
+ | Module | Responsibility |
106
+ |--------|----------------|
107
+ | `fetcher.py` | Static HTTP + retries, proxy rotation, robots.txt, jitter, auto-escalation to Playwright |
108
+ | `simplifier.py` | Reduce HTML to a compact outline for the LLM (80–95% fewer tokens) |
109
+ | `llm.py` | Pluggable OpenAI/Anthropic selector generation + offline heuristic fallback |
110
+ | `usage.py` | LLM call budget + token/spend accounting |
111
+ | `extractor.py` | Self-healing deterministic extraction core |
112
+ | `validate.py` | Schema validation + confidence scoring |
113
+ | `selector_cache.py` | Selector cache keyed by host+schema and by page-content hash |
114
+ | `db.py` | SQLite/Postgres connection + dialect abstraction |
115
+ | `storage.py` | Sites, runs, records (time series), change log |
116
+ | `monitor.py` | Change detection + sync/async runners |
117
+ | `scheduler.py` | Polling daemon that runs due sites |
118
+ | `alerts.py` | Webhook / Slack / email sinks + threshold rules |
119
+ | `schemas.py` | Unified registry: built-in verticals + user YAML/JSON schemas |
120
+ | `verticals/ecommerce.py` | Ready-made `product` and `product_list` schemas |
121
+ | `cli.py` / `api.py` | Typer CLI + FastAPI dashboard |
122
+
123
+ ## Install
124
+
125
+ Install the CLI in one line — no clone needed:
126
+
127
+ ```bash
128
+ pipx install "git+https://github.com/ardfaiyaz/crawlr.git"
129
+ # once published to PyPI:
130
+ pipx install crawlr # or: pip install crawlr
131
+ ```
132
+
133
+ For development (from a clone):
134
+
135
+ ```bash
136
+ python -m venv .venv && source .venv/bin/activate
137
+ pip install -e '.[dev]'
138
+ # optional extras
139
+ pip install -e '.[js]' && playwright install chromium # JS rendering
140
+ pip install -e '.[postgres]' # Postgres backend
141
+ ```
142
+
143
+ ## Usage
144
+
145
+ ```bash
146
+ # One-off scrape (prints confidence + validity + LLM spend)
147
+ crawlr scrape https://example-store.com/search?q=laptop --schema product_list
148
+
149
+ # Monitor sites
150
+ crawlr add https://example-store.com/product/123 --schema product --interval 30
151
+ crawlr monitor # run all due sites once (concurrently)
152
+ crawlr monitor --daemon --poll 60 # run continuously (built-in scheduler)
153
+
154
+ # Schemas
155
+ crawlr schemas # list built-in + user schemas
156
+ crawlr validate-schema ./my-schema.yaml
157
+
158
+ # Inspect + dashboard
159
+ crawlr sites
160
+ crawlr changes
161
+ crawlr serve # http://127.0.0.1:8000
162
+ ```
163
+
164
+ ### Defining a custom schema (no code)
165
+
166
+ Drop a YAML file into `CRAWLR_SCHEMA_DIR` (default `<data dir>/schemas`):
167
+
168
+ ```yaml
169
+ name: jobs
170
+ item_selector: ".job-card"
171
+ fields:
172
+ - name: title
173
+ description: the job title
174
+ type: text
175
+ required: true
176
+ - name: salary
177
+ description: annual salary
178
+ type: number
179
+ ```
180
+
181
+ Then `crawlr scrape <url> --schema jobs`.
182
+
183
+ ## Configuration reference
184
+
185
+ | Variable | Default | Description |
186
+ |----------|---------|-------------|
187
+ | `CRAWLR_DATA_DIR` | `./.crawlr` | SQLite DB, selector cache, schemas |
188
+ | `CRAWLR_DATABASE_URL` | — | `postgresql://...` to use Postgres instead of SQLite |
189
+ | `CRAWLR_SCHEMA_DIR` | `<data>/schemas` | Directory scanned for user YAML/JSON schemas |
190
+ | `CRAWLR_LLM_PROVIDER` | `none` | `openai`, `anthropic`, or `none` (heuristic) |
191
+ | `CRAWLR_LLM_API_KEY` | — | API key for the chosen provider |
192
+ | `CRAWLR_LLM_MODEL` | provider default | Model override |
193
+ | `CRAWLR_LLM_MAX_CALLS` | `2` | Max LLM calls per scrape (cost guardrail) |
194
+ | `CRAWLR_PROXIES` | — | Comma-separated proxy URLs to rotate through |
195
+ | `CRAWLR_RESPECT_ROBOTS` | `true` | Honor robots.txt |
196
+ | `CRAWLR_JITTER` | `0.75` | Max random extra delay (s) per request |
197
+ | `CRAWLR_ROTATE_UA` | `false` | Rotate realistic User-Agent strings |
198
+ | `CRAWLR_ALERT_WEBHOOK` | — | Generic webhook URL for change alerts |
199
+ | `CRAWLR_ALERT_SLACK` | — | Slack incoming-webhook URL |
200
+ | `CRAWLR_ALERT_EMAIL_TO` | — | Comma-separated recipient emails |
201
+ | `CRAWLR_SMTP_HOST` / `_PORT` / `_USER` / `_PASSWORD` / `_FROM` | — | SMTP settings for email alerts |
202
+ | `CRAWLR_ALERT_MIN_DROP` | `0.0` | Only alert on price drops ≥ this fraction (0.1 = 10%) |
203
+
204
+ ## Docker
205
+
206
+ ```bash
207
+ # Dashboard + Postgres + background scheduler
208
+ docker compose up --build
209
+ # Dashboard at http://localhost:8000
210
+ ```
211
+
212
+ ## Development
213
+
214
+ ```bash
215
+ pip install -e '.[dev]'
216
+ pytest # offline test suite (extraction, self-heal, validation, alerts, schemas, async, dashboard)
217
+ ruff check . # lint
218
+ ```
219
+
220
+ The test suite runs fully offline (no network, no LLM key): `fetch` is
221
+ monkeypatched with local fixtures and selector generation uses the heuristic
222
+ path. The same portable SQL is exercised on SQLite in CI and on Postgres in
223
+ production.
224
+
225
+ ## Releasing to PyPI
226
+
227
+ Publishing is automated via GitHub Actions using **PyPI Trusted Publishing**
228
+ (OIDC — no API tokens to manage). One-time setup:
229
+
230
+ 1. On PyPI, create the project (or add a *pending publisher*) under
231
+ **Publishing → GitHub**, with: owner `ardfaiyaz`, repo `crawlr`,
232
+ workflow `publish.yml`, environment `pypi`.
233
+ 2. Then cut a release by pushing a version tag:
234
+
235
+ ```bash
236
+ git tag v0.1.0
237
+ git push origin v0.1.0
238
+ ```
239
+
240
+ The workflow builds the sdist + wheel, runs `twine check`, and publishes to PyPI.
241
+
242
+ > Note: confirm the `crawlr` name is available on PyPI first. If it's taken,
243
+ > change `name` in `pyproject.toml` (the CLI command can stay `crawlr`).
244
+
245
+ ## Website &amp; docs
246
+
247
+ Crawlr is a **command-line product**. The `web/` directory is a single static landing page
248
+ (hero, features, how to use it, and an FAQ) — it deploys free on Vercel:
249
+
250
+ 1. Import the repo at [vercel.com/new](https://vercel.com/new).
251
+ 2. Set **Root Directory** to `web`.
252
+ 3. Framework preset: **Other** (no build step — it's static HTML/CSS).
253
+ 4. Deploy. You get a public URL like `https://crawlr.vercel.app`.
254
+
255
+ Locally you can preview it with any static server, e.g. `python -m http.server -d web 3000`.
256
+
257
+ ## Roadmap
258
+
259
+ - Additional verticals shipped as YAML presets
260
+ - Richer dashboard charts and filtering
261
+ - Distributed workers / task queue for very large fleets
@@ -0,0 +1,9 @@
1
+ """Crawlr: an AI-powered, self-healing web scraper.
2
+
3
+ Core idea: use an LLM to *generate and repair* deterministic CSS selectors
4
+ (the expensive, intelligent step) instead of extracting every page with the
5
+ LLM (the naive, slow, expensive approach). Deterministic selectors are cached
6
+ and reused; when they break, the extractor self-heals by regenerating them.
7
+ """
8
+
9
+ __version__ = "0.1.0"