webget-cli 0.6.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.
- webget_cli-0.6.0/LICENSE +21 -0
- webget_cli-0.6.0/PKG-INFO +232 -0
- webget_cli-0.6.0/README.md +200 -0
- webget_cli-0.6.0/pyproject.toml +52 -0
- webget_cli-0.6.0/setup.cfg +4 -0
- webget_cli-0.6.0/tests/test_webget.py +394 -0
- webget_cli-0.6.0/webget_cli.egg-info/PKG-INFO +232 -0
- webget_cli-0.6.0/webget_cli.egg-info/SOURCES.txt +11 -0
- webget_cli-0.6.0/webget_cli.egg-info/dependency_links.txt +1 -0
- webget_cli-0.6.0/webget_cli.egg-info/entry_points.txt +2 -0
- webget_cli-0.6.0/webget_cli.egg-info/requires.txt +11 -0
- webget_cli-0.6.0/webget_cli.egg-info/top_level.txt +1 -0
- webget_cli-0.6.0/webget_cli.py +1071 -0
webget_cli-0.6.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David Tarigan
|
|
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,232 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: webget-cli
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Local search + scrape CLI with an HTTP fast path, optional browser fallback, and authenticated session profiles. Zero API keys.
|
|
5
|
+
Author-email: David Tarigan <tarigansdavid@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/DavidPandleton/webget
|
|
8
|
+
Project-URL: Repository, https://github.com/DavidPandleton/webget
|
|
9
|
+
Project-URL: Changelog, https://github.com/DavidPandleton/webget/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: scraping,crawling,cli,search,duckduckgo,http
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
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
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: ddgs
|
|
23
|
+
Requires-Dist: httpx
|
|
24
|
+
Requires-Dist: trafilatura
|
|
25
|
+
Requires-Dist: html2text
|
|
26
|
+
Provides-Extra: browser
|
|
27
|
+
Requires-Dist: crawl4ai>=0.9; extra == "browser"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
30
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
<div align="center">
|
|
34
|
+
|
|
35
|
+
# webget
|
|
36
|
+
|
|
37
|
+
**Local search + scrape CLI. Zero API keys, unlimited usage.**
|
|
38
|
+
|
|
39
|
+
`webget` is a web acquisition layer for agents and scripts: it routes every URL
|
|
40
|
+
through a strategy ladder (HTTP fast path → Crawl4AI browser → optional
|
|
41
|
+
Firecrawl), and reports *provenance* - where the content came from and whether
|
|
42
|
+
the session that fetched it can be trusted.
|
|
43
|
+
|
|
44
|
+
[](https://github.com/DavidPandleton/webget/actions)
|
|
45
|
+
[](https://pypi.org/project/webget-cli/)
|
|
46
|
+
[](https://www.python.org/)
|
|
47
|
+
[](LICENSE)
|
|
48
|
+
[](https://github.com/DavidPandleton/webget)
|
|
49
|
+
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Why
|
|
55
|
+
|
|
56
|
+
Most scraping tools assume one engine. webget assumes the web is messy:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
FETCH_AUTO
|
|
60
|
+
├── http → fast path (httpx + trafilatura/html2text), no browser
|
|
61
|
+
├── crawl4ai → Playwright browser, JS rendering, persistent auth sessions
|
|
62
|
+
└── firecrawl → optional cloud fallback (needs WEBGET_FIRECRAWL_KEY)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Every fetch classifies what it hit - `success`, `login_required`, `challenge`,
|
|
66
|
+
`blocked`, or `error` - and reports it in machine-readable JSON. webget never
|
|
67
|
+
pretends an empty page is success, and it never solves CAPTCHAs or evades
|
|
68
|
+
anti-bot systems; it tells you honestly what happened.
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
Requires Python 3.11+.
|
|
73
|
+
|
|
74
|
+
### PyPI (`webget-cli`)
|
|
75
|
+
|
|
76
|
+
The CLI command is `webget`; the PyPI package name is `webget-cli`
|
|
77
|
+
(the bare `webget` name is taken by an unrelated package).
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# pip - HTTP fast path + search only (no browser)
|
|
81
|
+
pip install webget-cli
|
|
82
|
+
|
|
83
|
+
# pip - full stack with Crawl4AI/Playwright browser fallback
|
|
84
|
+
pip install "webget-cli[browser]"
|
|
85
|
+
|
|
86
|
+
# uv tool - isolated executable on your PATH
|
|
87
|
+
uv tool install webget-cli --with "webget-cli[browser]"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
After install, `webget` is available as a command:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
webget --help
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Browser runtime (optional)
|
|
97
|
+
|
|
98
|
+
Crawl4AI drives a Playwright Chromium. `pip install "webget-cli[browser]"`
|
|
99
|
+
installs the Python packages; the browser binary itself is downloaded
|
|
100
|
+
separately:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
python -m playwright install chromium
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Without the browser extra, `webget` still works for search and plain HTTP
|
|
107
|
+
fetches. A fetch that needs the browser (JS rendering, `--profile` sessions,
|
|
108
|
+
`login`) prints a clear warning telling you how to install it.
|
|
109
|
+
|
|
110
|
+
### From source (development)
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
git clone https://github.com/DavidPandleton/webget
|
|
114
|
+
cd webget
|
|
115
|
+
uv pip install -e ".[dev,browser]"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Usage
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
webget s "rust async runtime" # search DuckDuckGo (top 5)
|
|
122
|
+
webget u https://example.com # scrape (auto: http -> crawl4ai)
|
|
123
|
+
webget su "llm inference" 5 # search + scrape top 5, parallel
|
|
124
|
+
cat urls.txt | webget u - # batch scrape, one browser instance
|
|
125
|
+
webget fetch https://example.com --json # machine-readable result
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Long aliases: `search` = `s`, `fetch` = `u`, `search-fetch` = `su`.
|
|
129
|
+
|
|
130
|
+
### Options
|
|
131
|
+
|
|
132
|
+
| Flag | Meaning |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `-c, --cookies FILE` | Netscape-format cookie file |
|
|
135
|
+
| `--profile NAME` | Persistent browser profile (auth session) |
|
|
136
|
+
| `-H, --header "K: V"` | Extra header (repeatable) |
|
|
137
|
+
| `-n, --max-chars N` | Max output chars (default: 10000 for `u`, 4000 for `su`) |
|
|
138
|
+
| `--limit N` | Result count for `s`/`su` |
|
|
139
|
+
| `-t, --timeout N` | Per-URL timeout seconds (default 20) |
|
|
140
|
+
| `--fresh` | Bypass cache |
|
|
141
|
+
| `--ttl N` | Cache TTL seconds (default 3600) |
|
|
142
|
+
| `--strategy S` | `auto` \| `http` \| `crawl4ai` \| `firecrawl` |
|
|
143
|
+
| `--no-cache` | Don't read or write the disk cache (private fetch) |
|
|
144
|
+
| `--json` | JSON output with metadata |
|
|
145
|
+
|
|
146
|
+
## Authenticated sessions (profiles)
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# interactive login: browser opens, YOU log in manually, session persists
|
|
150
|
+
webget login https://campus.example --profile campus
|
|
151
|
+
|
|
152
|
+
# list profiles and their session status
|
|
153
|
+
webget profiles
|
|
154
|
+
webget profiles --json
|
|
155
|
+
|
|
156
|
+
# later fetches reuse the session - even on the HTTP fast path
|
|
157
|
+
webget fetch https://campus.example/dashboard --profile campus --json
|
|
158
|
+
|
|
159
|
+
# log out ONE domain, keep the rest of the profile
|
|
160
|
+
webget logout https://campus.example --profile campus
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`webget login` never stores passwords and never fills forms. A visible
|
|
164
|
+
browser opens, you authenticate yourself, then press Enter in the terminal and
|
|
165
|
+
webget persists the session. Persistent profiles live in
|
|
166
|
+
`~/.local/share/webget/profiles/<name>`; session cookies are exported to
|
|
167
|
+
`storage_state.json` inside the profile after each browser run, so the fast
|
|
168
|
+
path can reuse them. Secrets are never printed.
|
|
169
|
+
|
|
170
|
+
## JSON output
|
|
171
|
+
|
|
172
|
+
`--json` returns a dict keyed by URL, so batch results are easy to inspect:
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"https://campus.example/dashboard": {
|
|
177
|
+
"status": "success",
|
|
178
|
+
"method": "crawl4ai",
|
|
179
|
+
"cached": false,
|
|
180
|
+
"attempts": 1,
|
|
181
|
+
"auth": {
|
|
182
|
+
"profile": "campus",
|
|
183
|
+
"authenticated": true,
|
|
184
|
+
"state": "success"
|
|
185
|
+
},
|
|
186
|
+
"error": null
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Status values: `success | login_required | challenge | blocked | error`.
|
|
192
|
+
|
|
193
|
+
## Status detection rules
|
|
194
|
+
|
|
195
|
+
| Signal | State |
|
|
196
|
+
|---|---|
|
|
197
|
+
| Valid content (≥100 chars) | `success` |
|
|
198
|
+
| HTTP 401, login form, 403 + login markers | `login_required` |
|
|
199
|
+
| Cloudflare / CAPTCHA / "verify you are human" | `challenge` |
|
|
200
|
+
| HTTP 403 generic, 429, "access denied" | `blocked` |
|
|
201
|
+
| DNS failure, timeout, unexpected exception | `error` |
|
|
202
|
+
|
|
203
|
+
## Cache
|
|
204
|
+
|
|
205
|
+
Results are cached in `~/.cache/webget/` (sha1 of url + profile + options,
|
|
206
|
+
TTL 1h, eviction at 500 files). The cache is **content-level, not
|
|
207
|
+
strategy-level**, and **isolated per profile** - public, `campus`, and `work`
|
|
208
|
+
fetches never collide. Failures are never cached.
|
|
209
|
+
|
|
210
|
+
> **Privacy note:** cached content is plaintext JSON on disk. If you fetch
|
|
211
|
+
> authenticated/personal pages, use `--no-cache`.
|
|
212
|
+
|
|
213
|
+
## Development
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
make dev # install runtime + dev deps
|
|
217
|
+
make test # pytest (pure logic, no network needed)
|
|
218
|
+
make lint # ruff
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
- Single-file Python (`webget_cli.py`), no build step, runs via `uv run`.
|
|
222
|
+
- Lazy imports: `--strategy http` never pays the Crawl4AI import cost.
|
|
223
|
+
- Crawl4AI 0.9.2's `export_storage_state()` is broken (wrong attribute);
|
|
224
|
+
webget works around it by reaching into `browser_manager` directly.
|
|
225
|
+
|
|
226
|
+
## Contributing
|
|
227
|
+
|
|
228
|
+
Found a bug or have an idea? [Open an issue](https://github.com/DavidPandleton/webget/issues/new/choose) - we have templates. Pull requests welcome, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# webget
|
|
4
|
+
|
|
5
|
+
**Local search + scrape CLI. Zero API keys, unlimited usage.**
|
|
6
|
+
|
|
7
|
+
`webget` is a web acquisition layer for agents and scripts: it routes every URL
|
|
8
|
+
through a strategy ladder (HTTP fast path → Crawl4AI browser → optional
|
|
9
|
+
Firecrawl), and reports *provenance* - where the content came from and whether
|
|
10
|
+
the session that fetched it can be trusted.
|
|
11
|
+
|
|
12
|
+
[](https://github.com/DavidPandleton/webget/actions)
|
|
13
|
+
[](https://pypi.org/project/webget-cli/)
|
|
14
|
+
[](https://www.python.org/)
|
|
15
|
+
[](LICENSE)
|
|
16
|
+
[](https://github.com/DavidPandleton/webget)
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Why
|
|
23
|
+
|
|
24
|
+
Most scraping tools assume one engine. webget assumes the web is messy:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
FETCH_AUTO
|
|
28
|
+
├── http → fast path (httpx + trafilatura/html2text), no browser
|
|
29
|
+
├── crawl4ai → Playwright browser, JS rendering, persistent auth sessions
|
|
30
|
+
└── firecrawl → optional cloud fallback (needs WEBGET_FIRECRAWL_KEY)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Every fetch classifies what it hit - `success`, `login_required`, `challenge`,
|
|
34
|
+
`blocked`, or `error` - and reports it in machine-readable JSON. webget never
|
|
35
|
+
pretends an empty page is success, and it never solves CAPTCHAs or evades
|
|
36
|
+
anti-bot systems; it tells you honestly what happened.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
Requires Python 3.11+.
|
|
41
|
+
|
|
42
|
+
### PyPI (`webget-cli`)
|
|
43
|
+
|
|
44
|
+
The CLI command is `webget`; the PyPI package name is `webget-cli`
|
|
45
|
+
(the bare `webget` name is taken by an unrelated package).
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# pip - HTTP fast path + search only (no browser)
|
|
49
|
+
pip install webget-cli
|
|
50
|
+
|
|
51
|
+
# pip - full stack with Crawl4AI/Playwright browser fallback
|
|
52
|
+
pip install "webget-cli[browser]"
|
|
53
|
+
|
|
54
|
+
# uv tool - isolated executable on your PATH
|
|
55
|
+
uv tool install webget-cli --with "webget-cli[browser]"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
After install, `webget` is available as a command:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
webget --help
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Browser runtime (optional)
|
|
65
|
+
|
|
66
|
+
Crawl4AI drives a Playwright Chromium. `pip install "webget-cli[browser]"`
|
|
67
|
+
installs the Python packages; the browser binary itself is downloaded
|
|
68
|
+
separately:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python -m playwright install chromium
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Without the browser extra, `webget` still works for search and plain HTTP
|
|
75
|
+
fetches. A fetch that needs the browser (JS rendering, `--profile` sessions,
|
|
76
|
+
`login`) prints a clear warning telling you how to install it.
|
|
77
|
+
|
|
78
|
+
### From source (development)
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
git clone https://github.com/DavidPandleton/webget
|
|
82
|
+
cd webget
|
|
83
|
+
uv pip install -e ".[dev,browser]"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Usage
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
webget s "rust async runtime" # search DuckDuckGo (top 5)
|
|
90
|
+
webget u https://example.com # scrape (auto: http -> crawl4ai)
|
|
91
|
+
webget su "llm inference" 5 # search + scrape top 5, parallel
|
|
92
|
+
cat urls.txt | webget u - # batch scrape, one browser instance
|
|
93
|
+
webget fetch https://example.com --json # machine-readable result
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Long aliases: `search` = `s`, `fetch` = `u`, `search-fetch` = `su`.
|
|
97
|
+
|
|
98
|
+
### Options
|
|
99
|
+
|
|
100
|
+
| Flag | Meaning |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `-c, --cookies FILE` | Netscape-format cookie file |
|
|
103
|
+
| `--profile NAME` | Persistent browser profile (auth session) |
|
|
104
|
+
| `-H, --header "K: V"` | Extra header (repeatable) |
|
|
105
|
+
| `-n, --max-chars N` | Max output chars (default: 10000 for `u`, 4000 for `su`) |
|
|
106
|
+
| `--limit N` | Result count for `s`/`su` |
|
|
107
|
+
| `-t, --timeout N` | Per-URL timeout seconds (default 20) |
|
|
108
|
+
| `--fresh` | Bypass cache |
|
|
109
|
+
| `--ttl N` | Cache TTL seconds (default 3600) |
|
|
110
|
+
| `--strategy S` | `auto` \| `http` \| `crawl4ai` \| `firecrawl` |
|
|
111
|
+
| `--no-cache` | Don't read or write the disk cache (private fetch) |
|
|
112
|
+
| `--json` | JSON output with metadata |
|
|
113
|
+
|
|
114
|
+
## Authenticated sessions (profiles)
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# interactive login: browser opens, YOU log in manually, session persists
|
|
118
|
+
webget login https://campus.example --profile campus
|
|
119
|
+
|
|
120
|
+
# list profiles and their session status
|
|
121
|
+
webget profiles
|
|
122
|
+
webget profiles --json
|
|
123
|
+
|
|
124
|
+
# later fetches reuse the session - even on the HTTP fast path
|
|
125
|
+
webget fetch https://campus.example/dashboard --profile campus --json
|
|
126
|
+
|
|
127
|
+
# log out ONE domain, keep the rest of the profile
|
|
128
|
+
webget logout https://campus.example --profile campus
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`webget login` never stores passwords and never fills forms. A visible
|
|
132
|
+
browser opens, you authenticate yourself, then press Enter in the terminal and
|
|
133
|
+
webget persists the session. Persistent profiles live in
|
|
134
|
+
`~/.local/share/webget/profiles/<name>`; session cookies are exported to
|
|
135
|
+
`storage_state.json` inside the profile after each browser run, so the fast
|
|
136
|
+
path can reuse them. Secrets are never printed.
|
|
137
|
+
|
|
138
|
+
## JSON output
|
|
139
|
+
|
|
140
|
+
`--json` returns a dict keyed by URL, so batch results are easy to inspect:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"https://campus.example/dashboard": {
|
|
145
|
+
"status": "success",
|
|
146
|
+
"method": "crawl4ai",
|
|
147
|
+
"cached": false,
|
|
148
|
+
"attempts": 1,
|
|
149
|
+
"auth": {
|
|
150
|
+
"profile": "campus",
|
|
151
|
+
"authenticated": true,
|
|
152
|
+
"state": "success"
|
|
153
|
+
},
|
|
154
|
+
"error": null
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Status values: `success | login_required | challenge | blocked | error`.
|
|
160
|
+
|
|
161
|
+
## Status detection rules
|
|
162
|
+
|
|
163
|
+
| Signal | State |
|
|
164
|
+
|---|---|
|
|
165
|
+
| Valid content (≥100 chars) | `success` |
|
|
166
|
+
| HTTP 401, login form, 403 + login markers | `login_required` |
|
|
167
|
+
| Cloudflare / CAPTCHA / "verify you are human" | `challenge` |
|
|
168
|
+
| HTTP 403 generic, 429, "access denied" | `blocked` |
|
|
169
|
+
| DNS failure, timeout, unexpected exception | `error` |
|
|
170
|
+
|
|
171
|
+
## Cache
|
|
172
|
+
|
|
173
|
+
Results are cached in `~/.cache/webget/` (sha1 of url + profile + options,
|
|
174
|
+
TTL 1h, eviction at 500 files). The cache is **content-level, not
|
|
175
|
+
strategy-level**, and **isolated per profile** - public, `campus`, and `work`
|
|
176
|
+
fetches never collide. Failures are never cached.
|
|
177
|
+
|
|
178
|
+
> **Privacy note:** cached content is plaintext JSON on disk. If you fetch
|
|
179
|
+
> authenticated/personal pages, use `--no-cache`.
|
|
180
|
+
|
|
181
|
+
## Development
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
make dev # install runtime + dev deps
|
|
185
|
+
make test # pytest (pure logic, no network needed)
|
|
186
|
+
make lint # ruff
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- Single-file Python (`webget_cli.py`), no build step, runs via `uv run`.
|
|
190
|
+
- Lazy imports: `--strategy http` never pays the Crawl4AI import cost.
|
|
191
|
+
- Crawl4AI 0.9.2's `export_storage_state()` is broken (wrong attribute);
|
|
192
|
+
webget works around it by reaching into `browser_manager` directly.
|
|
193
|
+
|
|
194
|
+
## Contributing
|
|
195
|
+
|
|
196
|
+
Found a bug or have an idea? [Open an issue](https://github.com/DavidPandleton/webget/issues/new/choose) - we have templates. Pull requests welcome, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "webget-cli"
|
|
7
|
+
version = "0.6.0"
|
|
8
|
+
description = "Local search + scrape CLI with an HTTP fast path, optional browser fallback, and authenticated session profiles. Zero API keys."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "David Tarigan", email = "tarigansdavid@gmail.com" }]
|
|
13
|
+
keywords = ["scraping", "crawling", "cli", "search", "duckduckgo", "http"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Environment :: Console",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"ddgs",
|
|
26
|
+
"httpx",
|
|
27
|
+
"trafilatura",
|
|
28
|
+
"html2text",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/DavidPandleton/webget"
|
|
33
|
+
Repository = "https://github.com/DavidPandleton/webget"
|
|
34
|
+
Changelog = "https://github.com/DavidPandleton/webget/blob/main/CHANGELOG.md"
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
browser = ["crawl4ai>=0.9"]
|
|
38
|
+
dev = ["pytest>=8", "ruff>=0.6"]
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
webget = "webget_cli:main"
|
|
42
|
+
|
|
43
|
+
[tool.setuptools]
|
|
44
|
+
py-modules = ["webget_cli"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
testpaths = ["tests"]
|
|
48
|
+
addopts = "-q"
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
line-length = 100
|
|
52
|
+
target-version = "py311"
|