polite-scraper 1.0.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.
- polite_scraper-1.0.0/LICENSE +21 -0
- polite_scraper-1.0.0/PKG-INFO +283 -0
- polite_scraper-1.0.0/README.md +251 -0
- polite_scraper-1.0.0/polite_scraper/__init__.py +3 -0
- polite_scraper-1.0.0/polite_scraper/__main__.py +4 -0
- polite_scraper-1.0.0/polite_scraper/cli.py +186 -0
- polite_scraper-1.0.0/polite_scraper/config.py +40 -0
- polite_scraper-1.0.0/polite_scraper/crawler.py +59 -0
- polite_scraper-1.0.0/polite_scraper/extractor.py +92 -0
- polite_scraper-1.0.0/polite_scraper/fetcher.py +94 -0
- polite_scraper-1.0.0/polite_scraper/generic.py +153 -0
- polite_scraper-1.0.0/polite_scraper/main.py +224 -0
- polite_scraper-1.0.0/polite_scraper/models.py +60 -0
- polite_scraper-1.0.0/polite_scraper/reporter.py +71 -0
- polite_scraper-1.0.0/polite_scraper/ui.py +137 -0
- polite_scraper-1.0.0/polite_scraper/utils.py +75 -0
- polite_scraper-1.0.0/polite_scraper/validator.py +47 -0
- polite_scraper-1.0.0/polite_scraper.egg-info/PKG-INFO +283 -0
- polite_scraper-1.0.0/polite_scraper.egg-info/SOURCES.txt +24 -0
- polite_scraper-1.0.0/polite_scraper.egg-info/dependency_links.txt +1 -0
- polite_scraper-1.0.0/polite_scraper.egg-info/entry_points.txt +2 -0
- polite_scraper-1.0.0/polite_scraper.egg-info/requires.txt +7 -0
- polite_scraper-1.0.0/polite_scraper.egg-info/top_level.txt +1 -0
- polite_scraper-1.0.0/pyproject.toml +52 -0
- polite_scraper-1.0.0/setup.cfg +4 -0
- polite_scraper-1.0.0/tests/test_scraper.py +162 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 callmekriztal
|
|
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.
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: polite-scraper
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A rate-limited, cache-first web scraper with a rich terminal UI, robots.txt awareness, and Pydantic validation.
|
|
5
|
+
Author: callmekriztal
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/callmekriztal/polite_scapper
|
|
8
|
+
Project-URL: Repository, https://github.com/callmekriztal/polite_scapper
|
|
9
|
+
Project-URL: Issues, https://github.com/callmekriztal/polite_scapper/issues
|
|
10
|
+
Keywords: scraper,web-scraping,crawler,polite,beautifulsoup,cli
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: requests>=2.31.0
|
|
26
|
+
Requires-Dist: beautifulsoup4>=4.12.0
|
|
27
|
+
Requires-Dist: pydantic>=2.0.0
|
|
28
|
+
Requires-Dist: rich>=13.0.0
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
<div align="center">
|
|
34
|
+
|
|
35
|
+
# ๐ท๏ธ The Polite Scraper
|
|
36
|
+
|
|
37
|
+
**A rate-limited, cache-first web scraper with a polished terminal UI โ that treats every host with respect.**
|
|
38
|
+
|
|
39
|
+
[](https://www.python.org/)
|
|
40
|
+
[](tests/)
|
|
41
|
+
[](#two-modes)
|
|
42
|
+
[](https://github.com/Textualize/rich)
|
|
43
|
+
[](#%EF%B8%8F-politeness--ethics)
|
|
44
|
+
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
The Polite Scraper crawls websites the way a good citizen would: it identifies itself with a custom User-Agent, waits at least 500 ms between live requests, caches every page it downloads so re-runs cost the server **nothing**, validates everything it extracts against strict [Pydantic](https://docs.pydantic.dev/) schemas, and survives broken pages without crashing.
|
|
50
|
+
|
|
51
|
+
Give it a URL โ it figures out the rest.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python -m polite_scraper https://books.toscrape.com
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ The Polite Scraper โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
|
|
59
|
+
โ Target https://books.toscrape.com/ โ
|
|
60
|
+
โ Mode book โ
|
|
61
|
+
โ Run folder data/runs/books-toscrape-com โ
|
|
62
|
+
โ Max pages 3 โ
|
|
63
|
+
โ Min delay 0.5s between requests โ
|
|
64
|
+
โ Cache data/cache โ
|
|
65
|
+
โฐโโโโโโโโโ polite by design โ rate-limited, cached, identify-yourself โโโโโโโโโโฏ
|
|
66
|
+
|
|
67
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโ Discovering catalogue pages โโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
68
|
+
โ 3 catalogue pages crawled โ 60 unique book URLs
|
|
69
|
+
|
|
70
|
+
โโโโโโโโโโโโโโโโโโโโโ Extracting & validating book details โโโโโโโโโโโโโโโโโโโโโ
|
|
71
|
+
โ 60 valid books, 0 rejected
|
|
72
|
+
|
|
73
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Done โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
74
|
+
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Run Summary โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
|
|
75
|
+
โ Requests 0 network / 64 cache โ
|
|
76
|
+
โ Records saved 60 โ
|
|
77
|
+
โ Duration 0.772s โ
|
|
78
|
+
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## โจ Features
|
|
84
|
+
|
|
85
|
+
| | |
|
|
86
|
+
|---|---|
|
|
87
|
+
| ๐ฏ **One command, any site** | Pass the website as a plain argument โ no config files, no code changes |
|
|
88
|
+
| ๐ง **Auto mode detection** | Probes the target and picks the right extractor automatically |
|
|
89
|
+
| ๐พ **Disk cache** | Every fetched page is stored locally โ second runs are instant and offline |
|
|
90
|
+
| ๐ข **Rate limiting** | Hard 500 ms minimum delay between live HTTP requests (configurable) |
|
|
91
|
+
| ๐ชช **Identifiable** | Custom `User-Agent` sent with every request |
|
|
92
|
+
| ๐ค **robots.txt aware** | Generic mode consults `robots.txt` before crawling and aborts if disallowed |
|
|
93
|
+
| โ
**Strict validation** | Pydantic schemas reject malformed records into `errors.json` instead of crashing |
|
|
94
|
+
| ๐งฑ **Resilient** | A broken page fails alone โ the run continues and reports it |
|
|
95
|
+
| ๐ **Full reporting** | `run-report.json` with duration, cache hits, network fetches and record counts |
|
|
96
|
+
| ๐ฅ๏ธ **Clean terminal UI** | Progress bars, summary tables and panels via [`rich`](https://github.com/Textualize/rich) โ quiet by default, `-v` for full logs |
|
|
97
|
+
|
|
98
|
+
## ๐ฆ Two Modes
|
|
99
|
+
|
|
100
|
+
### `book` โ catalogue scraper
|
|
101
|
+
Built for e-commerce-style catalogues (originally [books.toscrape.com](https://books.toscrape.com/)). Follows pagination (`li.next a`), discovers product links, then extracts per-product: **title, price (raw + normalized), availability (raw + structured), rating, description, source page, timestamp**.
|
|
102
|
+
|
|
103
|
+
### `generic` โ any website
|
|
104
|
+
BFS-crawls same-domain links (rate-limited, robots.txt-checked) and extracts per-page metadata: **title, h1, meta description, headings, same-domain links, image count, word count**.
|
|
105
|
+
|
|
106
|
+
### `auto` โ the default
|
|
107
|
+
Fetches the seed page and checks for book-catalogue markup (`article.product_pod`). Book catalogue โ book mode. Anything else โ generic mode.
|
|
108
|
+
|
|
109
|
+
## ๐ Quickstart
|
|
110
|
+
|
|
111
|
+
**Option A โ install as a CLI tool** (recommended after `v1.0.0`):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# with pipx (isolated CLI install)
|
|
115
|
+
pipx install polite-scraper
|
|
116
|
+
|
|
117
|
+
# or from a clone
|
|
118
|
+
pip install .
|
|
119
|
+
polite-scraper https://books.toscrape.com
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Option B โ from source:**
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
git clone https://github.com/callmekriztal/polite_scapper.git
|
|
126
|
+
cd polite_scapper
|
|
127
|
+
|
|
128
|
+
python3 -m venv venv
|
|
129
|
+
source venv/bin/activate
|
|
130
|
+
pip install -r requirements.txt
|
|
131
|
+
|
|
132
|
+
# Book catalogue (auto-detected)
|
|
133
|
+
python -m polite_scraper https://books.toscrape.com
|
|
134
|
+
|
|
135
|
+
# Any other website, deeper crawl
|
|
136
|
+
python -m polite_scraper https://quotes.toscrape.com --mode generic --pages 5
|
|
137
|
+
|
|
138
|
+
# Bare domains get https:// added automatically
|
|
139
|
+
python -m polite_scraper example.com
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
With the installed CLI, the same commands are simply:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
polite-scraper https://books.toscrape.com
|
|
146
|
+
polite-scraper https://quotes.toscrape.com --mode generic --pages 5
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Every run gets **its own output folder**, named after the website:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
data/runs/
|
|
153
|
+
โโโ books-toscrape-com/ โ books.toscrape.com run
|
|
154
|
+
โโโ quotes-toscrape-com/ โ quotes.toscrape.com run
|
|
155
|
+
โโโ my-domain-org-2/ โ third run on my-domain.org (auto-suffixed)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
If a folder for that website already exists, you'll be asked to pick a new name โ or just press Enter to accept an auto-suffix (`-1`, `-2`, โฆ) so no run ever overwrites another. Override the name with `--name my-custom-run`.
|
|
159
|
+
|
|
160
|
+
## โจ๏ธ CLI Reference
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
usage: polite-scraper [-h] [--mode {auto,book,generic}] [--pages PAGES]
|
|
164
|
+
[--max-links MAX_LINKS] [--delay DELAY]
|
|
165
|
+
[--timeout TIMEOUT] [--force] [-v] [url]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
| Flag | Description | Default |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `url` | Website to scrape (required) | โ |
|
|
171
|
+
| `--mode auto\|book\|generic` | Scraper mode | `auto` |
|
|
172
|
+
| `--pages N` | Maximum pages to crawl | `3` |
|
|
173
|
+
| `--max-links N` | Generic mode: new same-domain links followed per page | `8` |
|
|
174
|
+
| `--delay SEC` | Minimum delay between live requests | `0.5` |
|
|
175
|
+
| `--timeout SEC` | Request timeout | `10` |
|
|
176
|
+
| `--force` | Ignore the local cache, re-fetch everything | off |
|
|
177
|
+
| `--name NAME` | Custom run folder name (default: derived from the website) | auto |
|
|
178
|
+
| `-v, --verbose` | Show per-request logs and raw record checkpoints | off |
|
|
179
|
+
|
|
180
|
+
Environment variables still work for `USER_AGENT`, `TIMEOUT`, `MIN_DELAY_SECONDS` and `BROKEN_TEST_URL` โ CLI flags win when both are set.
|
|
181
|
+
|
|
182
|
+
## ๐ Output
|
|
183
|
+
|
|
184
|
+
Every run writes into its own folder under `data/runs/`:
|
|
185
|
+
|
|
186
|
+
| File | Mode | Contents |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| `books.json` | book | Validated product records |
|
|
189
|
+
| `pages.json` | generic | Validated page-metadata records |
|
|
190
|
+
| `errors.json` | both | Validation / fetch failures with reasons |
|
|
191
|
+
| `run-report.json` | both | Full execution metrics |
|
|
192
|
+
|
|
193
|
+
<details>
|
|
194
|
+
<summary>Example record โ <code>books.json</code></summary>
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"title": "A Light in the Attic",
|
|
199
|
+
"product_url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
|
|
200
|
+
"price_text": "ยฃ51.77",
|
|
201
|
+
"price_gbp": 51.77,
|
|
202
|
+
"availability_text": "In stock (22 available)",
|
|
203
|
+
"rating_text": "Three",
|
|
204
|
+
"description": "It's hard to imagine a world without A Light in the Attic. This now-classic collection of poetry and drawings from Shel Silverstein celebrates its 20th anniversary...",
|
|
205
|
+
"source_page": "https://books.toscrape.com/catalogue/page-1.html",
|
|
206
|
+
"fetched_at": "2026-11-15T03:16:34.270989+00:00"
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
</details>
|
|
210
|
+
|
|
211
|
+
<details>
|
|
212
|
+
<summary>Example record โ <code>pages.json</code> (generic mode)</summary>
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"url": "https://quotes.toscrape.com/",
|
|
217
|
+
"title": "Quotes to Scrape",
|
|
218
|
+
"h1": "Quotes to Scrape",
|
|
219
|
+
"meta_description": null,
|
|
220
|
+
"headings": ["Quotes to Scrape", "Top Ten tags"],
|
|
221
|
+
"same_domain_links": ["https://quotes.toscrape.com/login", "..."],
|
|
222
|
+
"link_count": 47,
|
|
223
|
+
"image_count": 0,
|
|
224
|
+
"word_count": 282,
|
|
225
|
+
"fetched_at": "2026-11-15T08:44:55.123456+00:00"
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
</details>
|
|
229
|
+
|
|
230
|
+
## ๐๏ธ Architecture
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
โโโโโโโโโโโโโโโ
|
|
234
|
+
CLI argument โโโบ โ cli.py โ argparse โ Config
|
|
235
|
+
โโโโโโโโฌโโโโโโโ
|
|
236
|
+
โผ
|
|
237
|
+
โโโโโโโโโโโโโโโ
|
|
238
|
+
โ fetcher.py โ session ยท UA ยท rate limit ยท disk cache ยท robots.txt
|
|
239
|
+
โโโโโโโโฌโโโโโโโ
|
|
240
|
+
โโโโโโโโโโโโโโโดโโโโโโโโโโโโโโ
|
|
241
|
+
โผ โผ
|
|
242
|
+
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
|
|
243
|
+
โ crawler.py โ โ generic.py โ
|
|
244
|
+
โ (book mode) โ โ (any website)โ
|
|
245
|
+
โโโโโโโโฌโโโโโโโโ โโโโโโโโฌโโโโโโโโ
|
|
246
|
+
โผ โผ
|
|
247
|
+
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
|
|
248
|
+
โ extractor.py โ โ BFS links โ
|
|
249
|
+
โโโโโโโโฌโโโโโโโโ โโโโโโโโฌโโโโโโโโ
|
|
250
|
+
โโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
|
|
251
|
+
โผ
|
|
252
|
+
โโโโโโโโโโโโโโโโโ
|
|
253
|
+
โ validator.py โ Pydantic: BookRecord / PageRecord
|
|
254
|
+
โโโโโโโโโฌโโโโโโโโ
|
|
255
|
+
โผ
|
|
256
|
+
โโโโโโโโโโโโโโโโโ
|
|
257
|
+
โ reporter.py โ books.json ยท pages.json ยท errors.json ยท run-report.json
|
|
258
|
+
โโโโโโโโโโโโโโโโโ
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Each stage is isolated โ swapping the extractor or the target site never touches the fetch, validation, or reporting layers.
|
|
262
|
+
|
|
263
|
+
## ๐งช Testing
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
pytest tests/ -v
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Covers price/rating/availability normalization, Pydantic validation failures, broken-page simulation, generic extraction, and CLI config mapping.
|
|
270
|
+
|
|
271
|
+
## ๐ค Politeness & Ethics
|
|
272
|
+
|
|
273
|
+
- **Identifying `User-Agent`** on every request (configurable via `USER_AGENT`)
|
|
274
|
+
- **Minimum 500 ms delay** between live network requests โ cache reads never sleep
|
|
275
|
+
- **`robots.txt` consulted** in generic mode; the run aborts if our agent is disallowed
|
|
276
|
+
- **Only `200 OK` accepted** โ no retries that hammer a struggling server
|
|
277
|
+
- **Caching by default** so development re-runs cost the target server zero requests
|
|
278
|
+
|
|
279
|
+
> *I will not reuse this code on another site without checking its rules and terms first.*
|
|
280
|
+
|
|
281
|
+
## ๐ More Documentation
|
|
282
|
+
|
|
283
|
+
The deep-dive โ stage-by-stage design decisions, data schema tables, failure-handling hooks, and the internship assignment context โ lives in **[`docs/scraper.md`](docs/scraper.md)****.
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ๐ท๏ธ The Polite Scraper
|
|
4
|
+
|
|
5
|
+
**A rate-limited, cache-first web scraper with a polished terminal UI โ that treats every host with respect.**
|
|
6
|
+
|
|
7
|
+
[](https://www.python.org/)
|
|
8
|
+
[](tests/)
|
|
9
|
+
[](#two-modes)
|
|
10
|
+
[](https://github.com/Textualize/rich)
|
|
11
|
+
[](#%EF%B8%8F-politeness--ethics)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
The Polite Scraper crawls websites the way a good citizen would: it identifies itself with a custom User-Agent, waits at least 500 ms between live requests, caches every page it downloads so re-runs cost the server **nothing**, validates everything it extracts against strict [Pydantic](https://docs.pydantic.dev/) schemas, and survives broken pages without crashing.
|
|
18
|
+
|
|
19
|
+
Give it a URL โ it figures out the rest.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
python -m polite_scraper https://books.toscrape.com
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ The Polite Scraper โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
|
|
27
|
+
โ Target https://books.toscrape.com/ โ
|
|
28
|
+
โ Mode book โ
|
|
29
|
+
โ Run folder data/runs/books-toscrape-com โ
|
|
30
|
+
โ Max pages 3 โ
|
|
31
|
+
โ Min delay 0.5s between requests โ
|
|
32
|
+
โ Cache data/cache โ
|
|
33
|
+
โฐโโโโโโโโโ polite by design โ rate-limited, cached, identify-yourself โโโโโโโโโโฏ
|
|
34
|
+
|
|
35
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโ Discovering catalogue pages โโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
36
|
+
โ 3 catalogue pages crawled โ 60 unique book URLs
|
|
37
|
+
|
|
38
|
+
โโโโโโโโโโโโโโโโโโโโโ Extracting & validating book details โโโโโโโโโโโโโโโโโโโโโ
|
|
39
|
+
โ 60 valid books, 0 rejected
|
|
40
|
+
|
|
41
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Done โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
42
|
+
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Run Summary โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
|
|
43
|
+
โ Requests 0 network / 64 cache โ
|
|
44
|
+
โ Records saved 60 โ
|
|
45
|
+
โ Duration 0.772s โ
|
|
46
|
+
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## โจ Features
|
|
52
|
+
|
|
53
|
+
| | |
|
|
54
|
+
|---|---|
|
|
55
|
+
| ๐ฏ **One command, any site** | Pass the website as a plain argument โ no config files, no code changes |
|
|
56
|
+
| ๐ง **Auto mode detection** | Probes the target and picks the right extractor automatically |
|
|
57
|
+
| ๐พ **Disk cache** | Every fetched page is stored locally โ second runs are instant and offline |
|
|
58
|
+
| ๐ข **Rate limiting** | Hard 500 ms minimum delay between live HTTP requests (configurable) |
|
|
59
|
+
| ๐ชช **Identifiable** | Custom `User-Agent` sent with every request |
|
|
60
|
+
| ๐ค **robots.txt aware** | Generic mode consults `robots.txt` before crawling and aborts if disallowed |
|
|
61
|
+
| โ
**Strict validation** | Pydantic schemas reject malformed records into `errors.json` instead of crashing |
|
|
62
|
+
| ๐งฑ **Resilient** | A broken page fails alone โ the run continues and reports it |
|
|
63
|
+
| ๐ **Full reporting** | `run-report.json` with duration, cache hits, network fetches and record counts |
|
|
64
|
+
| ๐ฅ๏ธ **Clean terminal UI** | Progress bars, summary tables and panels via [`rich`](https://github.com/Textualize/rich) โ quiet by default, `-v` for full logs |
|
|
65
|
+
|
|
66
|
+
## ๐ฆ Two Modes
|
|
67
|
+
|
|
68
|
+
### `book` โ catalogue scraper
|
|
69
|
+
Built for e-commerce-style catalogues (originally [books.toscrape.com](https://books.toscrape.com/)). Follows pagination (`li.next a`), discovers product links, then extracts per-product: **title, price (raw + normalized), availability (raw + structured), rating, description, source page, timestamp**.
|
|
70
|
+
|
|
71
|
+
### `generic` โ any website
|
|
72
|
+
BFS-crawls same-domain links (rate-limited, robots.txt-checked) and extracts per-page metadata: **title, h1, meta description, headings, same-domain links, image count, word count**.
|
|
73
|
+
|
|
74
|
+
### `auto` โ the default
|
|
75
|
+
Fetches the seed page and checks for book-catalogue markup (`article.product_pod`). Book catalogue โ book mode. Anything else โ generic mode.
|
|
76
|
+
|
|
77
|
+
## ๐ Quickstart
|
|
78
|
+
|
|
79
|
+
**Option A โ install as a CLI tool** (recommended after `v1.0.0`):
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# with pipx (isolated CLI install)
|
|
83
|
+
pipx install polite-scraper
|
|
84
|
+
|
|
85
|
+
# or from a clone
|
|
86
|
+
pip install .
|
|
87
|
+
polite-scraper https://books.toscrape.com
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Option B โ from source:**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
git clone https://github.com/callmekriztal/polite_scapper.git
|
|
94
|
+
cd polite_scapper
|
|
95
|
+
|
|
96
|
+
python3 -m venv venv
|
|
97
|
+
source venv/bin/activate
|
|
98
|
+
pip install -r requirements.txt
|
|
99
|
+
|
|
100
|
+
# Book catalogue (auto-detected)
|
|
101
|
+
python -m polite_scraper https://books.toscrape.com
|
|
102
|
+
|
|
103
|
+
# Any other website, deeper crawl
|
|
104
|
+
python -m polite_scraper https://quotes.toscrape.com --mode generic --pages 5
|
|
105
|
+
|
|
106
|
+
# Bare domains get https:// added automatically
|
|
107
|
+
python -m polite_scraper example.com
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
With the installed CLI, the same commands are simply:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
polite-scraper https://books.toscrape.com
|
|
114
|
+
polite-scraper https://quotes.toscrape.com --mode generic --pages 5
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Every run gets **its own output folder**, named after the website:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
data/runs/
|
|
121
|
+
โโโ books-toscrape-com/ โ books.toscrape.com run
|
|
122
|
+
โโโ quotes-toscrape-com/ โ quotes.toscrape.com run
|
|
123
|
+
โโโ my-domain-org-2/ โ third run on my-domain.org (auto-suffixed)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
If a folder for that website already exists, you'll be asked to pick a new name โ or just press Enter to accept an auto-suffix (`-1`, `-2`, โฆ) so no run ever overwrites another. Override the name with `--name my-custom-run`.
|
|
127
|
+
|
|
128
|
+
## โจ๏ธ CLI Reference
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
usage: polite-scraper [-h] [--mode {auto,book,generic}] [--pages PAGES]
|
|
132
|
+
[--max-links MAX_LINKS] [--delay DELAY]
|
|
133
|
+
[--timeout TIMEOUT] [--force] [-v] [url]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
| Flag | Description | Default |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| `url` | Website to scrape (required) | โ |
|
|
139
|
+
| `--mode auto\|book\|generic` | Scraper mode | `auto` |
|
|
140
|
+
| `--pages N` | Maximum pages to crawl | `3` |
|
|
141
|
+
| `--max-links N` | Generic mode: new same-domain links followed per page | `8` |
|
|
142
|
+
| `--delay SEC` | Minimum delay between live requests | `0.5` |
|
|
143
|
+
| `--timeout SEC` | Request timeout | `10` |
|
|
144
|
+
| `--force` | Ignore the local cache, re-fetch everything | off |
|
|
145
|
+
| `--name NAME` | Custom run folder name (default: derived from the website) | auto |
|
|
146
|
+
| `-v, --verbose` | Show per-request logs and raw record checkpoints | off |
|
|
147
|
+
|
|
148
|
+
Environment variables still work for `USER_AGENT`, `TIMEOUT`, `MIN_DELAY_SECONDS` and `BROKEN_TEST_URL` โ CLI flags win when both are set.
|
|
149
|
+
|
|
150
|
+
## ๐ Output
|
|
151
|
+
|
|
152
|
+
Every run writes into its own folder under `data/runs/`:
|
|
153
|
+
|
|
154
|
+
| File | Mode | Contents |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| `books.json` | book | Validated product records |
|
|
157
|
+
| `pages.json` | generic | Validated page-metadata records |
|
|
158
|
+
| `errors.json` | both | Validation / fetch failures with reasons |
|
|
159
|
+
| `run-report.json` | both | Full execution metrics |
|
|
160
|
+
|
|
161
|
+
<details>
|
|
162
|
+
<summary>Example record โ <code>books.json</code></summary>
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"title": "A Light in the Attic",
|
|
167
|
+
"product_url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
|
|
168
|
+
"price_text": "ยฃ51.77",
|
|
169
|
+
"price_gbp": 51.77,
|
|
170
|
+
"availability_text": "In stock (22 available)",
|
|
171
|
+
"rating_text": "Three",
|
|
172
|
+
"description": "It's hard to imagine a world without A Light in the Attic. This now-classic collection of poetry and drawings from Shel Silverstein celebrates its 20th anniversary...",
|
|
173
|
+
"source_page": "https://books.toscrape.com/catalogue/page-1.html",
|
|
174
|
+
"fetched_at": "2026-11-15T03:16:34.270989+00:00"
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
</details>
|
|
178
|
+
|
|
179
|
+
<details>
|
|
180
|
+
<summary>Example record โ <code>pages.json</code> (generic mode)</summary>
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"url": "https://quotes.toscrape.com/",
|
|
185
|
+
"title": "Quotes to Scrape",
|
|
186
|
+
"h1": "Quotes to Scrape",
|
|
187
|
+
"meta_description": null,
|
|
188
|
+
"headings": ["Quotes to Scrape", "Top Ten tags"],
|
|
189
|
+
"same_domain_links": ["https://quotes.toscrape.com/login", "..."],
|
|
190
|
+
"link_count": 47,
|
|
191
|
+
"image_count": 0,
|
|
192
|
+
"word_count": 282,
|
|
193
|
+
"fetched_at": "2026-11-15T08:44:55.123456+00:00"
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
</details>
|
|
197
|
+
|
|
198
|
+
## ๐๏ธ Architecture
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
โโโโโโโโโโโโโโโ
|
|
202
|
+
CLI argument โโโบ โ cli.py โ argparse โ Config
|
|
203
|
+
โโโโโโโโฌโโโโโโโ
|
|
204
|
+
โผ
|
|
205
|
+
โโโโโโโโโโโโโโโ
|
|
206
|
+
โ fetcher.py โ session ยท UA ยท rate limit ยท disk cache ยท robots.txt
|
|
207
|
+
โโโโโโโโฌโโโโโโโ
|
|
208
|
+
โโโโโโโโโโโโโโโดโโโโโโโโโโโโโโ
|
|
209
|
+
โผ โผ
|
|
210
|
+
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
|
|
211
|
+
โ crawler.py โ โ generic.py โ
|
|
212
|
+
โ (book mode) โ โ (any website)โ
|
|
213
|
+
โโโโโโโโฌโโโโโโโโ โโโโโโโโฌโโโโโโโโ
|
|
214
|
+
โผ โผ
|
|
215
|
+
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
|
|
216
|
+
โ extractor.py โ โ BFS links โ
|
|
217
|
+
โโโโโโโโฌโโโโโโโโ โโโโโโโโฌโโโโโโโโ
|
|
218
|
+
โโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
|
|
219
|
+
โผ
|
|
220
|
+
โโโโโโโโโโโโโโโโโ
|
|
221
|
+
โ validator.py โ Pydantic: BookRecord / PageRecord
|
|
222
|
+
โโโโโโโโโฌโโโโโโโโ
|
|
223
|
+
โผ
|
|
224
|
+
โโโโโโโโโโโโโโโโโ
|
|
225
|
+
โ reporter.py โ books.json ยท pages.json ยท errors.json ยท run-report.json
|
|
226
|
+
โโโโโโโโโโโโโโโโโ
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Each stage is isolated โ swapping the extractor or the target site never touches the fetch, validation, or reporting layers.
|
|
230
|
+
|
|
231
|
+
## ๐งช Testing
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
pytest tests/ -v
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Covers price/rating/availability normalization, Pydantic validation failures, broken-page simulation, generic extraction, and CLI config mapping.
|
|
238
|
+
|
|
239
|
+
## ๐ค Politeness & Ethics
|
|
240
|
+
|
|
241
|
+
- **Identifying `User-Agent`** on every request (configurable via `USER_AGENT`)
|
|
242
|
+
- **Minimum 500 ms delay** between live network requests โ cache reads never sleep
|
|
243
|
+
- **`robots.txt` consulted** in generic mode; the run aborts if our agent is disallowed
|
|
244
|
+
- **Only `200 OK` accepted** โ no retries that hammer a struggling server
|
|
245
|
+
- **Caching by default** so development re-runs cost the target server zero requests
|
|
246
|
+
|
|
247
|
+
> *I will not reuse this code on another site without checking its rules and terms first.*
|
|
248
|
+
|
|
249
|
+
## ๐ More Documentation
|
|
250
|
+
|
|
251
|
+
The deep-dive โ stage-by-stage design decisions, data schema tables, failure-handling hooks, and the internship assignment context โ lives in **[`docs/scraper.md`](docs/scraper.md)****.
|