site-shot 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.
- site_shot-0.1.0/.gitignore +15 -0
- site_shot-0.1.0/LICENSE +21 -0
- site_shot-0.1.0/PKG-INFO +294 -0
- site_shot-0.1.0/README.md +261 -0
- site_shot-0.1.0/pyproject.toml +83 -0
- site_shot-0.1.0/scripts/smoke.py +57 -0
- site_shot-0.1.0/src/site_shot/__init__.py +47 -0
- site_shot-0.1.0/src/site_shot/client.py +682 -0
- site_shot-0.1.0/src/site_shot/errors.py +71 -0
- site_shot-0.1.0/src/site_shot/py.typed +0 -0
- site_shot-0.1.0/tests/test_client.py +1088 -0
- site_shot-0.1.0/tests/test_typing_examples.py +155 -0
site_shot-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Site-Shot
|
|
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.
|
site_shot-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: site-shot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Site-Shot screenshot API — capture website screenshots in a real Chromium browser (full-page, country proxies, ad & cookie-banner removal). Zero dependencies.
|
|
5
|
+
Project-URL: Homepage, https://www.site-shot.com/
|
|
6
|
+
Project-URL: Documentation, https://www.site-shot.com/#documentation
|
|
7
|
+
Project-URL: API keys & pricing, https://www.site-shot.com/pricing/
|
|
8
|
+
Project-URL: For AI agents, https://www.site-shot.com/ai-agents/
|
|
9
|
+
Project-URL: Repository, https://github.com/site-shot/site-shot-python
|
|
10
|
+
Project-URL: Issues, https://github.com/site-shot/site-shot-python/issues
|
|
11
|
+
Author-email: Site-Shot <support@site-shot.com>
|
|
12
|
+
Maintainer-email: Site-Shot <support@site-shot.com>
|
|
13
|
+
License: MIT
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Keywords: api-client,full-page-screenshot,screenshot,screenshot-api,sdk,site-shot,url-to-image,web-capture,web-scraping,website-screenshot
|
|
16
|
+
Classifier: Development Status :: 4 - Beta
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
27
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
28
|
+
Classifier: Topic :: Multimedia :: Graphics :: Capture :: Screen Capture
|
|
29
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
|
+
Classifier: Typing :: Typed
|
|
31
|
+
Requires-Python: >=3.9
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# site-shot
|
|
35
|
+
|
|
36
|
+
Official Python SDK for the [Site-Shot](https://www.site-shot.com/) screenshot API.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install site-shot
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Capture website screenshots in a real Chromium browser: full-page capture, country
|
|
43
|
+
proxies, automatic ad & cookie-banner removal. **Zero dependencies** (standard
|
|
44
|
+
library only), Python >= 3.9, fully typed (`py.typed`), sync.
|
|
45
|
+
|
|
46
|
+
API plans from **$5/mo** (X-Lite, 2,000 screenshots). The
|
|
47
|
+
[free browser tool](https://www.site-shot.com/) is no-signup, but the **API
|
|
48
|
+
requires a key** — get one at <https://www.site-shot.com/pricing/>.
|
|
49
|
+
|
|
50
|
+
## Quickstart
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from site_shot import SiteShot
|
|
54
|
+
|
|
55
|
+
client = SiteShot("YOUR_API_KEY") # or set SITESHOT_API_KEY in the env
|
|
56
|
+
|
|
57
|
+
png = client.capture("example.com", full_size=True) # png is bytes
|
|
58
|
+
with open("shot.png", "wb") as f:
|
|
59
|
+
f.write(png)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The import package is `site_shot`; the pip name is `site-shot` (PyPI treats the
|
|
63
|
+
two spellings as the same project).
|
|
64
|
+
|
|
65
|
+
Building an AI agent with an MCP-capable client instead? Use the sibling MCP
|
|
66
|
+
server: `npx -y site-shot-mcp`. Working in Node? `npm install site-shot-sdk`.
|
|
67
|
+
|
|
68
|
+
## Capture cleanly (ads and cookie banners removed)
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
png = client.capture(
|
|
72
|
+
"https://example.com/",
|
|
73
|
+
no_ads=True,
|
|
74
|
+
no_cookie_popup=True,
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Return modes
|
|
79
|
+
|
|
80
|
+
One capture concept, four return modes — pick the method, not a flag:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
# 1. Bytes (primary mode)
|
|
84
|
+
png = client.capture("https://example.com/")
|
|
85
|
+
|
|
86
|
+
# 2. Straight to a file
|
|
87
|
+
client.capture_to_file("https://example.com/", "shot.png")
|
|
88
|
+
|
|
89
|
+
# 3. Base64 string (data URLs, LLM vision payloads)
|
|
90
|
+
b64 = client.capture_base64("https://example.com/")
|
|
91
|
+
|
|
92
|
+
# 4. Full JSON result (image plus metadata; add source_code for rendered HTML)
|
|
93
|
+
meta = client.capture_json("https://example.com/", source_code=True)
|
|
94
|
+
print(meta["source_code"][:200])
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`url` is positional-only, every capture option is a keyword argument. An options
|
|
98
|
+
dict works too: `client.capture(url, **options)`.
|
|
99
|
+
|
|
100
|
+
### `build_url()` — build the request URL without executing it
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
url = client.build_url("https://example.com/", width=1280)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
> **⚠️ Key-leak warning:** the returned URL **embeds your API key** (`userkey`),
|
|
107
|
+
> and Site-Shot has no signed-URL scheme. Use it for debugging or server-side
|
|
108
|
+
> proxying only. **Never** put it in an `<img src>` or anywhere a browser or
|
|
109
|
+
> third party can see it.
|
|
110
|
+
|
|
111
|
+
## Screenshots from another country
|
|
112
|
+
|
|
113
|
+
Pass `country` as an **ISO 3166-1 alpha-2 code** (e.g. `"DE"`, `"BR"`, `"JP"` —
|
|
114
|
+
the current list is at <https://www.site-shot.com/countries/>). It automatically
|
|
115
|
+
sets a matching IP, language, time zone, and geolocation. Full country names are
|
|
116
|
+
not valid values.
|
|
117
|
+
|
|
118
|
+
By default, if the requested country has no capacity at that moment, the API
|
|
119
|
+
silently falls back to a US vantage point. Set `strict_country` to fail fast
|
|
120
|
+
instead:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from site_shot import SiteShot, CountryUnavailableError
|
|
124
|
+
|
|
125
|
+
client = SiteShot("YOUR_API_KEY", retries=2) # recommended for geo captures
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
shot = client.capture(
|
|
129
|
+
"https://whatismycountry.com/",
|
|
130
|
+
country="DE", # ISO 3166-1 alpha-2, never "Germany"
|
|
131
|
+
strict_country=True,
|
|
132
|
+
no_ads=True,
|
|
133
|
+
no_cookie_popup=True,
|
|
134
|
+
)
|
|
135
|
+
except CountryUnavailableError:
|
|
136
|
+
... # No live DE capacity right now — retry later or drop strict_country.
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
A GPS override, independent of the exit IP:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
png = client.capture(
|
|
143
|
+
"https://whatismycountry.com/",
|
|
144
|
+
country="JP", # ISO code
|
|
145
|
+
geolocation="35.6762,139.6503",
|
|
146
|
+
time_zone="Asia/Tokyo",
|
|
147
|
+
language="ja",
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
More on geotargeted screenshots:
|
|
152
|
+
<https://www.site-shot.com/blog/screenshot-website-from-another-country/>
|
|
153
|
+
|
|
154
|
+
## Options
|
|
155
|
+
|
|
156
|
+
Option names mirror the HTTP query parameters **verbatim** (snake_case) — the
|
|
157
|
+
[API reference on the homepage](https://www.site-shot.com/#documentation) and
|
|
158
|
+
this SDK share one vocabulary. Booleans are accepted and coerced to `1`/`0`.
|
|
159
|
+
**Unknown options pass through verbatim**, so future API params work without an
|
|
160
|
+
SDK update.
|
|
161
|
+
|
|
162
|
+
| Option | Type / range | API default | Notes |
|
|
163
|
+
|---|---|---|---|
|
|
164
|
+
| `url` | str, **required** (positional-only) | — | bare domains like `example.com` accepted (`https://` assumed) |
|
|
165
|
+
| `width` | int 100–8000 | 1024 | viewport width |
|
|
166
|
+
| `height` | int 100–20000 | 768 | viewport height |
|
|
167
|
+
| `zoom` | int 5–1000 | 100 | percentage zoom |
|
|
168
|
+
| `full_size` | bool | False | full-page capture (height capped by `max_height`) |
|
|
169
|
+
| `max_height` | int 100–20000 | 15000 | only meaningful with `full_size` |
|
|
170
|
+
| `scaled_width` | int 50–10000 | — | scale result image to width |
|
|
171
|
+
| `format` | `"png"` \| `"jpeg"` | png | |
|
|
172
|
+
| `delay_time` | int ms 0–60000 | 1500 | wait before capture (SPAs, animations) |
|
|
173
|
+
| `timeout` | int ms 0–120000 | 60000 | server-side render deadline |
|
|
174
|
+
| `user_agent` | str | — | custom UA for the rendering browser |
|
|
175
|
+
| `request_headers` | dict | — | emitted as repeated `request_header=Name:value` params |
|
|
176
|
+
| `http_proxy` / `proxy_username` / `proxy_password` | str | — | bring-your-own-proxy passthrough |
|
|
177
|
+
| `proxy_rotation` | bool | True | |
|
|
178
|
+
| `source_code` | bool | False | include rendered HTML (use with `capture_json`) |
|
|
179
|
+
| `javascript_code` | str | — | inject JS into the page before capture |
|
|
180
|
+
| `no_ads` | bool | False | remove ads |
|
|
181
|
+
| `no_cookie_popup` | bool | False | remove cookie-consent banners |
|
|
182
|
+
| `country` | ISO 3166-1 alpha-2 | — | e.g. `"DE"` — auto-sets language/time zone/geolocation |
|
|
183
|
+
| `strict_country` | bool | False | fail fast instead of silent US fallback |
|
|
184
|
+
| `geolocation` | `"lat,lng"` | — | GPS override, independent of IP |
|
|
185
|
+
| `language` | str | from `country` / en | Accept-Language |
|
|
186
|
+
| `time_zone` | IANA name | America/New_York | see <https://www.site-shot.com/time-zones/> |
|
|
187
|
+
|
|
188
|
+
`userkey` (your API key) is owned by the `SiteShot` constructor — never a
|
|
189
|
+
per-call option. `response_type` is owned by the return-mode methods.
|
|
190
|
+
|
|
191
|
+
A `CaptureOptions` `TypedDict` is exported for callers who build option dicts:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from site_shot import CaptureOptions
|
|
195
|
+
|
|
196
|
+
options: CaptureOptions = {"width": 1280, "full_size": True, "country": "DE"}
|
|
197
|
+
png = client.capture("https://example.com/", **options)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`url` is positional-only on every capture method, so an options dict that also
|
|
201
|
+
carries a `url` key still type-checks and runs — the positional URL wins, just
|
|
202
|
+
like `userkey`. (These snippets are type-checked under `mypy --strict` in the
|
|
203
|
+
test suite.)
|
|
204
|
+
|
|
205
|
+
The SDK sends GET requests; very long `javascript_code` or `user_agent` values
|
|
206
|
+
can exceed practical URL length limits (~8 KB).
|
|
207
|
+
|
|
208
|
+
## Client options
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
client = SiteShot(
|
|
212
|
+
"YOUR_API_KEY",
|
|
213
|
+
base_url="https://api.site-shot.com/",
|
|
214
|
+
timeout=90, # client-side deadline in SECONDS; default = server `timeout` + 30s headroom
|
|
215
|
+
retries=0, # connection-level retries only (see below)
|
|
216
|
+
)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The API key falls back to the `SITESHOT_API_KEY` environment variable — the same
|
|
220
|
+
variable the [Site-Shot MCP server](https://www.npmjs.com/package/site-shot-mcp)
|
|
221
|
+
uses:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
import os
|
|
225
|
+
client = SiteShot() # reads SITESHOT_API_KEY
|
|
226
|
+
client = SiteShot(os.environ["SITESHOT_API_KEY"]) # or pass it explicitly
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Two different things are called "timeout", exactly as in the HTTP API:
|
|
230
|
+
|
|
231
|
+
- the constructor's `timeout` is the **client-side deadline in seconds**, and it
|
|
232
|
+
bounds the *whole* exchange — connecting, waiting for the status line and
|
|
233
|
+
headers, and downloading the image body (a slow drip in any of those phases
|
|
234
|
+
cannot outlast it);
|
|
235
|
+
- the capture option `timeout` is the **server-side render deadline in
|
|
236
|
+
milliseconds** (the verbatim HTTP param name).
|
|
237
|
+
|
|
238
|
+
`SiteShot` also works as a context manager (`with SiteShot(...) as client:`); it
|
|
239
|
+
holds no pooled resources, so closing it is a no-op.
|
|
240
|
+
|
|
241
|
+
## Errors
|
|
242
|
+
|
|
243
|
+
Every error is a subclass of `SiteShotError` and carries `http_status` and the
|
|
244
|
+
raw response `body` where available.
|
|
245
|
+
|
|
246
|
+
| Error | Raised when |
|
|
247
|
+
|---|---|
|
|
248
|
+
| `AuthError` | missing or rejected API key (also raised by the constructor on an empty key) |
|
|
249
|
+
| `QuotaError` | plan quota exhausted / payment required |
|
|
250
|
+
| `CountryUnavailableError` | `strict_country` capture and the requested country has no capacity right now |
|
|
251
|
+
| `InvalidParamsError` | the API rejected a parameter (out-of-range width, bad format, ...) |
|
|
252
|
+
| `SiteShotTimeoutError` | client-side deadline, or the API reported a render timeout |
|
|
253
|
+
| `APIError` | anything else (server errors, unparseable bodies, connection failures) |
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from site_shot import SiteShot, SiteShotError, QuotaError
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
png = client.capture("https://example.com/")
|
|
260
|
+
except QuotaError as e:
|
|
261
|
+
print("out of quota:", e, e.http_status)
|
|
262
|
+
except SiteShotError as e:
|
|
263
|
+
print("capture failed:", e)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Under the hood the SDK always asks the API for a JSON response and decodes the
|
|
267
|
+
image itself — so errors surface as typed exceptions instead of an error picture
|
|
268
|
+
pretending to be your screenshot.
|
|
269
|
+
|
|
270
|
+
## Retries
|
|
271
|
+
|
|
272
|
+
Default: `0`. Screenshots cost quota, and a timed-out render may still have
|
|
273
|
+
consumed one — so the SDK never auto-retries a render that completed with an
|
|
274
|
+
error. The `retries` option applies to **connection-level failures only** (DNS,
|
|
275
|
+
connection reset, no bytes received), with jittered backoff. `retries=2` is a
|
|
276
|
+
sensible setting for `country=` captures.
|
|
277
|
+
|
|
278
|
+
## Requirements
|
|
279
|
+
|
|
280
|
+
- Python >= 3.9
|
|
281
|
+
- Zero dependencies (standard library `urllib` only)
|
|
282
|
+
|
|
283
|
+
## Links
|
|
284
|
+
|
|
285
|
+
- Pricing & API keys: <https://www.site-shot.com/pricing/>
|
|
286
|
+
- For AI agents & assistants: <https://www.site-shot.com/ai-agents/>
|
|
287
|
+
- Supported countries: <https://www.site-shot.com/countries/>
|
|
288
|
+
- Time zones: <https://www.site-shot.com/time-zones/>
|
|
289
|
+
- Node.js SDK: `npm install site-shot-sdk`
|
|
290
|
+
- MCP server (agent tooling sibling): `npx -y site-shot-mcp`
|
|
291
|
+
|
|
292
|
+
## License
|
|
293
|
+
|
|
294
|
+
MIT
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# site-shot
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [Site-Shot](https://www.site-shot.com/) screenshot API.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install site-shot
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Capture website screenshots in a real Chromium browser: full-page capture, country
|
|
10
|
+
proxies, automatic ad & cookie-banner removal. **Zero dependencies** (standard
|
|
11
|
+
library only), Python >= 3.9, fully typed (`py.typed`), sync.
|
|
12
|
+
|
|
13
|
+
API plans from **$5/mo** (X-Lite, 2,000 screenshots). The
|
|
14
|
+
[free browser tool](https://www.site-shot.com/) is no-signup, but the **API
|
|
15
|
+
requires a key** — get one at <https://www.site-shot.com/pricing/>.
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from site_shot import SiteShot
|
|
21
|
+
|
|
22
|
+
client = SiteShot("YOUR_API_KEY") # or set SITESHOT_API_KEY in the env
|
|
23
|
+
|
|
24
|
+
png = client.capture("example.com", full_size=True) # png is bytes
|
|
25
|
+
with open("shot.png", "wb") as f:
|
|
26
|
+
f.write(png)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The import package is `site_shot`; the pip name is `site-shot` (PyPI treats the
|
|
30
|
+
two spellings as the same project).
|
|
31
|
+
|
|
32
|
+
Building an AI agent with an MCP-capable client instead? Use the sibling MCP
|
|
33
|
+
server: `npx -y site-shot-mcp`. Working in Node? `npm install site-shot-sdk`.
|
|
34
|
+
|
|
35
|
+
## Capture cleanly (ads and cookie banners removed)
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
png = client.capture(
|
|
39
|
+
"https://example.com/",
|
|
40
|
+
no_ads=True,
|
|
41
|
+
no_cookie_popup=True,
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Return modes
|
|
46
|
+
|
|
47
|
+
One capture concept, four return modes — pick the method, not a flag:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
# 1. Bytes (primary mode)
|
|
51
|
+
png = client.capture("https://example.com/")
|
|
52
|
+
|
|
53
|
+
# 2. Straight to a file
|
|
54
|
+
client.capture_to_file("https://example.com/", "shot.png")
|
|
55
|
+
|
|
56
|
+
# 3. Base64 string (data URLs, LLM vision payloads)
|
|
57
|
+
b64 = client.capture_base64("https://example.com/")
|
|
58
|
+
|
|
59
|
+
# 4. Full JSON result (image plus metadata; add source_code for rendered HTML)
|
|
60
|
+
meta = client.capture_json("https://example.com/", source_code=True)
|
|
61
|
+
print(meta["source_code"][:200])
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`url` is positional-only, every capture option is a keyword argument. An options
|
|
65
|
+
dict works too: `client.capture(url, **options)`.
|
|
66
|
+
|
|
67
|
+
### `build_url()` — build the request URL without executing it
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
url = client.build_url("https://example.com/", width=1280)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
> **⚠️ Key-leak warning:** the returned URL **embeds your API key** (`userkey`),
|
|
74
|
+
> and Site-Shot has no signed-URL scheme. Use it for debugging or server-side
|
|
75
|
+
> proxying only. **Never** put it in an `<img src>` or anywhere a browser or
|
|
76
|
+
> third party can see it.
|
|
77
|
+
|
|
78
|
+
## Screenshots from another country
|
|
79
|
+
|
|
80
|
+
Pass `country` as an **ISO 3166-1 alpha-2 code** (e.g. `"DE"`, `"BR"`, `"JP"` —
|
|
81
|
+
the current list is at <https://www.site-shot.com/countries/>). It automatically
|
|
82
|
+
sets a matching IP, language, time zone, and geolocation. Full country names are
|
|
83
|
+
not valid values.
|
|
84
|
+
|
|
85
|
+
By default, if the requested country has no capacity at that moment, the API
|
|
86
|
+
silently falls back to a US vantage point. Set `strict_country` to fail fast
|
|
87
|
+
instead:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from site_shot import SiteShot, CountryUnavailableError
|
|
91
|
+
|
|
92
|
+
client = SiteShot("YOUR_API_KEY", retries=2) # recommended for geo captures
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
shot = client.capture(
|
|
96
|
+
"https://whatismycountry.com/",
|
|
97
|
+
country="DE", # ISO 3166-1 alpha-2, never "Germany"
|
|
98
|
+
strict_country=True,
|
|
99
|
+
no_ads=True,
|
|
100
|
+
no_cookie_popup=True,
|
|
101
|
+
)
|
|
102
|
+
except CountryUnavailableError:
|
|
103
|
+
... # No live DE capacity right now — retry later or drop strict_country.
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
A GPS override, independent of the exit IP:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
png = client.capture(
|
|
110
|
+
"https://whatismycountry.com/",
|
|
111
|
+
country="JP", # ISO code
|
|
112
|
+
geolocation="35.6762,139.6503",
|
|
113
|
+
time_zone="Asia/Tokyo",
|
|
114
|
+
language="ja",
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
More on geotargeted screenshots:
|
|
119
|
+
<https://www.site-shot.com/blog/screenshot-website-from-another-country/>
|
|
120
|
+
|
|
121
|
+
## Options
|
|
122
|
+
|
|
123
|
+
Option names mirror the HTTP query parameters **verbatim** (snake_case) — the
|
|
124
|
+
[API reference on the homepage](https://www.site-shot.com/#documentation) and
|
|
125
|
+
this SDK share one vocabulary. Booleans are accepted and coerced to `1`/`0`.
|
|
126
|
+
**Unknown options pass through verbatim**, so future API params work without an
|
|
127
|
+
SDK update.
|
|
128
|
+
|
|
129
|
+
| Option | Type / range | API default | Notes |
|
|
130
|
+
|---|---|---|---|
|
|
131
|
+
| `url` | str, **required** (positional-only) | — | bare domains like `example.com` accepted (`https://` assumed) |
|
|
132
|
+
| `width` | int 100–8000 | 1024 | viewport width |
|
|
133
|
+
| `height` | int 100–20000 | 768 | viewport height |
|
|
134
|
+
| `zoom` | int 5–1000 | 100 | percentage zoom |
|
|
135
|
+
| `full_size` | bool | False | full-page capture (height capped by `max_height`) |
|
|
136
|
+
| `max_height` | int 100–20000 | 15000 | only meaningful with `full_size` |
|
|
137
|
+
| `scaled_width` | int 50–10000 | — | scale result image to width |
|
|
138
|
+
| `format` | `"png"` \| `"jpeg"` | png | |
|
|
139
|
+
| `delay_time` | int ms 0–60000 | 1500 | wait before capture (SPAs, animations) |
|
|
140
|
+
| `timeout` | int ms 0–120000 | 60000 | server-side render deadline |
|
|
141
|
+
| `user_agent` | str | — | custom UA for the rendering browser |
|
|
142
|
+
| `request_headers` | dict | — | emitted as repeated `request_header=Name:value` params |
|
|
143
|
+
| `http_proxy` / `proxy_username` / `proxy_password` | str | — | bring-your-own-proxy passthrough |
|
|
144
|
+
| `proxy_rotation` | bool | True | |
|
|
145
|
+
| `source_code` | bool | False | include rendered HTML (use with `capture_json`) |
|
|
146
|
+
| `javascript_code` | str | — | inject JS into the page before capture |
|
|
147
|
+
| `no_ads` | bool | False | remove ads |
|
|
148
|
+
| `no_cookie_popup` | bool | False | remove cookie-consent banners |
|
|
149
|
+
| `country` | ISO 3166-1 alpha-2 | — | e.g. `"DE"` — auto-sets language/time zone/geolocation |
|
|
150
|
+
| `strict_country` | bool | False | fail fast instead of silent US fallback |
|
|
151
|
+
| `geolocation` | `"lat,lng"` | — | GPS override, independent of IP |
|
|
152
|
+
| `language` | str | from `country` / en | Accept-Language |
|
|
153
|
+
| `time_zone` | IANA name | America/New_York | see <https://www.site-shot.com/time-zones/> |
|
|
154
|
+
|
|
155
|
+
`userkey` (your API key) is owned by the `SiteShot` constructor — never a
|
|
156
|
+
per-call option. `response_type` is owned by the return-mode methods.
|
|
157
|
+
|
|
158
|
+
A `CaptureOptions` `TypedDict` is exported for callers who build option dicts:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from site_shot import CaptureOptions
|
|
162
|
+
|
|
163
|
+
options: CaptureOptions = {"width": 1280, "full_size": True, "country": "DE"}
|
|
164
|
+
png = client.capture("https://example.com/", **options)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`url` is positional-only on every capture method, so an options dict that also
|
|
168
|
+
carries a `url` key still type-checks and runs — the positional URL wins, just
|
|
169
|
+
like `userkey`. (These snippets are type-checked under `mypy --strict` in the
|
|
170
|
+
test suite.)
|
|
171
|
+
|
|
172
|
+
The SDK sends GET requests; very long `javascript_code` or `user_agent` values
|
|
173
|
+
can exceed practical URL length limits (~8 KB).
|
|
174
|
+
|
|
175
|
+
## Client options
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
client = SiteShot(
|
|
179
|
+
"YOUR_API_KEY",
|
|
180
|
+
base_url="https://api.site-shot.com/",
|
|
181
|
+
timeout=90, # client-side deadline in SECONDS; default = server `timeout` + 30s headroom
|
|
182
|
+
retries=0, # connection-level retries only (see below)
|
|
183
|
+
)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The API key falls back to the `SITESHOT_API_KEY` environment variable — the same
|
|
187
|
+
variable the [Site-Shot MCP server](https://www.npmjs.com/package/site-shot-mcp)
|
|
188
|
+
uses:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
import os
|
|
192
|
+
client = SiteShot() # reads SITESHOT_API_KEY
|
|
193
|
+
client = SiteShot(os.environ["SITESHOT_API_KEY"]) # or pass it explicitly
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Two different things are called "timeout", exactly as in the HTTP API:
|
|
197
|
+
|
|
198
|
+
- the constructor's `timeout` is the **client-side deadline in seconds**, and it
|
|
199
|
+
bounds the *whole* exchange — connecting, waiting for the status line and
|
|
200
|
+
headers, and downloading the image body (a slow drip in any of those phases
|
|
201
|
+
cannot outlast it);
|
|
202
|
+
- the capture option `timeout` is the **server-side render deadline in
|
|
203
|
+
milliseconds** (the verbatim HTTP param name).
|
|
204
|
+
|
|
205
|
+
`SiteShot` also works as a context manager (`with SiteShot(...) as client:`); it
|
|
206
|
+
holds no pooled resources, so closing it is a no-op.
|
|
207
|
+
|
|
208
|
+
## Errors
|
|
209
|
+
|
|
210
|
+
Every error is a subclass of `SiteShotError` and carries `http_status` and the
|
|
211
|
+
raw response `body` where available.
|
|
212
|
+
|
|
213
|
+
| Error | Raised when |
|
|
214
|
+
|---|---|
|
|
215
|
+
| `AuthError` | missing or rejected API key (also raised by the constructor on an empty key) |
|
|
216
|
+
| `QuotaError` | plan quota exhausted / payment required |
|
|
217
|
+
| `CountryUnavailableError` | `strict_country` capture and the requested country has no capacity right now |
|
|
218
|
+
| `InvalidParamsError` | the API rejected a parameter (out-of-range width, bad format, ...) |
|
|
219
|
+
| `SiteShotTimeoutError` | client-side deadline, or the API reported a render timeout |
|
|
220
|
+
| `APIError` | anything else (server errors, unparseable bodies, connection failures) |
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from site_shot import SiteShot, SiteShotError, QuotaError
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
png = client.capture("https://example.com/")
|
|
227
|
+
except QuotaError as e:
|
|
228
|
+
print("out of quota:", e, e.http_status)
|
|
229
|
+
except SiteShotError as e:
|
|
230
|
+
print("capture failed:", e)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Under the hood the SDK always asks the API for a JSON response and decodes the
|
|
234
|
+
image itself — so errors surface as typed exceptions instead of an error picture
|
|
235
|
+
pretending to be your screenshot.
|
|
236
|
+
|
|
237
|
+
## Retries
|
|
238
|
+
|
|
239
|
+
Default: `0`. Screenshots cost quota, and a timed-out render may still have
|
|
240
|
+
consumed one — so the SDK never auto-retries a render that completed with an
|
|
241
|
+
error. The `retries` option applies to **connection-level failures only** (DNS,
|
|
242
|
+
connection reset, no bytes received), with jittered backoff. `retries=2` is a
|
|
243
|
+
sensible setting for `country=` captures.
|
|
244
|
+
|
|
245
|
+
## Requirements
|
|
246
|
+
|
|
247
|
+
- Python >= 3.9
|
|
248
|
+
- Zero dependencies (standard library `urllib` only)
|
|
249
|
+
|
|
250
|
+
## Links
|
|
251
|
+
|
|
252
|
+
- Pricing & API keys: <https://www.site-shot.com/pricing/>
|
|
253
|
+
- For AI agents & assistants: <https://www.site-shot.com/ai-agents/>
|
|
254
|
+
- Supported countries: <https://www.site-shot.com/countries/>
|
|
255
|
+
- Time zones: <https://www.site-shot.com/time-zones/>
|
|
256
|
+
- Node.js SDK: `npm install site-shot-sdk`
|
|
257
|
+
- MCP server (agent tooling sibling): `npx -y site-shot-mcp`
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
MIT
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "site-shot"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for the Site-Shot screenshot API — capture website screenshots in a real Chromium browser (full-page, country proxies, ad & cookie-banner removal). Zero dependencies."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Site-Shot", email = "support@site-shot.com" }]
|
|
13
|
+
maintainers = [{ name = "Site-Shot", email = "support@site-shot.com" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"screenshot",
|
|
16
|
+
"screenshot-api",
|
|
17
|
+
"website-screenshot",
|
|
18
|
+
"site-shot",
|
|
19
|
+
"sdk",
|
|
20
|
+
"api-client",
|
|
21
|
+
"full-page-screenshot",
|
|
22
|
+
"url-to-image",
|
|
23
|
+
"web-capture",
|
|
24
|
+
"web-scraping",
|
|
25
|
+
]
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Development Status :: 4 - Beta",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"License :: OSI Approved :: MIT License",
|
|
30
|
+
"Operating System :: OS Independent",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
33
|
+
"Programming Language :: Python :: 3.9",
|
|
34
|
+
"Programming Language :: Python :: 3.10",
|
|
35
|
+
"Programming Language :: Python :: 3.11",
|
|
36
|
+
"Programming Language :: Python :: 3.12",
|
|
37
|
+
"Programming Language :: Python :: 3.13",
|
|
38
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
39
|
+
"Topic :: Multimedia :: Graphics :: Capture :: Screen Capture",
|
|
40
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
41
|
+
"Typing :: Typed",
|
|
42
|
+
]
|
|
43
|
+
dependencies = []
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Homepage = "https://www.site-shot.com/"
|
|
47
|
+
Documentation = "https://www.site-shot.com/#documentation"
|
|
48
|
+
"API keys & pricing" = "https://www.site-shot.com/pricing/"
|
|
49
|
+
"For AI agents" = "https://www.site-shot.com/ai-agents/"
|
|
50
|
+
Repository = "https://github.com/site-shot/site-shot-python"
|
|
51
|
+
Issues = "https://github.com/site-shot/site-shot-python/issues"
|
|
52
|
+
|
|
53
|
+
[tool.hatch.build.targets.wheel]
|
|
54
|
+
packages = ["src/site_shot"]
|
|
55
|
+
|
|
56
|
+
[tool.hatch.build.targets.sdist]
|
|
57
|
+
include = [
|
|
58
|
+
"/src",
|
|
59
|
+
"/tests",
|
|
60
|
+
"/scripts",
|
|
61
|
+
"/README.md",
|
|
62
|
+
"/LICENSE",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[tool.mypy]
|
|
66
|
+
strict = true
|
|
67
|
+
# Always check the checked-out source, never a stale installed copy.
|
|
68
|
+
mypy_path = "src"
|
|
69
|
+
# The tests are checked too: consumer-shaped usage is exactly where the exported
|
|
70
|
+
# TypedDict broke, and checking only src/ is why that shipped. Bodies are
|
|
71
|
+
# checked everywhere; only the legacy test module is excused from annotating
|
|
72
|
+
# every def.
|
|
73
|
+
files = ["src/site_shot", "tests"]
|
|
74
|
+
|
|
75
|
+
[[tool.mypy.overrides]]
|
|
76
|
+
module = ["test_client", "tests.test_client"]
|
|
77
|
+
disallow_untyped_defs = false
|
|
78
|
+
disallow_incomplete_defs = false
|
|
79
|
+
disallow_untyped_calls = false
|
|
80
|
+
|
|
81
|
+
[tool.ruff]
|
|
82
|
+
line-length = 100
|
|
83
|
+
target-version = "py39"
|