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.
- crawlr-0.1.0/.gitignore +25 -0
- crawlr-0.1.0/LICENSE +21 -0
- crawlr-0.1.0/PKG-INFO +326 -0
- crawlr-0.1.0/README.md +261 -0
- crawlr-0.1.0/crawlr/__init__.py +9 -0
- crawlr-0.1.0/crawlr/alerts.py +130 -0
- crawlr-0.1.0/crawlr/api.py +330 -0
- crawlr-0.1.0/crawlr/cli.py +369 -0
- crawlr-0.1.0/crawlr/config.py +109 -0
- crawlr-0.1.0/crawlr/db.py +153 -0
- crawlr-0.1.0/crawlr/extractor.py +152 -0
- crawlr-0.1.0/crawlr/fetcher.py +175 -0
- crawlr-0.1.0/crawlr/llm.py +268 -0
- crawlr-0.1.0/crawlr/models.py +125 -0
- crawlr-0.1.0/crawlr/monitor.py +185 -0
- crawlr-0.1.0/crawlr/scheduler.py +52 -0
- crawlr-0.1.0/crawlr/schemas.py +89 -0
- crawlr-0.1.0/crawlr/selector_cache.py +74 -0
- crawlr-0.1.0/crawlr/simplifier.py +89 -0
- crawlr-0.1.0/crawlr/storage.py +268 -0
- crawlr-0.1.0/crawlr/triggers.py +211 -0
- crawlr-0.1.0/crawlr/usage.py +73 -0
- crawlr-0.1.0/crawlr/validate.py +65 -0
- crawlr-0.1.0/crawlr/verticals/__init__.py +9 -0
- crawlr-0.1.0/crawlr/verticals/ecommerce.py +84 -0
- crawlr-0.1.0/pyproject.toml +75 -0
crawlr-0.1.0/.gitignore
ADDED
|
@@ -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 & 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 & 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"
|