webscour 0.1.1__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.
- webscour-0.1.1/LICENSE +21 -0
- webscour-0.1.1/PKG-INFO +324 -0
- webscour-0.1.1/README.md +300 -0
- webscour-0.1.1/pyproject.toml +80 -0
- webscour-0.1.1/searx/README.md +55 -0
- webscour-0.1.1/searx/install.sh +267 -0
- webscour-0.1.1/searx/probe-engines.py +323 -0
- webscour-0.1.1/searx/serve.py +43 -0
- webscour-0.1.1/setup.cfg +4 -0
- webscour-0.1.1/tests/test_argv.py +206 -0
- webscour-0.1.1/tests/test_cache.py +232 -0
- webscour-0.1.1/tests/test_cli.py +522 -0
- webscour-0.1.1/tests/test_extract.py +369 -0
- webscour-0.1.1/tests/test_fetch_cache.py +392 -0
- webscour-0.1.1/tests/test_mcp.py +243 -0
- webscour-0.1.1/tests/test_routes.py +273 -0
- webscour-0.1.1/tests/test_sources.py +311 -0
- webscour-0.1.1/webscour/__init__.py +3 -0
- webscour-0.1.1/webscour/__main__.py +13 -0
- webscour-0.1.1/webscour/argv.py +209 -0
- webscour-0.1.1/webscour/backends.py +619 -0
- webscour-0.1.1/webscour/cache.py +251 -0
- webscour-0.1.1/webscour/cli.py +2109 -0
- webscour-0.1.1/webscour/extract.py +1580 -0
- webscour-0.1.1/webscour/render_venv.py +320 -0
- webscour-0.1.1/webscour/routes.py +239 -0
- webscour-0.1.1/webscour/searx.py +118 -0
- webscour-0.1.1/webscour/sources.py +564 -0
- webscour-0.1.1/webscour.egg-info/PKG-INFO +324 -0
- webscour-0.1.1/webscour.egg-info/SOURCES.txt +31 -0
- webscour-0.1.1/webscour.egg-info/dependency_links.txt +1 -0
- webscour-0.1.1/webscour.egg-info/entry_points.txt +2 -0
- webscour-0.1.1/webscour.egg-info/top_level.txt +1 -0
webscour-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Furkan Cevik
|
|
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.
|
webscour-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: webscour
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: One keyless CLI for web search, page text, PDFs, sitemaps and a polite crawl
|
|
5
|
+
Author: Furkan Cevik
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/FurBlood344324/webscour
|
|
8
|
+
Project-URL: Issues, https://github.com/FurBlood344324/webscour/issues
|
|
9
|
+
Keywords: cli,search,web,scraper,pdf,searxng,crawler
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
<p align="center">
|
|
26
|
+
<img src="assets/logo.svg" alt="webscour" width="460">
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
<p align="center">
|
|
30
|
+
<b>webscour</b> is one keyless CLI for the web: search, page text, PDFs, sitemaps and a polite crawl.<br>
|
|
31
|
+
No API key. No account. No log.
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<p align="center">
|
|
35
|
+
<a href="https://pypi.org/project/webscour/"><img src="https://img.shields.io/pypi/v/webscour?color=black" alt="PyPI"></a>
|
|
36
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/licence-MIT-black" alt="MIT"></a>
|
|
37
|
+
<img src="https://img.shields.io/badge/python-3.11%2B-black" alt="Python 3.11+">
|
|
38
|
+
<img src="https://img.shields.io/badge/platform-linux-black" alt="Linux">
|
|
39
|
+
<img src="https://img.shields.io/badge/dependencies-none-black" alt="No dependencies">
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
`wsc` (the command webscour installs) asks a local SearXNG first, falls back to scrapers and
|
|
43
|
+
structured APIs, and prints text sized for a terminal instead of raw HTML. The standard library is the
|
|
44
|
+
whole stack; the only system binaries are the ones you choose to install for rendering and documents.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pipx install webscour # recommended: isolated, on PATH as `wsc`
|
|
50
|
+
uvx webscour search "pyproject vs setup.py" # no install at all
|
|
51
|
+
uv tool install webscour
|
|
52
|
+
pip install --user webscour
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or, on Linux, the installer that also sets up the search backend:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
curl -LsSf https://wsc.ashina.live/install.sh | sh
|
|
59
|
+
wsc setup # installs SearXNG as a systemd user unit
|
|
60
|
+
wsc doctor # one line: OK, DEGRADED or DOWN
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Install with an agent
|
|
64
|
+
|
|
65
|
+
Paste this into Claude Code, pi, dsh, or any agent that can run a shell:
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
Install webscour, a keyless web CLI. Run:
|
|
69
|
+
curl -LsSf https://wsc.ashina.live/install.sh | sh
|
|
70
|
+
Then run `wsc doctor`. A good report says lightpanda present and the SearXNG unit active, with
|
|
71
|
+
one VERDICT line ending in OK. If the installer is unavailable, fall back in this order:
|
|
72
|
+
`uv tool install webscour`, then `pipx install webscour`, then `python3 -m pip install --user
|
|
73
|
+
webscour` (skip the last one on Debian or Ubuntu if it refuses with externally-managed-environment).
|
|
74
|
+
|
|
75
|
+
Ask the user before running `wsc setup`, because it installs a systemd user unit on their machine.
|
|
76
|
+
Use exactly one install method. The skill file, if your agent supports skills, is at
|
|
77
|
+
https://raw.githubusercontent.com/FurBlood344324/webscour/main/skill/SKILL.md
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
From a checkout, the same thing without a build step:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
cd webscour
|
|
84
|
+
python3 -m webscour.cli search "pyproject vs setup.py"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**System requirements: python 3.11+, lightpanda, linux.** No Python dependencies are installed, by design:
|
|
88
|
+
`dependencies = []` in `pyproject.toml` is a decision, not an omission. Everything else is a system binary,
|
|
89
|
+
which pip cannot manage and should not pretend to.
|
|
90
|
+
|
|
91
|
+
- **lightpanda** is required. It is the render engine, and `wsc doctor` fails when it is missing.
|
|
92
|
+
- Linux only because `wsc setup` installs a systemd **user** unit for SearXNG. The crawler itself runs on
|
|
93
|
+
macOS, and lightpanda ships for it too; the limit is the service, not the scraper.
|
|
94
|
+
- Optional: `pdftotext` (poppler) for PDF, `pandoc` for docx/epub, `markitdown` as a last resort, and
|
|
95
|
+
playwright as a render fallback.
|
|
96
|
+
|
|
97
|
+
Install lightpanda from one of these, in the order that usually applies:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
yay -S lightpanda-bin # Arch / AUR; nightly variant: lightpanda-nightly-bin
|
|
101
|
+
curl -fsSL https://pkg.lightpanda.io/install.sh | bash -s "<version>" # any Linux; needs curl, jq, sha256sum
|
|
102
|
+
# Debian/Ubuntu: the .deb published with each tagged release
|
|
103
|
+
brew install lightpanda-io/browser/lightpanda # Homebrew
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The one-liner installs to `~/.local/bin/lightpanda`; `LIGHTPANDA_DIR` overrides the directory. `wsc setup`
|
|
107
|
+
verifies lightpanda on PATH and never installs it, because a package install that downloads and runs a
|
|
108
|
+
binary is something a user has to audit rather than trust.
|
|
109
|
+
|
|
110
|
+
### What is required, and what is optional
|
|
111
|
+
|
|
112
|
+
| piece | needed for | installed by |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| python 3.11+ | everything | you |
|
|
115
|
+
| SearXNG | `search --source web` | `wsc setup` (SearXNG only) |
|
|
116
|
+
| lightpanda | `--render`, and the retry of a hollow page | you, from the list above |
|
|
117
|
+
| playwright | the render fallback, only for a page lightpanda defeats | `wsc setup --playwright` |
|
|
118
|
+
| pdftotext / pandoc / markitdown | `wsc pdf`, and docx/epub | you, optional |
|
|
119
|
+
|
|
120
|
+
playwright is the one Python package this project can use, and the tool installs it into a directory
|
|
121
|
+
it owns rather than into your environment:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
wsc setup --playwright # ~/.local/share/webscour/render-venv, then chromium
|
|
125
|
+
wsc setup --playwright --with-deps # also let playwright install chromium's OS libraries (needs root)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`pip install webscour` never does this: setup is a command you run, not a post-install hook. The lookup
|
|
129
|
+
order for the fallback is `WEBSCOUR_PLAYWRIGHT_PYTHON`, that venv, the running interpreter, then
|
|
130
|
+
`python3`; it never searches your environment manager. `wsc doctor` reports the venv, its health, and
|
|
131
|
+
whether its chromium build still matches the installed playwright package. A missing playwright is a
|
|
132
|
+
note. A missing lightpanda is a failure.
|
|
133
|
+
|
|
134
|
+
`wsc setup` alone still means "install SearXNG"; `--all` does both installs in one run.
|
|
135
|
+
|
|
136
|
+
### Agent skill
|
|
137
|
+
|
|
138
|
+
The repo is also a pi package and a Claude Code marketplace.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pi install git:github.com/FurBlood344324/webscour # adds skill/SKILL.md as a skill
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`package.json` at the repo root declares the skill, because a git source is cloned whole and pi has no
|
|
145
|
+
subdirectory syntax; `pi/package.json` declares the same skill when `pi/` is the source instead.
|
|
146
|
+
|
|
147
|
+
Claude Code, one step (2.1.275 or later):
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
/plugin install webscour --marketplace FurBlood344324/webscour
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`.claude-plugin/marketplace.json` lists one plugin whose `source` is `./skill`, with `skills: ["."]` because
|
|
154
|
+
that directory is the skill itself rather than a folder of `<name>/SKILL.md` subdirectories.
|
|
155
|
+
|
|
156
|
+
## First run
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
wsc setup # installs the local SearXNG unit, then checks lightpanda
|
|
160
|
+
wsc doctor
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
unit active (enabled at boot)
|
|
165
|
+
listen 127.0.0.1:8888
|
|
166
|
+
engines 10 loaded
|
|
167
|
+
blocked abcnyheter, brave, quark, yacy
|
|
168
|
+
answered bing, mwmbl, naver, rawweb, seznam, yacy
|
|
169
|
+
silent abcnyheter, brave, quark, wikibooks
|
|
170
|
+
backends lightpanda=ok playwright=ok pdftotext=ok pandoc=ok markitdown=ok
|
|
171
|
+
VERDICT OK (6/10 engines answering, 4 blocked)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`wsc setup` is idempotent: a venv under `~/.local/share/searxng`, settings under `~/.config/searxng`, one
|
|
175
|
+
systemd user unit on loopback, no docker and no sudo. `--check` reports state without touching anything,
|
|
176
|
+
`--force` rebuilds the venv, `--no-start` installs without starting.
|
|
177
|
+
|
|
178
|
+
Then:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
wsc search "python packaging pyproject" --limit 3
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
1. Packaging Python Projects - Python Packaging User Guide - https://packaging.python.org/en/latest/tutorials/packaging-projects/ [naver,seznam]
|
|
186
|
+
This tutorial walks you through how to package a simple Python project. It will show you how to add the necessary files and structure to create the package, how to build the package, and how to uploa…
|
|
187
|
+
2. PEP 609 – Python Packaging Authority (PyPA) Governance | peps.py… - https://www.python.org/dev/peps/pep-0609 [mwmbl]
|
|
188
|
+
This document describes a governance model for the Python Packaging Authority (PyPA). The model is closely based on existing informal practices and the …
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Each row names the engines that produced it, so a result from one weak upstream is visible as such.
|
|
192
|
+
`--json` prints the same rows as objects. Exit codes: 0 answered, 2 nothing answered or a bad verb.
|
|
193
|
+
|
|
194
|
+
## Verbs
|
|
195
|
+
|
|
196
|
+
| Verb | What it does |
|
|
197
|
+
|---|---|
|
|
198
|
+
| `wsc search "<q>"` | one query, at most 30 rows, engine tags, optional `--json`, `--source` selects the backend |
|
|
199
|
+
| `wsc fetch <url>` | a page as text; `--max-chars`, `--grep` (context lines around a match), `--render`, `--render-engine`, `-v` |
|
|
200
|
+
| `wsc pdf <url\|path>` | a paper or manual as text, through pdftotext, pandoc or markitdown; `-v` names the backend |
|
|
201
|
+
| `wsc map <url>` | the URL list from sitemap, robots, llms.txt; `--limit` (default 200); `-v` names the source |
|
|
202
|
+
| `wsc crawl <url>` | links from that seed, same origin by default, `--depth`, `--limit`, `--any-origin`, `-v` |
|
|
203
|
+
| `wsc setup` | installs SearXNG and verifies lightpanda; `--playwright` installs the render fallback into its own venv, `--all` does both, `--check` reports without changing anything |
|
|
204
|
+
| `wsc doctor` | unit, listener, engine health, render backends, the render venv, one `VERDICT OK\|DEGRADED\|DOWN` line, `--json` |
|
|
205
|
+
| `wsc probe` | measures which engines this IP can use; `--quality --write` stores the survivors |
|
|
206
|
+
| `wsc cache list\|get\|prune\|path` | the on-disk captures under `./.webscour/`; `--save` writes one, `--fresh` bypasses a hit |
|
|
207
|
+
| `wsc mcp` | the same tool over stdio as a JSON-RPC MCP server, one tool named `web_scour` |
|
|
208
|
+
| `wsc tool-argv --json '<params>'` | the argv builder the MCP server and the pi extension both call, so neither owns a flag string |
|
|
209
|
+
| `wsc --self-test` | 29 offline checks of the parsers, the extraction rules, the router and the shell heuristic |
|
|
210
|
+
|
|
211
|
+
Output is the answer. By default stderr carries failures only, so `wsc fetch url > page.txt` leaves it
|
|
212
|
+
empty; `-v` explains every choice. When a page really is a JavaScript shell, plain HTTP comes back hollow
|
|
213
|
+
and one render is attempted; that fallback prints exactly one line, `render: lightpanda` or
|
|
214
|
+
`render: playwright`. A short page with real text is complete at any length, so `example.com` costs no
|
|
215
|
+
browser launch.
|
|
216
|
+
|
|
217
|
+
Two more stderr lines a caller can rely on: `route: <name>` when a known site is read through its own API
|
|
218
|
+
rather than scraped, and `cache: hit (2h ago)` when a capture is replayed instead of fetched again.
|
|
219
|
+
|
|
220
|
+
Exit codes: `0` the question was answered, `1` a usable answer was not produced and the reason is on
|
|
221
|
+
stderr (a 404, a block, an empty document), `2` the command line was wrong. A non-zero exit is a real
|
|
222
|
+
failure, never an empty result.
|
|
223
|
+
|
|
224
|
+
## Sources
|
|
225
|
+
|
|
226
|
+
| `--source` | Service | Notes |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| `web` (default) | local SearXNG, then DuckDuckGo lite, DuckDuckGo html, Bing | each row tagged `[naver,bing]`; a fallback prints one `backend <name>: <reason>` line |
|
|
229
|
+
| `github` | api.github.com repositories and issues | unauthenticated, 10 requests/min, remaining budget printed |
|
|
230
|
+
| `so` | Stack Exchange API, `site=stackoverflow` | answered/open, tags |
|
|
231
|
+
| `hn` | hn.algolia.com | story points, comment counts |
|
|
232
|
+
| `arxiv` | export.arxiv.org Atom | title, id URL, abstract |
|
|
233
|
+
| `discourse:<base>` | `<base>/search.json` | e.g. `discourse:https://forums.developer.nvidia.com` |
|
|
234
|
+
|
|
235
|
+
A `fetch` on a forum topic URL (`forums.developer.nvidia.com/t/...`) reads the topic's `.json` form and
|
|
236
|
+
prints `#post author` with plain text, which is far cleaner than the HTML and needs no render.
|
|
237
|
+
|
|
238
|
+
## Privacy
|
|
239
|
+
|
|
240
|
+
Only the query leaves the machine, through the local SearXNG to the upstream engines. No cwd, no host, no
|
|
241
|
+
user, no project name, no identifiers, no telemetry, and no log file: the tool writes nothing except its
|
|
242
|
+
one-second pacing stamp in `~/.cache/web/last-live`.
|
|
243
|
+
|
|
244
|
+
The SearXNG unit drops the query from its own logs. Access log off, level WARNING, autocomplete off (no
|
|
245
|
+
keystroke leaves the box), and the bundled `serve.py` filters every log record so that any URL query
|
|
246
|
+
string becomes `?<query withheld>`. The journal keeps engine errors and none of the terms. That is a
|
|
247
|
+
feature, and the reason the unit runs a wrapper instead of stock SearXNG.
|
|
248
|
+
|
|
249
|
+
This is not anonymity. Your IP talks to whichever upstream engines are enabled; a local proxy does not
|
|
250
|
+
change that. Send generic technical terms and keep private context out of a query.
|
|
251
|
+
|
|
252
|
+
`map` and `crawl` send only a URL to the target site, never a query, and every live request is paced to one
|
|
253
|
+
per second across processes, so a shell loop cannot look like a scraper.
|
|
254
|
+
|
|
255
|
+
## How it differs from firecrawl, and from an MCP server
|
|
256
|
+
|
|
257
|
+
firecrawl is a hosted pipeline for turning sites into clean markdown at scale: crawl jobs, JS rendering
|
|
258
|
+
farms, structured extraction, and an API key. webscour is the opposite shape, a local tool for one page or
|
|
259
|
+
one query, with no account, no credits and no state, and it tells you which engine or backend answered
|
|
260
|
+
instead of hiding the plumbing. An MCP server is a transport: it puts the same kind of tool behind a
|
|
261
|
+
protocol so a chat client can call it, and then something still has to be installed, run and authenticated.
|
|
262
|
+
`wsc` is a plain executable that an agent, a shell script or a human calls the same way, which is why it has
|
|
263
|
+
no server, no registered tool, and no process to keep alive. If you already run an MCP server for this, the
|
|
264
|
+
only thing webscour adds is the local search backend and the pacing.
|
|
265
|
+
|
|
266
|
+
## Limitations
|
|
267
|
+
|
|
268
|
+
- **Linux only**, because of the systemd user unit. Without the unit the scrapers answer from a residential
|
|
269
|
+
IP, and mostly they answer CAPTCHA.
|
|
270
|
+
- **The scrapers degrade on residential IPs.** Measured on 2026-11-13 with 30 generic queries, per engine;
|
|
271
|
+
a meta-search needs coverage (how often an engine helps) and precision (share of its rows that are on
|
|
272
|
+
topic), kept means coverage >= 10 % and precision >= 60 %:
|
|
273
|
+
|
|
274
|
+
| engine | cover | prec | rows | | engine | cover | prec | rows |
|
|
275
|
+
|---|---|---|---|---|---|---|---|---|
|
|
276
|
+
| naver | 90.0 % | 80.1 % | 403 | | wikibooks | 20.0 % | 93.8 % | 16 |
|
|
277
|
+
| abcnyheter | 70.0 % | 95.3 % | 214 | | wiby | 13.3 % | 42.9 % | 21 (dropped) |
|
|
278
|
+
| rawweb | 53.3 % | 98.8 % | 82 | | wikiversity, wikiquote | <=6.7 % | 100 % | 1-2 (dropped) |
|
|
279
|
+
| bing | 36.7 % | 60.0 % | 180 | | encyclosearch, wikisource | 3.3 % | 50 % | 2-14 (dropped) |
|
|
280
|
+
| yacy | 30.0 % | 96.7 % | 90 | | brave | 0 % this pass | n/a | kept by name |
|
|
281
|
+
|
|
282
|
+
duckduckgo, google, mojeek, qwant, startpage, searchmysite and yandex answered CAPTCHA, "access denied"
|
|
283
|
+
or a parsing error from a residential IP and are not in the set. Median merged query 5.0 s. Kept:
|
|
284
|
+
naver, abcnyheter, rawweb, bing, yacy, wikibooks, brave. `wsc probe --quality --write` repeats the
|
|
285
|
+
measurement on your own IP; the table above is one machine on one day, not a promise.
|
|
286
|
+
- **No render backend is installed by pip.** lightpanda is the one to install, and playwright is the
|
|
287
|
+
fallback for a page that defeats it. `wsc doctor` says which is present.
|
|
288
|
+
- **Scanned PDFs have no text layer.** `wsc pdf` says so and stops; OCR is out of scope.
|
|
289
|
+
- **Snippets are quotes from a scraped page.** Open the URL before relying on a claim, and treat a row whose
|
|
290
|
+
title and url lack every query term as drift, which the tool drops.
|
|
291
|
+
- **GitHub is unauthenticated**: 10 requests/min per IP, and `--source github` makes two calls.
|
|
292
|
+
|
|
293
|
+
## Development
|
|
294
|
+
|
|
295
|
+
Everything a release needs runs on this machine, offline:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
python3 -m venv .venv-dev && .venv-dev/bin/pip install ruff build twine
|
|
299
|
+
./scripts/check.sh # lint, format, tests, --self-test, build, twine, guards
|
|
300
|
+
BENCH=1 ./scripts/check.sh # also the extraction benchmark (needs the network)
|
|
301
|
+
./scripts/release.sh 0.2.0 # local: gate, stamp, changelog, tag. Never pushes or publishes
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`scripts/check.sh` is the only definition of "releasable": it prints one `PASS <name>` line per
|
|
305
|
+
check and a summary, and the workflows in `.github/workflows/` are thin wrappers that call it. It
|
|
306
|
+
runs with no network, no SearXNG and `LIGHTPANDA=0`.
|
|
307
|
+
|
|
308
|
+
The extraction benchmark compares our reading text with trafilatura's on six pages, from captured
|
|
309
|
+
fixtures in `tests/fixtures/bench/`, and fails when a page regresses past `tests/baselines/`. The
|
|
310
|
+
reference extractor is a throwaway venv, never a dependency:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
python3 -m venv /tmp/tfvenv && /tmp/tfvenv/bin/pip install trafilatura
|
|
314
|
+
python3 tests/bench_extract.py # table, and a check against the baseline
|
|
315
|
+
python3 tests/bench_extract.py --live # re-fetch the pages first
|
|
316
|
+
python3 tests/bench_extract.py --save-baseline tests/baselines/0.2.0.json
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Design decisions live in `docs/decisions/`, one short record each, with the evidence and the
|
|
320
|
+
condition that would reverse it.
|
|
321
|
+
|
|
322
|
+
## License
|
|
323
|
+
|
|
324
|
+
MIT. See [LICENSE](LICENSE).
|
webscour-0.1.1/README.md
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/logo.svg" alt="webscour" width="460">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<b>webscour</b> is one keyless CLI for the web: search, page text, PDFs, sitemaps and a polite crawl.<br>
|
|
7
|
+
No API key. No account. No log.
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="https://pypi.org/project/webscour/"><img src="https://img.shields.io/pypi/v/webscour?color=black" alt="PyPI"></a>
|
|
12
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/licence-MIT-black" alt="MIT"></a>
|
|
13
|
+
<img src="https://img.shields.io/badge/python-3.11%2B-black" alt="Python 3.11+">
|
|
14
|
+
<img src="https://img.shields.io/badge/platform-linux-black" alt="Linux">
|
|
15
|
+
<img src="https://img.shields.io/badge/dependencies-none-black" alt="No dependencies">
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
`wsc` (the command webscour installs) asks a local SearXNG first, falls back to scrapers and
|
|
19
|
+
structured APIs, and prints text sized for a terminal instead of raw HTML. The standard library is the
|
|
20
|
+
whole stack; the only system binaries are the ones you choose to install for rendering and documents.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pipx install webscour # recommended: isolated, on PATH as `wsc`
|
|
26
|
+
uvx webscour search "pyproject vs setup.py" # no install at all
|
|
27
|
+
uv tool install webscour
|
|
28
|
+
pip install --user webscour
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or, on Linux, the installer that also sets up the search backend:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
curl -LsSf https://wsc.ashina.live/install.sh | sh
|
|
35
|
+
wsc setup # installs SearXNG as a systemd user unit
|
|
36
|
+
wsc doctor # one line: OK, DEGRADED or DOWN
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Install with an agent
|
|
40
|
+
|
|
41
|
+
Paste this into Claude Code, pi, dsh, or any agent that can run a shell:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
Install webscour, a keyless web CLI. Run:
|
|
45
|
+
curl -LsSf https://wsc.ashina.live/install.sh | sh
|
|
46
|
+
Then run `wsc doctor`. A good report says lightpanda present and the SearXNG unit active, with
|
|
47
|
+
one VERDICT line ending in OK. If the installer is unavailable, fall back in this order:
|
|
48
|
+
`uv tool install webscour`, then `pipx install webscour`, then `python3 -m pip install --user
|
|
49
|
+
webscour` (skip the last one on Debian or Ubuntu if it refuses with externally-managed-environment).
|
|
50
|
+
|
|
51
|
+
Ask the user before running `wsc setup`, because it installs a systemd user unit on their machine.
|
|
52
|
+
Use exactly one install method. The skill file, if your agent supports skills, is at
|
|
53
|
+
https://raw.githubusercontent.com/FurBlood344324/webscour/main/skill/SKILL.md
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
From a checkout, the same thing without a build step:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
cd webscour
|
|
60
|
+
python3 -m webscour.cli search "pyproject vs setup.py"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**System requirements: python 3.11+, lightpanda, linux.** No Python dependencies are installed, by design:
|
|
64
|
+
`dependencies = []` in `pyproject.toml` is a decision, not an omission. Everything else is a system binary,
|
|
65
|
+
which pip cannot manage and should not pretend to.
|
|
66
|
+
|
|
67
|
+
- **lightpanda** is required. It is the render engine, and `wsc doctor` fails when it is missing.
|
|
68
|
+
- Linux only because `wsc setup` installs a systemd **user** unit for SearXNG. The crawler itself runs on
|
|
69
|
+
macOS, and lightpanda ships for it too; the limit is the service, not the scraper.
|
|
70
|
+
- Optional: `pdftotext` (poppler) for PDF, `pandoc` for docx/epub, `markitdown` as a last resort, and
|
|
71
|
+
playwright as a render fallback.
|
|
72
|
+
|
|
73
|
+
Install lightpanda from one of these, in the order that usually applies:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
yay -S lightpanda-bin # Arch / AUR; nightly variant: lightpanda-nightly-bin
|
|
77
|
+
curl -fsSL https://pkg.lightpanda.io/install.sh | bash -s "<version>" # any Linux; needs curl, jq, sha256sum
|
|
78
|
+
# Debian/Ubuntu: the .deb published with each tagged release
|
|
79
|
+
brew install lightpanda-io/browser/lightpanda # Homebrew
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The one-liner installs to `~/.local/bin/lightpanda`; `LIGHTPANDA_DIR` overrides the directory. `wsc setup`
|
|
83
|
+
verifies lightpanda on PATH and never installs it, because a package install that downloads and runs a
|
|
84
|
+
binary is something a user has to audit rather than trust.
|
|
85
|
+
|
|
86
|
+
### What is required, and what is optional
|
|
87
|
+
|
|
88
|
+
| piece | needed for | installed by |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| python 3.11+ | everything | you |
|
|
91
|
+
| SearXNG | `search --source web` | `wsc setup` (SearXNG only) |
|
|
92
|
+
| lightpanda | `--render`, and the retry of a hollow page | you, from the list above |
|
|
93
|
+
| playwright | the render fallback, only for a page lightpanda defeats | `wsc setup --playwright` |
|
|
94
|
+
| pdftotext / pandoc / markitdown | `wsc pdf`, and docx/epub | you, optional |
|
|
95
|
+
|
|
96
|
+
playwright is the one Python package this project can use, and the tool installs it into a directory
|
|
97
|
+
it owns rather than into your environment:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
wsc setup --playwright # ~/.local/share/webscour/render-venv, then chromium
|
|
101
|
+
wsc setup --playwright --with-deps # also let playwright install chromium's OS libraries (needs root)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`pip install webscour` never does this: setup is a command you run, not a post-install hook. The lookup
|
|
105
|
+
order for the fallback is `WEBSCOUR_PLAYWRIGHT_PYTHON`, that venv, the running interpreter, then
|
|
106
|
+
`python3`; it never searches your environment manager. `wsc doctor` reports the venv, its health, and
|
|
107
|
+
whether its chromium build still matches the installed playwright package. A missing playwright is a
|
|
108
|
+
note. A missing lightpanda is a failure.
|
|
109
|
+
|
|
110
|
+
`wsc setup` alone still means "install SearXNG"; `--all` does both installs in one run.
|
|
111
|
+
|
|
112
|
+
### Agent skill
|
|
113
|
+
|
|
114
|
+
The repo is also a pi package and a Claude Code marketplace.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pi install git:github.com/FurBlood344324/webscour # adds skill/SKILL.md as a skill
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`package.json` at the repo root declares the skill, because a git source is cloned whole and pi has no
|
|
121
|
+
subdirectory syntax; `pi/package.json` declares the same skill when `pi/` is the source instead.
|
|
122
|
+
|
|
123
|
+
Claude Code, one step (2.1.275 or later):
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
/plugin install webscour --marketplace FurBlood344324/webscour
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`.claude-plugin/marketplace.json` lists one plugin whose `source` is `./skill`, with `skills: ["."]` because
|
|
130
|
+
that directory is the skill itself rather than a folder of `<name>/SKILL.md` subdirectories.
|
|
131
|
+
|
|
132
|
+
## First run
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
wsc setup # installs the local SearXNG unit, then checks lightpanda
|
|
136
|
+
wsc doctor
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
unit active (enabled at boot)
|
|
141
|
+
listen 127.0.0.1:8888
|
|
142
|
+
engines 10 loaded
|
|
143
|
+
blocked abcnyheter, brave, quark, yacy
|
|
144
|
+
answered bing, mwmbl, naver, rawweb, seznam, yacy
|
|
145
|
+
silent abcnyheter, brave, quark, wikibooks
|
|
146
|
+
backends lightpanda=ok playwright=ok pdftotext=ok pandoc=ok markitdown=ok
|
|
147
|
+
VERDICT OK (6/10 engines answering, 4 blocked)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`wsc setup` is idempotent: a venv under `~/.local/share/searxng`, settings under `~/.config/searxng`, one
|
|
151
|
+
systemd user unit on loopback, no docker and no sudo. `--check` reports state without touching anything,
|
|
152
|
+
`--force` rebuilds the venv, `--no-start` installs without starting.
|
|
153
|
+
|
|
154
|
+
Then:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
wsc search "python packaging pyproject" --limit 3
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
1. Packaging Python Projects - Python Packaging User Guide - https://packaging.python.org/en/latest/tutorials/packaging-projects/ [naver,seznam]
|
|
162
|
+
This tutorial walks you through how to package a simple Python project. It will show you how to add the necessary files and structure to create the package, how to build the package, and how to uploa…
|
|
163
|
+
2. PEP 609 – Python Packaging Authority (PyPA) Governance | peps.py… - https://www.python.org/dev/peps/pep-0609 [mwmbl]
|
|
164
|
+
This document describes a governance model for the Python Packaging Authority (PyPA). The model is closely based on existing informal practices and the …
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Each row names the engines that produced it, so a result from one weak upstream is visible as such.
|
|
168
|
+
`--json` prints the same rows as objects. Exit codes: 0 answered, 2 nothing answered or a bad verb.
|
|
169
|
+
|
|
170
|
+
## Verbs
|
|
171
|
+
|
|
172
|
+
| Verb | What it does |
|
|
173
|
+
|---|---|
|
|
174
|
+
| `wsc search "<q>"` | one query, at most 30 rows, engine tags, optional `--json`, `--source` selects the backend |
|
|
175
|
+
| `wsc fetch <url>` | a page as text; `--max-chars`, `--grep` (context lines around a match), `--render`, `--render-engine`, `-v` |
|
|
176
|
+
| `wsc pdf <url\|path>` | a paper or manual as text, through pdftotext, pandoc or markitdown; `-v` names the backend |
|
|
177
|
+
| `wsc map <url>` | the URL list from sitemap, robots, llms.txt; `--limit` (default 200); `-v` names the source |
|
|
178
|
+
| `wsc crawl <url>` | links from that seed, same origin by default, `--depth`, `--limit`, `--any-origin`, `-v` |
|
|
179
|
+
| `wsc setup` | installs SearXNG and verifies lightpanda; `--playwright` installs the render fallback into its own venv, `--all` does both, `--check` reports without changing anything |
|
|
180
|
+
| `wsc doctor` | unit, listener, engine health, render backends, the render venv, one `VERDICT OK\|DEGRADED\|DOWN` line, `--json` |
|
|
181
|
+
| `wsc probe` | measures which engines this IP can use; `--quality --write` stores the survivors |
|
|
182
|
+
| `wsc cache list\|get\|prune\|path` | the on-disk captures under `./.webscour/`; `--save` writes one, `--fresh` bypasses a hit |
|
|
183
|
+
| `wsc mcp` | the same tool over stdio as a JSON-RPC MCP server, one tool named `web_scour` |
|
|
184
|
+
| `wsc tool-argv --json '<params>'` | the argv builder the MCP server and the pi extension both call, so neither owns a flag string |
|
|
185
|
+
| `wsc --self-test` | 29 offline checks of the parsers, the extraction rules, the router and the shell heuristic |
|
|
186
|
+
|
|
187
|
+
Output is the answer. By default stderr carries failures only, so `wsc fetch url > page.txt` leaves it
|
|
188
|
+
empty; `-v` explains every choice. When a page really is a JavaScript shell, plain HTTP comes back hollow
|
|
189
|
+
and one render is attempted; that fallback prints exactly one line, `render: lightpanda` or
|
|
190
|
+
`render: playwright`. A short page with real text is complete at any length, so `example.com` costs no
|
|
191
|
+
browser launch.
|
|
192
|
+
|
|
193
|
+
Two more stderr lines a caller can rely on: `route: <name>` when a known site is read through its own API
|
|
194
|
+
rather than scraped, and `cache: hit (2h ago)` when a capture is replayed instead of fetched again.
|
|
195
|
+
|
|
196
|
+
Exit codes: `0` the question was answered, `1` a usable answer was not produced and the reason is on
|
|
197
|
+
stderr (a 404, a block, an empty document), `2` the command line was wrong. A non-zero exit is a real
|
|
198
|
+
failure, never an empty result.
|
|
199
|
+
|
|
200
|
+
## Sources
|
|
201
|
+
|
|
202
|
+
| `--source` | Service | Notes |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| `web` (default) | local SearXNG, then DuckDuckGo lite, DuckDuckGo html, Bing | each row tagged `[naver,bing]`; a fallback prints one `backend <name>: <reason>` line |
|
|
205
|
+
| `github` | api.github.com repositories and issues | unauthenticated, 10 requests/min, remaining budget printed |
|
|
206
|
+
| `so` | Stack Exchange API, `site=stackoverflow` | answered/open, tags |
|
|
207
|
+
| `hn` | hn.algolia.com | story points, comment counts |
|
|
208
|
+
| `arxiv` | export.arxiv.org Atom | title, id URL, abstract |
|
|
209
|
+
| `discourse:<base>` | `<base>/search.json` | e.g. `discourse:https://forums.developer.nvidia.com` |
|
|
210
|
+
|
|
211
|
+
A `fetch` on a forum topic URL (`forums.developer.nvidia.com/t/...`) reads the topic's `.json` form and
|
|
212
|
+
prints `#post author` with plain text, which is far cleaner than the HTML and needs no render.
|
|
213
|
+
|
|
214
|
+
## Privacy
|
|
215
|
+
|
|
216
|
+
Only the query leaves the machine, through the local SearXNG to the upstream engines. No cwd, no host, no
|
|
217
|
+
user, no project name, no identifiers, no telemetry, and no log file: the tool writes nothing except its
|
|
218
|
+
one-second pacing stamp in `~/.cache/web/last-live`.
|
|
219
|
+
|
|
220
|
+
The SearXNG unit drops the query from its own logs. Access log off, level WARNING, autocomplete off (no
|
|
221
|
+
keystroke leaves the box), and the bundled `serve.py` filters every log record so that any URL query
|
|
222
|
+
string becomes `?<query withheld>`. The journal keeps engine errors and none of the terms. That is a
|
|
223
|
+
feature, and the reason the unit runs a wrapper instead of stock SearXNG.
|
|
224
|
+
|
|
225
|
+
This is not anonymity. Your IP talks to whichever upstream engines are enabled; a local proxy does not
|
|
226
|
+
change that. Send generic technical terms and keep private context out of a query.
|
|
227
|
+
|
|
228
|
+
`map` and `crawl` send only a URL to the target site, never a query, and every live request is paced to one
|
|
229
|
+
per second across processes, so a shell loop cannot look like a scraper.
|
|
230
|
+
|
|
231
|
+
## How it differs from firecrawl, and from an MCP server
|
|
232
|
+
|
|
233
|
+
firecrawl is a hosted pipeline for turning sites into clean markdown at scale: crawl jobs, JS rendering
|
|
234
|
+
farms, structured extraction, and an API key. webscour is the opposite shape, a local tool for one page or
|
|
235
|
+
one query, with no account, no credits and no state, and it tells you which engine or backend answered
|
|
236
|
+
instead of hiding the plumbing. An MCP server is a transport: it puts the same kind of tool behind a
|
|
237
|
+
protocol so a chat client can call it, and then something still has to be installed, run and authenticated.
|
|
238
|
+
`wsc` is a plain executable that an agent, a shell script or a human calls the same way, which is why it has
|
|
239
|
+
no server, no registered tool, and no process to keep alive. If you already run an MCP server for this, the
|
|
240
|
+
only thing webscour adds is the local search backend and the pacing.
|
|
241
|
+
|
|
242
|
+
## Limitations
|
|
243
|
+
|
|
244
|
+
- **Linux only**, because of the systemd user unit. Without the unit the scrapers answer from a residential
|
|
245
|
+
IP, and mostly they answer CAPTCHA.
|
|
246
|
+
- **The scrapers degrade on residential IPs.** Measured on 2026-11-13 with 30 generic queries, per engine;
|
|
247
|
+
a meta-search needs coverage (how often an engine helps) and precision (share of its rows that are on
|
|
248
|
+
topic), kept means coverage >= 10 % and precision >= 60 %:
|
|
249
|
+
|
|
250
|
+
| engine | cover | prec | rows | | engine | cover | prec | rows |
|
|
251
|
+
|---|---|---|---|---|---|---|---|---|
|
|
252
|
+
| naver | 90.0 % | 80.1 % | 403 | | wikibooks | 20.0 % | 93.8 % | 16 |
|
|
253
|
+
| abcnyheter | 70.0 % | 95.3 % | 214 | | wiby | 13.3 % | 42.9 % | 21 (dropped) |
|
|
254
|
+
| rawweb | 53.3 % | 98.8 % | 82 | | wikiversity, wikiquote | <=6.7 % | 100 % | 1-2 (dropped) |
|
|
255
|
+
| bing | 36.7 % | 60.0 % | 180 | | encyclosearch, wikisource | 3.3 % | 50 % | 2-14 (dropped) |
|
|
256
|
+
| yacy | 30.0 % | 96.7 % | 90 | | brave | 0 % this pass | n/a | kept by name |
|
|
257
|
+
|
|
258
|
+
duckduckgo, google, mojeek, qwant, startpage, searchmysite and yandex answered CAPTCHA, "access denied"
|
|
259
|
+
or a parsing error from a residential IP and are not in the set. Median merged query 5.0 s. Kept:
|
|
260
|
+
naver, abcnyheter, rawweb, bing, yacy, wikibooks, brave. `wsc probe --quality --write` repeats the
|
|
261
|
+
measurement on your own IP; the table above is one machine on one day, not a promise.
|
|
262
|
+
- **No render backend is installed by pip.** lightpanda is the one to install, and playwright is the
|
|
263
|
+
fallback for a page that defeats it. `wsc doctor` says which is present.
|
|
264
|
+
- **Scanned PDFs have no text layer.** `wsc pdf` says so and stops; OCR is out of scope.
|
|
265
|
+
- **Snippets are quotes from a scraped page.** Open the URL before relying on a claim, and treat a row whose
|
|
266
|
+
title and url lack every query term as drift, which the tool drops.
|
|
267
|
+
- **GitHub is unauthenticated**: 10 requests/min per IP, and `--source github` makes two calls.
|
|
268
|
+
|
|
269
|
+
## Development
|
|
270
|
+
|
|
271
|
+
Everything a release needs runs on this machine, offline:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
python3 -m venv .venv-dev && .venv-dev/bin/pip install ruff build twine
|
|
275
|
+
./scripts/check.sh # lint, format, tests, --self-test, build, twine, guards
|
|
276
|
+
BENCH=1 ./scripts/check.sh # also the extraction benchmark (needs the network)
|
|
277
|
+
./scripts/release.sh 0.2.0 # local: gate, stamp, changelog, tag. Never pushes or publishes
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
`scripts/check.sh` is the only definition of "releasable": it prints one `PASS <name>` line per
|
|
281
|
+
check and a summary, and the workflows in `.github/workflows/` are thin wrappers that call it. It
|
|
282
|
+
runs with no network, no SearXNG and `LIGHTPANDA=0`.
|
|
283
|
+
|
|
284
|
+
The extraction benchmark compares our reading text with trafilatura's on six pages, from captured
|
|
285
|
+
fixtures in `tests/fixtures/bench/`, and fails when a page regresses past `tests/baselines/`. The
|
|
286
|
+
reference extractor is a throwaway venv, never a dependency:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
python3 -m venv /tmp/tfvenv && /tmp/tfvenv/bin/pip install trafilatura
|
|
290
|
+
python3 tests/bench_extract.py # table, and a check against the baseline
|
|
291
|
+
python3 tests/bench_extract.py --live # re-fetch the pages first
|
|
292
|
+
python3 tests/bench_extract.py --save-baseline tests/baselines/0.2.0.json
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Design decisions live in `docs/decisions/`, one short record each, with the evidence and the
|
|
296
|
+
condition that would reverse it.
|
|
297
|
+
|
|
298
|
+
## License
|
|
299
|
+
|
|
300
|
+
MIT. See [LICENSE](LICENSE).
|