snapapi-python 1.3.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.
- snapapi_python-1.3.0/PKG-INFO +13 -0
- snapapi_python-1.3.0/README.md +137 -0
- snapapi_python-1.3.0/setup.cfg +4 -0
- snapapi_python-1.3.0/setup.py +12 -0
- snapapi_python-1.3.0/snapapi.py +269 -0
- snapapi_python-1.3.0/snapapi_python.egg-info/PKG-INFO +13 -0
- snapapi_python-1.3.0/snapapi_python.egg-info/SOURCES.txt +8 -0
- snapapi_python-1.3.0/snapapi_python.egg-info/dependency_links.txt +1 -0
- snapapi_python-1.3.0/snapapi_python.egg-info/requires.txt +1 -0
- snapapi_python-1.3.0/snapapi_python.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: snapapi-python
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: Official Python SDK for SnapAPI — screenshot, metadata extraction, and HTML rendering
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: screenshot,api,webpage-capture,snapapi
|
|
7
|
+
Requires-Python: >=3.7
|
|
8
|
+
Requires-Dist: requests>=2.20.0
|
|
9
|
+
Dynamic: keywords
|
|
10
|
+
Dynamic: license
|
|
11
|
+
Dynamic: requires-dist
|
|
12
|
+
Dynamic: requires-python
|
|
13
|
+
Dynamic: summary
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# SnapAPI Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for [SnapAPI](https://snapapi.tech) — capture any website as an image.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install snapapi-python
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or install from source:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from snapapi import SnapAPI
|
|
21
|
+
|
|
22
|
+
snap = SnapAPI("snap_your_key_here")
|
|
23
|
+
|
|
24
|
+
# Basic screenshot — returns PNG bytes
|
|
25
|
+
image = snap.screenshot("example.com")
|
|
26
|
+
with open("screenshot.png", "wb") as f:
|
|
27
|
+
f.write(image)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Options
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
# WebP format with dark mode and full-page capture
|
|
34
|
+
webp = snap.screenshot("example.com", format="webp", dark_mode=True, full_page=True)
|
|
35
|
+
|
|
36
|
+
# Mobile screenshot (iPhone 14)
|
|
37
|
+
mobile = snap.screenshot("example.com", device="iphone14")
|
|
38
|
+
|
|
39
|
+
# Custom viewport
|
|
40
|
+
image = snap.screenshot("example.com", width=1920, height=1080)
|
|
41
|
+
|
|
42
|
+
# Capture a specific element
|
|
43
|
+
header = snap.screenshot("example.com", selector="header")
|
|
44
|
+
|
|
45
|
+
# Add a delay before capture (milliseconds)
|
|
46
|
+
image = snap.screenshot("example.com", delay=2000)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Metadata Mode
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
meta = snap.screenshot("example.com", meta=True)
|
|
53
|
+
print(meta["title"], meta["description"])
|
|
54
|
+
print(meta["headings"]) # [{"level": 1, "text": "..."}, ...]
|
|
55
|
+
print(meta["links"]) # [{"text": "...", "href": "..."}, ...]
|
|
56
|
+
print(meta["text"]) # Extracted visible text (first 2000 chars)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Account & Usage
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
# Check usage
|
|
63
|
+
usage = snap.usage()
|
|
64
|
+
print(f"Used {usage['usage']} of {usage['limit']}")
|
|
65
|
+
|
|
66
|
+
# Account info
|
|
67
|
+
account = snap.account()
|
|
68
|
+
|
|
69
|
+
# Update settings
|
|
70
|
+
snap.update_settings(name="My App", allowed_domains=["example.com"])
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Scheduled Monitors
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# Create a monitor — captures every 15 minutes
|
|
77
|
+
monitor = snap.create_monitor(
|
|
78
|
+
url="https://example.com",
|
|
79
|
+
interval_minutes=15,
|
|
80
|
+
name="Homepage",
|
|
81
|
+
params={"format": "webp", "full_page": True},
|
|
82
|
+
webhook_url="https://your-app.com/webhook",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# List all monitors
|
|
86
|
+
monitors = snap.list_monitors()
|
|
87
|
+
|
|
88
|
+
# Pause / resume
|
|
89
|
+
snap.update_monitor(monitor["id"], status="paused")
|
|
90
|
+
snap.update_monitor(monitor["id"], status="active")
|
|
91
|
+
|
|
92
|
+
# Browse snapshots
|
|
93
|
+
result = snap.list_snapshots(monitor["id"], limit=10)
|
|
94
|
+
for s in result["snapshots"]:
|
|
95
|
+
print(s["taken_at"], s["file_size"])
|
|
96
|
+
|
|
97
|
+
# Download a snapshot
|
|
98
|
+
image = snap.get_snapshot(monitor["id"], result["snapshots"][0]["id"])
|
|
99
|
+
with open("snapshot.webp", "wb") as f:
|
|
100
|
+
f.write(image)
|
|
101
|
+
|
|
102
|
+
# Delete a monitor (cascades to snapshots)
|
|
103
|
+
snap.delete_monitor(monitor["id"])
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
> Monitors require **Starter** tier or above. See [pricing](https://snapapi.tech/#pricing) for limits.
|
|
107
|
+
|
|
108
|
+
## Sign Up
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from snapapi import SnapAPI
|
|
112
|
+
|
|
113
|
+
result = SnapAPI.signup("you@example.com")
|
|
114
|
+
print(f"Your API key: {result['key']}")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Error Handling
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from snapapi import SnapAPI, SnapAPIError
|
|
121
|
+
|
|
122
|
+
snap = SnapAPI("snap_your_key_here")
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
image = snap.screenshot("example.com")
|
|
126
|
+
except SnapAPIError as e:
|
|
127
|
+
print(f"Status: {e.status}, Message: {e.message}")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Requirements
|
|
131
|
+
|
|
132
|
+
- Python 3.7+
|
|
133
|
+
- `requests` >= 2.20.0
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="snapapi-python",
|
|
5
|
+
version="1.3.0",
|
|
6
|
+
description="Official Python SDK for SnapAPI — screenshot, metadata extraction, and HTML rendering",
|
|
7
|
+
py_modules=["snapapi"],
|
|
8
|
+
python_requires=">=3.7",
|
|
9
|
+
install_requires=["requests>=2.20.0"],
|
|
10
|
+
license="MIT",
|
|
11
|
+
keywords=["screenshot", "api", "webpage-capture", "snapapi"],
|
|
12
|
+
)
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SnapAPI Python SDK — capture any website as an image.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from snapapi import SnapAPI
|
|
6
|
+
|
|
7
|
+
snap = SnapAPI("snap_your_key_here")
|
|
8
|
+
image = snap.screenshot("example.com")
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SnapAPIError(Exception):
|
|
15
|
+
"""Raised when the SnapAPI returns a non-2xx response."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, status, message, body=None):
|
|
18
|
+
self.status = status
|
|
19
|
+
self.message = message
|
|
20
|
+
self.body = body
|
|
21
|
+
super().__init__(f"SnapAPI Error {status}: {message}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SnapAPI:
|
|
25
|
+
"""Client for the SnapAPI screenshot service."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, api_key, base_url="https://snapapi.tech"):
|
|
28
|
+
self.api_key = api_key
|
|
29
|
+
self.base_url = base_url.rstrip("/")
|
|
30
|
+
self._session = requests.Session()
|
|
31
|
+
self._session.headers.update({"X-Api-Key": self.api_key})
|
|
32
|
+
|
|
33
|
+
def _request(self, method, path, params=None, json=None):
|
|
34
|
+
"""Send a request and return the response, raising on errors."""
|
|
35
|
+
url = f"{self.base_url}{path}"
|
|
36
|
+
resp = self._session.request(method, url, params=params, json=json)
|
|
37
|
+
|
|
38
|
+
if not resp.ok:
|
|
39
|
+
try:
|
|
40
|
+
body = resp.json()
|
|
41
|
+
message = body.get("error", body.get("message", resp.reason))
|
|
42
|
+
except ValueError:
|
|
43
|
+
body = None
|
|
44
|
+
message = resp.reason or resp.text
|
|
45
|
+
raise SnapAPIError(resp.status_code, message, body)
|
|
46
|
+
|
|
47
|
+
return resp
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def _bool_param(value):
|
|
51
|
+
"""Convert a Python bool to a query-string friendly string."""
|
|
52
|
+
return "true" if value else "false"
|
|
53
|
+
|
|
54
|
+
def screenshot(self, url, width=1280, height=800, format="png", quality=80,
|
|
55
|
+
full_page=False, delay=0, dark_mode=False, block_ads=False,
|
|
56
|
+
cache=True, selector=None, meta=False, device=None):
|
|
57
|
+
"""Capture a screenshot of the given URL.
|
|
58
|
+
|
|
59
|
+
Returns bytes (raw image data) by default.
|
|
60
|
+
When meta=True, returns a dict with page metadata instead.
|
|
61
|
+
"""
|
|
62
|
+
params = {
|
|
63
|
+
"url": url,
|
|
64
|
+
"width": width,
|
|
65
|
+
"height": height,
|
|
66
|
+
"format": format,
|
|
67
|
+
"quality": quality,
|
|
68
|
+
"full_page": self._bool_param(full_page),
|
|
69
|
+
"delay": delay,
|
|
70
|
+
"dark_mode": self._bool_param(dark_mode),
|
|
71
|
+
"block_ads": self._bool_param(block_ads),
|
|
72
|
+
"cache": self._bool_param(cache),
|
|
73
|
+
"meta": self._bool_param(meta),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if selector is not None:
|
|
77
|
+
params["selector"] = selector
|
|
78
|
+
if device is not None:
|
|
79
|
+
params["device"] = device
|
|
80
|
+
|
|
81
|
+
resp = self._request("GET", "/v1/screenshot", params=params)
|
|
82
|
+
|
|
83
|
+
content_type = resp.headers.get("Content-Type", "")
|
|
84
|
+
if "application/json" in content_type:
|
|
85
|
+
return resp.json()
|
|
86
|
+
|
|
87
|
+
return resp.content
|
|
88
|
+
|
|
89
|
+
def metadata(self, url):
|
|
90
|
+
"""Extract metadata from a URL without taking a screenshot.
|
|
91
|
+
|
|
92
|
+
Returns title, description, OG tags, favicon, headings, links, and more.
|
|
93
|
+
Available on all plans.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
url: The webpage URL to extract metadata from.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
dict with metadata fields.
|
|
100
|
+
"""
|
|
101
|
+
resp = self._request("GET", "/v1/metadata", params={"url": url})
|
|
102
|
+
return resp.json()
|
|
103
|
+
|
|
104
|
+
def render(self, html, width=1200, height=630, format="png", quality=90):
|
|
105
|
+
"""Render raw HTML to an image (PNG, JPEG, or WebP).
|
|
106
|
+
|
|
107
|
+
Perfect for OG cards, email previews, and dashboard exports.
|
|
108
|
+
Requires Starter tier or above.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
html: Full HTML string to render (max 2MB).
|
|
112
|
+
width: Viewport width in pixels (default 1200).
|
|
113
|
+
height: Viewport height in pixels (default 630).
|
|
114
|
+
format: Output format — 'png', 'jpeg', or 'webp' (default 'png').
|
|
115
|
+
quality: Image quality 1-100 for jpeg/webp (default 90).
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
bytes (raw image data).
|
|
119
|
+
"""
|
|
120
|
+
resp = self._request("POST", "/v1/render", json={
|
|
121
|
+
"html": html,
|
|
122
|
+
"width": width,
|
|
123
|
+
"height": height,
|
|
124
|
+
"format": format,
|
|
125
|
+
"quality": quality,
|
|
126
|
+
})
|
|
127
|
+
return resp.content
|
|
128
|
+
|
|
129
|
+
def usage(self):
|
|
130
|
+
"""Return current usage statistics (tier, usage count, limits)."""
|
|
131
|
+
resp = self._request("GET", "/v1/usage")
|
|
132
|
+
return resp.json()
|
|
133
|
+
|
|
134
|
+
def account(self):
|
|
135
|
+
"""Return full account information."""
|
|
136
|
+
resp = self._request("GET", "/v1/account")
|
|
137
|
+
return resp.json()
|
|
138
|
+
|
|
139
|
+
def update_settings(self, name=None, allowed_domains=None):
|
|
140
|
+
"""Update account settings.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
name: Display name to set.
|
|
144
|
+
allowed_domains: List of allowed domains for the API key.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
dict with updated settings.
|
|
148
|
+
"""
|
|
149
|
+
payload = {}
|
|
150
|
+
if name is not None:
|
|
151
|
+
payload["name"] = name
|
|
152
|
+
if allowed_domains is not None:
|
|
153
|
+
payload["allowed_domains"] = allowed_domains
|
|
154
|
+
|
|
155
|
+
resp = self._request("POST", "/v1/account/settings", json=payload)
|
|
156
|
+
return resp.json()
|
|
157
|
+
|
|
158
|
+
# ─── Monitors (Scheduled Screenshots) ───
|
|
159
|
+
|
|
160
|
+
def create_monitor(self, url, interval_minutes, name=None, params=None, webhook_url=None):
|
|
161
|
+
"""Create a new scheduled monitor.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
url: URL to monitor.
|
|
165
|
+
interval_minutes: Capture interval in minutes.
|
|
166
|
+
name: Optional display name.
|
|
167
|
+
params: Optional screenshot options dict (width, height, format, etc.).
|
|
168
|
+
webhook_url: Optional webhook URL for notifications.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
dict with monitor details.
|
|
172
|
+
"""
|
|
173
|
+
payload = {"url": url, "interval_minutes": interval_minutes}
|
|
174
|
+
if name is not None:
|
|
175
|
+
payload["name"] = name
|
|
176
|
+
if params is not None:
|
|
177
|
+
payload["params"] = params
|
|
178
|
+
if webhook_url is not None:
|
|
179
|
+
payload["webhook_url"] = webhook_url
|
|
180
|
+
|
|
181
|
+
resp = self._request("POST", "/v1/monitors", json=payload)
|
|
182
|
+
return resp.json()
|
|
183
|
+
|
|
184
|
+
def list_monitors(self):
|
|
185
|
+
"""List all monitors for this API key.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
list of monitor dicts.
|
|
189
|
+
"""
|
|
190
|
+
resp = self._request("GET", "/v1/monitors")
|
|
191
|
+
return resp.json()
|
|
192
|
+
|
|
193
|
+
def get_monitor(self, monitor_id):
|
|
194
|
+
"""Get a single monitor by ID.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
dict with monitor details.
|
|
198
|
+
"""
|
|
199
|
+
resp = self._request("GET", f"/v1/monitors/{monitor_id}")
|
|
200
|
+
return resp.json()
|
|
201
|
+
|
|
202
|
+
def update_monitor(self, monitor_id, **kwargs):
|
|
203
|
+
"""Update a monitor.
|
|
204
|
+
|
|
205
|
+
Keyword Args:
|
|
206
|
+
name: New display name.
|
|
207
|
+
interval_minutes: New interval.
|
|
208
|
+
status: 'active' or 'paused'.
|
|
209
|
+
params: New screenshot options dict.
|
|
210
|
+
webhook_url: New webhook URL.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
dict with updated monitor.
|
|
214
|
+
"""
|
|
215
|
+
resp = self._request("PATCH", f"/v1/monitors/{monitor_id}", json=kwargs)
|
|
216
|
+
return resp.json()
|
|
217
|
+
|
|
218
|
+
def delete_monitor(self, monitor_id):
|
|
219
|
+
"""Delete a monitor and all its snapshots."""
|
|
220
|
+
self._request("DELETE", f"/v1/monitors/{monitor_id}")
|
|
221
|
+
|
|
222
|
+
def list_snapshots(self, monitor_id, limit=50, offset=0):
|
|
223
|
+
"""List snapshots for a monitor.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
monitor_id: Monitor ID.
|
|
227
|
+
limit: Max results (default 50).
|
|
228
|
+
offset: Pagination offset.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
dict with snapshots list, total, limit, offset.
|
|
232
|
+
"""
|
|
233
|
+
resp = self._request("GET", f"/v1/monitors/{monitor_id}/snapshots",
|
|
234
|
+
params={"limit": limit, "offset": offset})
|
|
235
|
+
return resp.json()
|
|
236
|
+
|
|
237
|
+
def get_snapshot(self, monitor_id, snapshot_id):
|
|
238
|
+
"""Download a specific snapshot image.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
bytes (raw image data).
|
|
242
|
+
"""
|
|
243
|
+
resp = self._request("GET", f"/v1/monitors/{monitor_id}/snapshots/{snapshot_id}")
|
|
244
|
+
return resp.content
|
|
245
|
+
|
|
246
|
+
@staticmethod
|
|
247
|
+
def signup(email, base_url="https://snapapi.tech"):
|
|
248
|
+
"""Create a new SnapAPI account. No API key required.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
email: Email address for the new account.
|
|
252
|
+
base_url: API base URL.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
dict with key, tier, and limit.
|
|
256
|
+
"""
|
|
257
|
+
url = f"{base_url.rstrip('/')}/v1/signup"
|
|
258
|
+
resp = requests.post(url, json={"email": email})
|
|
259
|
+
|
|
260
|
+
if not resp.ok:
|
|
261
|
+
try:
|
|
262
|
+
body = resp.json()
|
|
263
|
+
message = body.get("error", body.get("message", resp.reason))
|
|
264
|
+
except ValueError:
|
|
265
|
+
body = None
|
|
266
|
+
message = resp.reason or resp.text
|
|
267
|
+
raise SnapAPIError(resp.status_code, message, body)
|
|
268
|
+
|
|
269
|
+
return resp.json()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: snapapi-python
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: Official Python SDK for SnapAPI — screenshot, metadata extraction, and HTML rendering
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: screenshot,api,webpage-capture,snapapi
|
|
7
|
+
Requires-Python: >=3.7
|
|
8
|
+
Requires-Dist: requests>=2.20.0
|
|
9
|
+
Dynamic: keywords
|
|
10
|
+
Dynamic: license
|
|
11
|
+
Dynamic: requires-dist
|
|
12
|
+
Dynamic: requires-python
|
|
13
|
+
Dynamic: summary
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.20.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
snapapi
|