pagefetch 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pagefetch-0.1.0/LICENSE +22 -0
- pagefetch-0.1.0/PKG-INFO +423 -0
- pagefetch-0.1.0/README.md +398 -0
- pagefetch-0.1.0/pagefetch/__init__.py +29 -0
- pagefetch-0.1.0/pagefetch/__main__.py +4 -0
- pagefetch-0.1.0/pagefetch/bootstrap.py +113 -0
- pagefetch-0.1.0/pagefetch/cache/__init__.py +7 -0
- pagefetch-0.1.0/pagefetch/cache/keys.py +28 -0
- pagefetch-0.1.0/pagefetch/cache/sqlite.py +103 -0
- pagefetch-0.1.0/pagefetch/cli.py +165 -0
- pagefetch-0.1.0/pagefetch/client.py +643 -0
- pagefetch-0.1.0/pagefetch/config.py +167 -0
- pagefetch-0.1.0/pagefetch/constants.py +32 -0
- pagefetch-0.1.0/pagefetch/exceptions.py +15 -0
- pagefetch-0.1.0/pagefetch/fetching/__init__.py +7 -0
- pagefetch-0.1.0/pagefetch/fetching/browser.py +261 -0
- pagefetch-0.1.0/pagefetch/fetching/http.py +130 -0
- pagefetch-0.1.0/pagefetch/fetching/readiness.py +81 -0
- pagefetch-0.1.0/pagefetch/models.py +100 -0
- pagefetch-0.1.0/pagefetch/processing/__init__.py +6 -0
- pagefetch-0.1.0/pagefetch/processing/cleaner.py +39 -0
- pagefetch-0.1.0/pagefetch/processing/detector.py +142 -0
- pagefetch-0.1.0/pagefetch/processing/html.py +47 -0
- pagefetch-0.1.0/pagefetch/processing/images.py +31 -0
- pagefetch-0.1.0/pagefetch/processing/links.py +40 -0
- pagefetch-0.1.0/pagefetch/processing/markdown.py +217 -0
- pagefetch-0.1.0/pagefetch/processing/metadata.py +84 -0
- pagefetch-0.1.0/pagefetch/processing/non_html.py +74 -0
- pagefetch-0.1.0/pagefetch/proxy/__init__.py +6 -0
- pagefetch-0.1.0/pagefetch/proxy/providers.py +84 -0
- pagefetch-0.1.0/pagefetch/utils/__init__.py +2 -0
- pagefetch-0.1.0/pagefetch/utils/durations.py +25 -0
- pagefetch-0.1.0/pagefetch/utils/urls.py +68 -0
- pagefetch-0.1.0/pagefetch.egg-info/PKG-INFO +423 -0
- pagefetch-0.1.0/pagefetch.egg-info/SOURCES.txt +47 -0
- pagefetch-0.1.0/pagefetch.egg-info/dependency_links.txt +1 -0
- pagefetch-0.1.0/pagefetch.egg-info/entry_points.txt +2 -0
- pagefetch-0.1.0/pagefetch.egg-info/requires.txt +15 -0
- pagefetch-0.1.0/pagefetch.egg-info/top_level.txt +1 -0
- pagefetch-0.1.0/pyproject.toml +46 -0
- pagefetch-0.1.0/setup.cfg +4 -0
- pagefetch-0.1.0/tests/test_bootstrap.py +70 -0
- pagefetch-0.1.0/tests/test_browser.py +183 -0
- pagefetch-0.1.0/tests/test_client.py +306 -0
- pagefetch-0.1.0/tests/test_config_cli.py +25 -0
- pagefetch-0.1.0/tests/test_non_html.py +83 -0
- pagefetch-0.1.0/tests/test_processing.py +114 -0
- pagefetch-0.1.0/tests/test_proxy_cache.py +78 -0
- pagefetch-0.1.0/tests/test_utils_models.py +56 -0
pagefetch-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PageFetch contributors
|
|
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.
|
|
22
|
+
|
pagefetch-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pagefetch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Asynchronous HTTP-first web fetching with conservative content extraction and browser fallback
|
|
5
|
+
Author: PageFetch contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: aiosqlite<1,>=0.20
|
|
11
|
+
Requires-Dist: beautifulsoup4<5,>=4.12
|
|
12
|
+
Requires-Dist: brotlicffi<2,>=1.0
|
|
13
|
+
Requires-Dist: camoufox<1,>=0.4
|
|
14
|
+
Requires-Dist: httpx[http2,socks]<1,>=0.27
|
|
15
|
+
Requires-Dist: lxml<6,>=5.2
|
|
16
|
+
Requires-Dist: platformdirs<5,>=4.2
|
|
17
|
+
Requires-Dist: pypdf<6,>=5.0
|
|
18
|
+
Requires-Dist: pyyaml<7,>=6.0
|
|
19
|
+
Requires-Dist: tldextract<6,>=5.1
|
|
20
|
+
Provides-Extra: test
|
|
21
|
+
Requires-Dist: pytest<9,>=8.0; extra == "test"
|
|
22
|
+
Requires-Dist: pytest-asyncio<1,>=0.23; extra == "test"
|
|
23
|
+
Requires-Dist: ruff<1,>=0.6; extra == "test"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# PageFetch
|
|
27
|
+
|
|
28
|
+
**Async-first web scraping and content extraction library for Python 3.11+ — with
|
|
29
|
+
intelligent browser fallback, built-in caching, and structured Markdown output.**
|
|
30
|
+
|
|
31
|
+
<p align="center">
|
|
32
|
+
<img src="https://img.shields.io/badge/python-3.11%2B-blue" alt="Python 3.11+">
|
|
33
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License">
|
|
34
|
+
<img src="https://img.shields.io/badge/code%20style-ruff-261230" alt="Ruff">
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
PageFetch is a modern, asynchronous Python library for **fetching web pages** and
|
|
38
|
+
extracting clean, structured content. It attempts a pooled **HTTP request** first,
|
|
39
|
+
scores the returned HTML using multiple **completeness signals**, and lazily falls
|
|
40
|
+
back to a **Camoufox stealth browser** when the page looks blocked, empty, or
|
|
41
|
+
dependent on client-side JavaScript rendering. The result is always a consistent
|
|
42
|
+
`FetchResult` object carrying **Markdown**, raw HTML, extracted links, images,
|
|
43
|
+
metadata, and more — regardless of which method succeeded.
|
|
44
|
+
|
|
45
|
+
Perfect for **web scraping**, **content aggregation**, **LLM data pipelines**,
|
|
46
|
+
**SEO analysis**, **archiving**, and any workflow that needs reliable page
|
|
47
|
+
content without fighting bot detection.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Table of Contents
|
|
52
|
+
|
|
53
|
+
- [Quick Start](#quick-start)
|
|
54
|
+
- [Why PageFetch](#why-pagefetch)
|
|
55
|
+
- [How It Works](#how-it-works)
|
|
56
|
+
- [Installation](#installation)
|
|
57
|
+
- [API Reference](#api-reference)
|
|
58
|
+
- [Output Model](#output-model)
|
|
59
|
+
- [CLI Usage](#cli-usage)
|
|
60
|
+
- [Caching](#caching)
|
|
61
|
+
- [Proxy Support](#proxy-support)
|
|
62
|
+
- [Non-HTML Content](#non-html-content)
|
|
63
|
+
- [Configuration Reference](#configuration-reference)
|
|
64
|
+
- [Development](#development)
|
|
65
|
+
- [License](#license)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Quick Start
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import asyncio
|
|
73
|
+
|
|
74
|
+
from pagefetch import PageFetch
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def main() -> None:
|
|
78
|
+
async with PageFetch(mode="auto", cache_ttl="24h") as client:
|
|
79
|
+
result = await client.fetch("https://example.com")
|
|
80
|
+
if result.success:
|
|
81
|
+
print(f"Title: {result.title}")
|
|
82
|
+
print(f"Markdown length: {len(result.markdown or '')} chars")
|
|
83
|
+
print(f"Links found: {len(result.links or [])}")
|
|
84
|
+
print(f"Images found: {len(result.images or [])}")
|
|
85
|
+
else:
|
|
86
|
+
print(f"Failed: {result.error.message}")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
asyncio.run(main())
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Fetch Multiple URLs in Parallel
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
async with PageFetch(mode="auto") as client:
|
|
96
|
+
results = await client.fetch_many([
|
|
97
|
+
"https://example.com",
|
|
98
|
+
"https://example.org",
|
|
99
|
+
"https://httpbin.org/html",
|
|
100
|
+
])
|
|
101
|
+
for r in results:
|
|
102
|
+
status = "✓" if r.success else "✗"
|
|
103
|
+
print(f"{status} {r.url} ({r.fetch_method}, {r.duration_ms:.0f}ms)")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Why PageFetch
|
|
109
|
+
|
|
110
|
+
| Challenge | PageFetch Solution |
|
|
111
|
+
|---|---|
|
|
112
|
+
| **Bot detection & blocking** | Camoufox stealth browser with automatic fallback when HTTP returns empty, blocked (403/429), or JavaScript-dependent pages |
|
|
113
|
+
| **Over-fetching with heavy browsers** | HTTP-first strategy — only ~5–15% of pages need the browser in auto mode |
|
|
114
|
+
| **Inconsistent output formats** | Single `FetchResult` model: always get `.markdown`, `.html`, `.text`, `.links`, `.images`, `.metadata` |
|
|
115
|
+
| **Managing concurrency** | Built-in semaphores for HTTP (default 10) and browser (default 4) — safe for hundreds of URLs |
|
|
116
|
+
| **Repeated requests waste bandwidth** | SQLite disk cache with configurable TTL, shared across runs |
|
|
117
|
+
| **Proxy rotation complexity** | Native Decodo and DataImpulse integration — configure via env vars |
|
|
118
|
+
| **Content that isn't HTML** | PDFs auto-detected and extracted; XML documents parsed; plain text preserved |
|
|
119
|
+
| **Dependency management friction** | Automatic bootstrap installs missing Python packages and the Camoufox binary at first startup |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## How It Works
|
|
124
|
+
|
|
125
|
+
1. **Normalize** the URL (scheme, encoding, fragments).
|
|
126
|
+
2. **Check cache** — if a valid SQLite entry exists, return instantly.
|
|
127
|
+
3. **HTTP fetch** using `httpx` with HTTP/2, connection pooling, and configurable retries.
|
|
128
|
+
Non-HTML responses (PDF, XML, plain text) are handled directly without browser overhead.
|
|
129
|
+
4. **Content analysis** — the `confidence` score evaluates HTML completeness using
|
|
130
|
+
text density, structural markup, heading presence, link counts, and common blocking signals
|
|
131
|
+
(captcha walls, empty bodies, access-denied patterns).
|
|
132
|
+
5. **Browser fallback** (auto mode only) — if confidence is below the threshold (default 0.80),
|
|
133
|
+
Camoufox takes over: renders JavaScript, scrolls for lazy-loaded content, waits for
|
|
134
|
+
network stability, and re-extracts.
|
|
135
|
+
6. **Processing pipeline** — cleaned HTML → extracted links, images, metadata →
|
|
136
|
+
converted to Markdown via a custom converter that preserves tables, code blocks,
|
|
137
|
+
and nested lists.
|
|
138
|
+
7. **Cache & return** — the structured `FetchResult` is persisted to SQLite and returned.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Installation
|
|
143
|
+
|
|
144
|
+
### Prerequisites
|
|
145
|
+
|
|
146
|
+
- **Python** ≥ 3.11
|
|
147
|
+
- **Camoufox** browser binary — *downloaded automatically* at first startup
|
|
148
|
+
|
|
149
|
+
### Install
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pip install .
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Automatic bootstrap is **enabled by default**. At first import, PageFetch will:
|
|
156
|
+
|
|
157
|
+
1. Install any missing Python dependencies via `pip`.
|
|
158
|
+
2. Download the Camoufox browser binary.
|
|
159
|
+
|
|
160
|
+
Subsequent starts perform quick availability checks only — the browser is launched
|
|
161
|
+
only when `mode="browser"` is selected or auto mode detects incomplete HTTP content.
|
|
162
|
+
|
|
163
|
+
### Disable Auto-Install (Managed / Offline Environments)
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Windows
|
|
167
|
+
set PAGEFETCH_AUTO_INSTALL=0
|
|
168
|
+
|
|
169
|
+
# Linux / macOS
|
|
170
|
+
export PAGEFETCH_AUTO_INSTALL=0
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
With auto-install disabled, provision both components manually:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
pip install .
|
|
177
|
+
python -m camoufox fetch
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## API Reference
|
|
183
|
+
|
|
184
|
+
### `PageFetch` Client
|
|
185
|
+
|
|
186
|
+
The main entry point. Use as an async context manager for automatic cleanup.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from pagefetch import PageFetch
|
|
190
|
+
|
|
191
|
+
client = PageFetch(
|
|
192
|
+
mode="auto", # "auto" | "http" | "browser"
|
|
193
|
+
proxy="none", # "none" | "decodo" | "dataimpulse"
|
|
194
|
+
http_concurrency=10, # Max parallel HTTP requests
|
|
195
|
+
browser_concurrency=4, # Max parallel browser instances
|
|
196
|
+
cache_enabled=True, # Enable SQLite disk cache
|
|
197
|
+
cache_ttl="24h", # TTL: "30m", "2h", "7d", or seconds as int
|
|
198
|
+
cache_path=None, # Custom SQLite cache path (None = platform default)
|
|
199
|
+
http_timeout=20.0, # Per-request HTTP timeout (seconds)
|
|
200
|
+
browser_timeout=45.0, # Per-page browser timeout (seconds)
|
|
201
|
+
retries_http=3, # Retry on 429/5xx for HTTP
|
|
202
|
+
retries_browser=2, # Retry on browser failure
|
|
203
|
+
max_redirects=10, # Maximum redirect chain
|
|
204
|
+
max_content_size=25 * 1024 * 1024, # Max response body bytes (25 MiB)
|
|
205
|
+
confidence_threshold=0.80, # Min confidence before browser fallback
|
|
206
|
+
raise_on_error=False, # Raise PageFetchError instead of returning error result
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Modes
|
|
211
|
+
|
|
212
|
+
| Mode | Behavior |
|
|
213
|
+
|---|---|
|
|
214
|
+
| `"auto"` | HTTP first; falls back to Camoufox if confidence < threshold (default) |
|
|
215
|
+
| `"http"` | Pure HTTP/2 fetching — no browser, no confidence scoring |
|
|
216
|
+
| `"browser"` | Camoufox stealth browser for every request |
|
|
217
|
+
|
|
218
|
+
### Methods
|
|
219
|
+
|
|
220
|
+
**`fetch(url, *, mode=None, proxy=None, use_cache=True, cache_ttl=None, raise_on_error=None) → FetchResult`**
|
|
221
|
+
|
|
222
|
+
Fetch a single URL. All keyword arguments override the client-level defaults
|
|
223
|
+
for this individual request only.
|
|
224
|
+
|
|
225
|
+
**`fetch_many(urls, *, mode=None, proxy=None, use_cache=True, cache_ttl=None, raise_on_error=None) → list[FetchResult]`**
|
|
226
|
+
|
|
227
|
+
Fetch multiple URLs concurrently. Deduplicates identical inputs internally,
|
|
228
|
+
preserves the original input order, and isolates individual failures — one
|
|
229
|
+
bad URL never affects the others.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Output Model
|
|
234
|
+
|
|
235
|
+
Every fetch returns a `FetchResult` dataclass:
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
from dataclasses import dataclass
|
|
239
|
+
|
|
240
|
+
@dataclass
|
|
241
|
+
class FetchResult:
|
|
242
|
+
url: str # Normalized request URL
|
|
243
|
+
final_url: str | None # URL after all redirects
|
|
244
|
+
status_code: int | None # HTTP status code
|
|
245
|
+
success: bool # Did the fetch succeed?
|
|
246
|
+
content_type: str | None # e.g. "text/html", "application/pdf"
|
|
247
|
+
encoding: str | None # Detected charset
|
|
248
|
+
title: str | None # Page <title> or PDF title
|
|
249
|
+
markdown: str | None # Cleaned Markdown body
|
|
250
|
+
html: str | None # Raw HTML (excluded from JSON by default)
|
|
251
|
+
text: str | None # Plain-text body fallback
|
|
252
|
+
metadata: dict # OpenGraph, Twitter Cards, meta tags
|
|
253
|
+
links: list[LinkInfo] # All <a> tags with text, URL, rel
|
|
254
|
+
images: list[ImageInfo] # All <img> tags with src, alt, title
|
|
255
|
+
fetch_method: str | None # "http" or "browser"
|
|
256
|
+
proxy_provider: str # "none", "decodo", or "dataimpulse"
|
|
257
|
+
content_confidence: float | None # 0–1 completeness score (None for browser mode)
|
|
258
|
+
from_cache: bool # Was this served from cache?
|
|
259
|
+
duration_ms: float | None # Total fetch duration
|
|
260
|
+
fetched_at: datetime | None # ISO 8601 timestamp
|
|
261
|
+
warnings: list[str] # Non-fatal issues (cache skip, etc.)
|
|
262
|
+
error: FetchErrorInfo | None # Error details when success=False
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Serialization
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
# JSON output (HTML excluded by default for compactness)
|
|
269
|
+
print(result.json(indent=2))
|
|
270
|
+
print(result.json(include_html=True)) # Include raw HTML
|
|
271
|
+
|
|
272
|
+
# Python dict
|
|
273
|
+
data = result.to_dict()
|
|
274
|
+
data = result.to_dict(include_html=True)
|
|
275
|
+
|
|
276
|
+
# Reconstruct from cached JSON
|
|
277
|
+
reconstructed = FetchResult.from_dict(data)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## CLI Usage
|
|
283
|
+
|
|
284
|
+
PageFetch ships with a command-line interface accessible via `pagefetch`:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Fetch and print Markdown
|
|
288
|
+
pagefetch https://example.com --format markdown
|
|
289
|
+
|
|
290
|
+
# Fetch from a list and output JSON
|
|
291
|
+
pagefetch urls.txt --format json --mode auto
|
|
292
|
+
|
|
293
|
+
# Save output to a file
|
|
294
|
+
pagefetch https://example.com --mode browser -o output.md
|
|
295
|
+
|
|
296
|
+
# Structured JSON with raw HTML included
|
|
297
|
+
pagefetch https://example.com --format json --include-html
|
|
298
|
+
|
|
299
|
+
# Multiple URLs from a file (one URL per line)
|
|
300
|
+
pagefetch urls.txt --format json --mode auto
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
CLI arguments map directly to the Python API — `--mode`, `--proxy`, `--timeout`,
|
|
304
|
+
`--http-concurrency`, and `--browser-concurrency` are all supported.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Caching
|
|
309
|
+
|
|
310
|
+
PageFetch uses a **SQLite-backed disk cache** (`platformdirs` user cache directory
|
|
311
|
+
by default). Cache entries are keyed by normalized URL + mode + proxy + relevant
|
|
312
|
+
fetch settings, so switching from `"auto"` to `"browser"` mode produces a
|
|
313
|
+
different cache key.
|
|
314
|
+
|
|
315
|
+
- **Default TTL**: 24 hours (configurable: `"30m"`, `"2h"`, `"7d"`, or integer seconds)
|
|
316
|
+
- **Automatic**: cache hits skip all network and browser work
|
|
317
|
+
- **Graceful degradation**: cache read/write failures never crash a fetch — they produce warnings
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
# Disable caching for a single request
|
|
321
|
+
result = await client.fetch("https://example.com", use_cache=False)
|
|
322
|
+
|
|
323
|
+
# Override TTL per-request
|
|
324
|
+
result = await client.fetch("https://example.com", cache_ttl="1h")
|
|
325
|
+
|
|
326
|
+
# Use a custom cache location
|
|
327
|
+
client = PageFetch(cache_path="/path/to/custom_cache.sqlite3")
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Proxy Support
|
|
333
|
+
|
|
334
|
+
PageFetch natively supports **rotating residential proxy** providers:
|
|
335
|
+
|
|
336
|
+
| Provider | Env Var (Full URL) | Env Vars (Components) |
|
|
337
|
+
|---|---|---|
|
|
338
|
+
| **Decodo** | `DECODO_PROXY_URL` | `DECODO_HOST`, `DECODO_PORT`, `DECODO_USERNAME`, `DECODO_PASSWORD` |
|
|
339
|
+
| **DataImpulse** | `DATAIMPULSE_PROXY_URL` | `DATAIMPULSE_HOST`, `DATAIMPULSE_PORT`, `DATAIMPULSE_USERNAME`, `DATAIMPULSE_PASSWORD` |
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
# Use a proxy provider
|
|
343
|
+
async with PageFetch(proxy="decodo") as client:
|
|
344
|
+
result = await client.fetch("https://example.com")
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Credentials are **never** included in results, logs, or cache keys. Configure
|
|
348
|
+
either a full proxy URL or the individual components — PageFetch validates
|
|
349
|
+
both forms automatically.
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Non-HTML Content
|
|
354
|
+
|
|
355
|
+
PageFetch handles content types beyond HTML natively:
|
|
356
|
+
|
|
357
|
+
| Content Type | Detection | Extraction |
|
|
358
|
+
|---|---|---|
|
|
359
|
+
| **PDF** | Magic bytes + `Content-Type` | Text via `pypdf` |
|
|
360
|
+
| **XML** | `Content-Type` matching `+xml` or `application/xml` | Preserved as `.text` |
|
|
361
|
+
| **Plain text** | Fallback when no structured type matches | Served as `.text` directly |
|
|
362
|
+
|
|
363
|
+
No browser overhead is incurred for non-HTML content — detection happens
|
|
364
|
+
at the HTTP response level before any processing pipeline runs.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Configuration Reference
|
|
369
|
+
|
|
370
|
+
| Parameter | Type | Default | Description |
|
|
371
|
+
|---|---|---|---|
|
|
372
|
+
| `mode` | `str` | `"auto"` | Fetch strategy: `"auto"`, `"http"`, or `"browser"` |
|
|
373
|
+
| `proxy` | `str` | `"none"` | Proxy provider: `"none"`, `"decodo"`, or `"dataimpulse"` |
|
|
374
|
+
| `http_concurrency` | `int` | `10` | Maximum concurrent HTTP connections |
|
|
375
|
+
| `browser_concurrency` | `int` | `4` | Maximum concurrent browser instances |
|
|
376
|
+
| `cache_enabled` | `bool` | `True` | Enable SQLite disk cache |
|
|
377
|
+
| `cache_ttl` | `str \| int` | `"24h"` | Cache time-to-live |
|
|
378
|
+
| `cache_path` | `str \| Path` | *platform default* | Custom SQLite cache file path |
|
|
379
|
+
| `http_timeout` | `float` | `20.0` | HTTP request timeout in seconds |
|
|
380
|
+
| `browser_timeout` | `float` | `45.0` | Browser page load timeout in seconds |
|
|
381
|
+
| `retries_http` | `int` | `3` | Automatic retries on retryable HTTP errors |
|
|
382
|
+
| `retries_browser` | `int` | `2` | Automatic retries on browser failures |
|
|
383
|
+
| `max_redirects` | `int` | `10` | Maximum redirect chain to follow |
|
|
384
|
+
| `max_content_size` | `int` | `25 MiB` | Maximum response body in bytes |
|
|
385
|
+
| `confidence_threshold` | `float` | `0.80` | Threshold for browser fallback in auto mode |
|
|
386
|
+
| `raise_on_error` | `bool` | `False` | Raise `PageFetchError` on failure instead of returning error result |
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Development
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
# Clone and set up
|
|
394
|
+
git clone <repo-url> && cd pagefetch
|
|
395
|
+
|
|
396
|
+
# Install with test dependencies
|
|
397
|
+
pip install -e ".[test]"
|
|
398
|
+
|
|
399
|
+
# Run the test suite (browser integration tests are opt-in)
|
|
400
|
+
pytest
|
|
401
|
+
|
|
402
|
+
# Lint
|
|
403
|
+
ruff check .
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Browser integration requires the separately downloaded Camoufox binary and is
|
|
407
|
+
therefore kept optional in deterministic test environments. The test suite is
|
|
408
|
+
designed to run fully offline — URLs are served via local fixtures.
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## License
|
|
413
|
+
|
|
414
|
+
PageFetch is released under the [MIT License](LICENSE).
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
<p align="center">
|
|
419
|
+
<sub>
|
|
420
|
+
Built with ❤️ for developers who need reliable, structured web content
|
|
421
|
+
without fighting bot detection.
|
|
422
|
+
</sub>
|
|
423
|
+
</p>
|