snapapi-python 0.1.1__py3-none-any.whl
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.py
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SnapAPI Python SDK
|
|
3
|
+
==================
|
|
4
|
+
A minimal, zero-dependency Python client for the SnapAPI web intelligence API.
|
|
5
|
+
Uses only the Python standard library (urllib, json, os).
|
|
6
|
+
|
|
7
|
+
Install:
|
|
8
|
+
pip install snapapi-python
|
|
9
|
+
# or: python setup.py install
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
from snapapi import SnapAPI
|
|
13
|
+
|
|
14
|
+
client = SnapAPI() # reads SNAPAPI_KEY from env
|
|
15
|
+
# or
|
|
16
|
+
client = SnapAPI(api_key="snap_xxxxxxxx") # explicit key
|
|
17
|
+
|
|
18
|
+
png_bytes = client.screenshot("https://github.com")
|
|
19
|
+
metadata = client.metadata("https://github.com")
|
|
20
|
+
analysis = client.analyze("https://github.com", screenshot=True)
|
|
21
|
+
pdf_bytes = client.pdf("https://github.com", format="A4")
|
|
22
|
+
img_bytes = client.render("<h1>Hello</h1>", width=1200, height=630)
|
|
23
|
+
results = client.batch(["https://a.com", "https://b.com"])
|
|
24
|
+
|
|
25
|
+
Docs: https://snapapi.tech/docs
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import json
|
|
29
|
+
import os
|
|
30
|
+
import urllib.error
|
|
31
|
+
import urllib.parse
|
|
32
|
+
import urllib.request
|
|
33
|
+
from typing import Any, Dict, List, Optional
|
|
34
|
+
|
|
35
|
+
__version__ = "0.1.1"
|
|
36
|
+
__all__ = ["SnapAPI", "SnapAPIError"]
|
|
37
|
+
|
|
38
|
+
_BASE_URL = "https://snapapi.tech"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SnapAPIError(Exception):
|
|
42
|
+
"""Raised when the SnapAPI returns an error response."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, status: int, message: str) -> None:
|
|
45
|
+
self.status = status
|
|
46
|
+
self.message = message
|
|
47
|
+
super().__init__(f"SnapAPI error {status}: {message}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SnapAPI:
|
|
51
|
+
"""
|
|
52
|
+
Client for the SnapAPI web intelligence API.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
api_key : str, optional
|
|
57
|
+
Your SnapAPI API key. If omitted, the value of the
|
|
58
|
+
``SNAPAPI_KEY`` environment variable is used.
|
|
59
|
+
base_url : str, optional
|
|
60
|
+
Override the default API base URL (useful for self-hosted installs).
|
|
61
|
+
timeout : int, optional
|
|
62
|
+
Request timeout in seconds. Defaults to 45.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
api_key: Optional[str] = None,
|
|
68
|
+
base_url: str = _BASE_URL,
|
|
69
|
+
timeout: int = 45,
|
|
70
|
+
) -> None:
|
|
71
|
+
self._api_key = api_key or os.environ.get("SNAPAPI_KEY", "")
|
|
72
|
+
if not self._api_key:
|
|
73
|
+
raise SnapAPIError(
|
|
74
|
+
0,
|
|
75
|
+
"No API key provided. Set SNAPAPI_KEY env var or pass api_key= to SnapAPI().\n"
|
|
76
|
+
"Get a free key at https://snapapi.tech",
|
|
77
|
+
)
|
|
78
|
+
self._base_url = base_url.rstrip("/")
|
|
79
|
+
self._timeout = timeout
|
|
80
|
+
|
|
81
|
+
# ── Internal helpers ──────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
def _get(self, path: str, params: Dict[str, Any]) -> bytes:
|
|
84
|
+
"""Make a GET request; return raw response bytes."""
|
|
85
|
+
qs = urllib.parse.urlencode(
|
|
86
|
+
{k: str(v) for k, v in params.items() if v is not None}
|
|
87
|
+
)
|
|
88
|
+
url = f"{self._base_url}{path}?{qs}"
|
|
89
|
+
req = urllib.request.Request(url, headers={"x-api-key": self._api_key})
|
|
90
|
+
try:
|
|
91
|
+
with urllib.request.urlopen(req, timeout=self._timeout) as resp:
|
|
92
|
+
return resp.read()
|
|
93
|
+
except urllib.error.HTTPError as exc:
|
|
94
|
+
body = exc.read().decode("utf-8", errors="replace")
|
|
95
|
+
try:
|
|
96
|
+
msg = json.loads(body).get("error", body)
|
|
97
|
+
except Exception:
|
|
98
|
+
msg = body
|
|
99
|
+
raise SnapAPIError(exc.code, msg) from exc
|
|
100
|
+
|
|
101
|
+
def _get_json(self, path: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
102
|
+
"""Make a GET request; parse and return JSON dict."""
|
|
103
|
+
raw = self._get(path, params)
|
|
104
|
+
try:
|
|
105
|
+
return json.loads(raw)
|
|
106
|
+
except json.JSONDecodeError as exc:
|
|
107
|
+
raise SnapAPIError(0, f"Invalid JSON response: {raw[:200]}") from exc
|
|
108
|
+
|
|
109
|
+
def _post_json(self, path: str, payload: Dict[str, Any]) -> bytes:
|
|
110
|
+
"""Make a POST request with a JSON body; return raw response bytes."""
|
|
111
|
+
body = json.dumps(payload).encode("utf-8")
|
|
112
|
+
url = f"{self._base_url}{path}"
|
|
113
|
+
req = urllib.request.Request(
|
|
114
|
+
url,
|
|
115
|
+
data=body,
|
|
116
|
+
headers={
|
|
117
|
+
"x-api-key": self._api_key,
|
|
118
|
+
"Content-Type": "application/json",
|
|
119
|
+
"Content-Length": str(len(body)),
|
|
120
|
+
},
|
|
121
|
+
method="POST",
|
|
122
|
+
)
|
|
123
|
+
try:
|
|
124
|
+
with urllib.request.urlopen(req, timeout=self._timeout) as resp:
|
|
125
|
+
return resp.read()
|
|
126
|
+
except urllib.error.HTTPError as exc:
|
|
127
|
+
body_str = exc.read().decode("utf-8", errors="replace")
|
|
128
|
+
try:
|
|
129
|
+
msg = json.loads(body_str).get("error", body_str)
|
|
130
|
+
except Exception:
|
|
131
|
+
msg = body_str
|
|
132
|
+
raise SnapAPIError(exc.code, msg) from exc
|
|
133
|
+
|
|
134
|
+
# ── Public API ────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
def screenshot(
|
|
137
|
+
self,
|
|
138
|
+
url: str,
|
|
139
|
+
*,
|
|
140
|
+
format: str = "png",
|
|
141
|
+
width: int = 1280,
|
|
142
|
+
height: int = 800,
|
|
143
|
+
full_page: bool = False,
|
|
144
|
+
dark_mode: bool = False,
|
|
145
|
+
device: Optional[str] = None,
|
|
146
|
+
selector: Optional[str] = None,
|
|
147
|
+
delay: Optional[int] = None,
|
|
148
|
+
) -> bytes:
|
|
149
|
+
"""
|
|
150
|
+
Capture a screenshot of *url*.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
url : str
|
|
155
|
+
The page to capture.
|
|
156
|
+
format : str
|
|
157
|
+
Output format: ``"png"``, ``"jpeg"``, or ``"webp"``.
|
|
158
|
+
width : int
|
|
159
|
+
Viewport width in pixels.
|
|
160
|
+
height : int
|
|
161
|
+
Viewport height in pixels.
|
|
162
|
+
full_page : bool
|
|
163
|
+
Capture the full scrollable page.
|
|
164
|
+
dark_mode : bool
|
|
165
|
+
Render with ``prefers-color-scheme: dark``.
|
|
166
|
+
device : str, optional
|
|
167
|
+
Device preset, e.g. ``"iphone14"``, ``"pixel7"``, ``"ipad"``.
|
|
168
|
+
selector : str, optional
|
|
169
|
+
CSS selector — capture only that element.
|
|
170
|
+
delay : int, optional
|
|
171
|
+
Extra milliseconds to wait before capturing.
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
bytes
|
|
176
|
+
Raw image bytes (PNG/JPEG/WebP).
|
|
177
|
+
"""
|
|
178
|
+
params: Dict[str, Any] = dict(
|
|
179
|
+
url=url,
|
|
180
|
+
format=format,
|
|
181
|
+
width=width,
|
|
182
|
+
height=height,
|
|
183
|
+
full_page=str(full_page).lower(),
|
|
184
|
+
dark_mode=str(dark_mode).lower(),
|
|
185
|
+
)
|
|
186
|
+
if device is not None:
|
|
187
|
+
params["device"] = device
|
|
188
|
+
if selector is not None:
|
|
189
|
+
params["selector"] = selector
|
|
190
|
+
if delay is not None:
|
|
191
|
+
params["delay"] = delay
|
|
192
|
+
|
|
193
|
+
return self._get("/v1/screenshot", params)
|
|
194
|
+
|
|
195
|
+
def metadata(self, url: str) -> Dict[str, Any]:
|
|
196
|
+
"""
|
|
197
|
+
Extract metadata from *url*.
|
|
198
|
+
|
|
199
|
+
Returns a dict with keys:
|
|
200
|
+
``url``, ``title``, ``description``, ``og_title``, ``og_description``,
|
|
201
|
+
``og_image``, ``og_type``, ``favicon``, ``canonical``, ``language``.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
url : str
|
|
206
|
+
The page to extract metadata from.
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
dict
|
|
211
|
+
"""
|
|
212
|
+
return self._get_json("/v1/metadata", {"url": url})
|
|
213
|
+
|
|
214
|
+
def analyze(
|
|
215
|
+
self,
|
|
216
|
+
url: str,
|
|
217
|
+
*,
|
|
218
|
+
screenshot: bool = False,
|
|
219
|
+
) -> Dict[str, Any]:
|
|
220
|
+
"""
|
|
221
|
+
Full page analysis of *url*.
|
|
222
|
+
|
|
223
|
+
Returns page_type, primary_cta, nav_items, technologies, word_count,
|
|
224
|
+
load_time_ms, OG metadata, and (optionally) a base64 screenshot.
|
|
225
|
+
|
|
226
|
+
Parameters
|
|
227
|
+
----------
|
|
228
|
+
url : str
|
|
229
|
+
The page to analyze.
|
|
230
|
+
screenshot : bool
|
|
231
|
+
Include a base64-encoded screenshot in the response.
|
|
232
|
+
|
|
233
|
+
Returns
|
|
234
|
+
-------
|
|
235
|
+
dict
|
|
236
|
+
"""
|
|
237
|
+
params: Dict[str, Any] = {"url": url}
|
|
238
|
+
if screenshot:
|
|
239
|
+
params["screenshot"] = "true"
|
|
240
|
+
return self._get_json("/v1/analyze", params)
|
|
241
|
+
|
|
242
|
+
def pdf(
|
|
243
|
+
self,
|
|
244
|
+
url: str,
|
|
245
|
+
*,
|
|
246
|
+
format: str = "A4",
|
|
247
|
+
landscape: bool = False,
|
|
248
|
+
margin_top: int = 20,
|
|
249
|
+
margin_bottom: int = 20,
|
|
250
|
+
margin_left: int = 20,
|
|
251
|
+
margin_right: int = 20,
|
|
252
|
+
print_background: bool = True,
|
|
253
|
+
scale: float = 1.0,
|
|
254
|
+
delay: Optional[int] = None,
|
|
255
|
+
) -> bytes:
|
|
256
|
+
"""
|
|
257
|
+
Convert *url* to a PDF.
|
|
258
|
+
|
|
259
|
+
Parameters
|
|
260
|
+
----------
|
|
261
|
+
url : str
|
|
262
|
+
The page to convert.
|
|
263
|
+
format : str
|
|
264
|
+
Paper format: ``"A4"``, ``"Letter"``, ``"A3"``, ``"A5"``, ``"Legal"``.
|
|
265
|
+
landscape : bool
|
|
266
|
+
Landscape orientation.
|
|
267
|
+
margin_top / margin_bottom / margin_left / margin_right : int
|
|
268
|
+
Margins in pixels.
|
|
269
|
+
print_background : bool
|
|
270
|
+
Print CSS background colors and images.
|
|
271
|
+
scale : float
|
|
272
|
+
Page scale (0.1–2.0).
|
|
273
|
+
delay : int, optional
|
|
274
|
+
Extra milliseconds to wait before generating.
|
|
275
|
+
|
|
276
|
+
Returns
|
|
277
|
+
-------
|
|
278
|
+
bytes
|
|
279
|
+
Raw PDF bytes.
|
|
280
|
+
"""
|
|
281
|
+
params: Dict[str, Any] = dict(
|
|
282
|
+
url=url,
|
|
283
|
+
format=format,
|
|
284
|
+
landscape=str(landscape).lower(),
|
|
285
|
+
margin_top=margin_top,
|
|
286
|
+
margin_bottom=margin_bottom,
|
|
287
|
+
margin_left=margin_left,
|
|
288
|
+
margin_right=margin_right,
|
|
289
|
+
print_background=str(print_background).lower(),
|
|
290
|
+
scale=scale,
|
|
291
|
+
)
|
|
292
|
+
if delay is not None:
|
|
293
|
+
params["delay"] = delay
|
|
294
|
+
return self._get("/v1/pdf", params)
|
|
295
|
+
|
|
296
|
+
def render(
|
|
297
|
+
self,
|
|
298
|
+
html: str,
|
|
299
|
+
*,
|
|
300
|
+
width: int = 1200,
|
|
301
|
+
height: int = 630,
|
|
302
|
+
format: str = "png",
|
|
303
|
+
) -> bytes:
|
|
304
|
+
"""
|
|
305
|
+
Render raw *html* to a pixel-perfect image.
|
|
306
|
+
|
|
307
|
+
Ideal for OG cards, email previews, certificate images.
|
|
308
|
+
|
|
309
|
+
Parameters
|
|
310
|
+
----------
|
|
311
|
+
html : str
|
|
312
|
+
Full HTML string to render.
|
|
313
|
+
width : int
|
|
314
|
+
Viewport width in pixels.
|
|
315
|
+
height : int
|
|
316
|
+
Viewport height in pixels.
|
|
317
|
+
format : str
|
|
318
|
+
Output format: ``"png"``, ``"jpeg"``, or ``"webp"``.
|
|
319
|
+
|
|
320
|
+
Returns
|
|
321
|
+
-------
|
|
322
|
+
bytes
|
|
323
|
+
Raw image bytes.
|
|
324
|
+
"""
|
|
325
|
+
payload = {"html": html, "width": width, "height": height, "format": format}
|
|
326
|
+
return self._post_json("/v1/render", payload)
|
|
327
|
+
|
|
328
|
+
def batch(
|
|
329
|
+
self,
|
|
330
|
+
urls: List[str],
|
|
331
|
+
*,
|
|
332
|
+
endpoint: str = "screenshot",
|
|
333
|
+
params: Optional[Dict[str, Any]] = None,
|
|
334
|
+
) -> List[Dict[str, Any]]:
|
|
335
|
+
"""
|
|
336
|
+
Process multiple URLs in parallel.
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
----------
|
|
340
|
+
urls : list[str]
|
|
341
|
+
List of URLs to process.
|
|
342
|
+
endpoint : str
|
|
343
|
+
Which endpoint to call per URL: ``"screenshot"``, ``"metadata"``,
|
|
344
|
+
or ``"analyze"``.
|
|
345
|
+
params : dict, optional
|
|
346
|
+
Extra parameters to pass to each per-URL call.
|
|
347
|
+
|
|
348
|
+
Returns
|
|
349
|
+
-------
|
|
350
|
+
list[dict]
|
|
351
|
+
One result dict per URL. Each has ``status`` (``"ok"`` or
|
|
352
|
+
``"error"``), ``url``, and endpoint-specific fields.
|
|
353
|
+
|
|
354
|
+
Raises
|
|
355
|
+
------
|
|
356
|
+
SnapAPIError
|
|
357
|
+
If the batch request itself fails (not per-URL errors).
|
|
358
|
+
"""
|
|
359
|
+
payload: Dict[str, Any] = {
|
|
360
|
+
"urls": urls,
|
|
361
|
+
"endpoint": endpoint,
|
|
362
|
+
}
|
|
363
|
+
if params:
|
|
364
|
+
payload["params"] = params
|
|
365
|
+
|
|
366
|
+
raw = self._post_json("/v1/batch", payload)
|
|
367
|
+
try:
|
|
368
|
+
data = json.loads(raw)
|
|
369
|
+
except json.JSONDecodeError as exc:
|
|
370
|
+
raise SnapAPIError(0, f"Invalid JSON in batch response: {raw[:200]}") from exc
|
|
371
|
+
|
|
372
|
+
return data.get("results", [])
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
# ── CLI / demo ────────────────────────────────────────────────────────────────
|
|
376
|
+
|
|
377
|
+
if __name__ == "__main__":
|
|
378
|
+
import sys
|
|
379
|
+
|
|
380
|
+
print("SnapAPI Python SDK — demo\n")
|
|
381
|
+
|
|
382
|
+
client = SnapAPI() # requires SNAPAPI_KEY in environment
|
|
383
|
+
|
|
384
|
+
target = sys.argv[1] if len(sys.argv) > 1 else "https://github.com"
|
|
385
|
+
|
|
386
|
+
# 1. Screenshot
|
|
387
|
+
print(f"[1] Screenshot of {target}...")
|
|
388
|
+
png = client.screenshot(target, full_page=False)
|
|
389
|
+
with open("demo_screenshot.png", "wb") as f:
|
|
390
|
+
f.write(png)
|
|
391
|
+
print(f" Saved demo_screenshot.png ({len(png) // 1024} KB)")
|
|
392
|
+
|
|
393
|
+
# 2. Metadata
|
|
394
|
+
print(f"\n[2] Metadata for {target}...")
|
|
395
|
+
meta = client.metadata(target)
|
|
396
|
+
print(f" Title: {meta.get('title', '')[:60]}")
|
|
397
|
+
print(f" OG image: {meta.get('og_image', '(none)')[:80]}")
|
|
398
|
+
|
|
399
|
+
# 3. Analyze
|
|
400
|
+
print(f"\n[3] Page analysis for {target}...")
|
|
401
|
+
analysis = client.analyze(target)
|
|
402
|
+
print(f" Page type: {analysis.get('page_type', '')}")
|
|
403
|
+
print(f" Primary CTA: {analysis.get('primary_cta', '')}")
|
|
404
|
+
print(f" Tech stack: {', '.join(analysis.get('technologies', []))}")
|
|
405
|
+
|
|
406
|
+
# 4. PDF
|
|
407
|
+
print(f"\n[4] PDF of {target}...")
|
|
408
|
+
pdf = client.pdf(target, format="A4")
|
|
409
|
+
with open("demo.pdf", "wb") as f:
|
|
410
|
+
f.write(pdf)
|
|
411
|
+
print(f" Saved demo.pdf ({len(pdf) // 1024} KB)")
|
|
412
|
+
|
|
413
|
+
# 5. Render HTML → image
|
|
414
|
+
print("\n[5] Render HTML to PNG...")
|
|
415
|
+
html = """
|
|
416
|
+
<div style="background:#1a1a2e;color:#f0f0f4;font-family:sans-serif;
|
|
417
|
+
padding:80px;width:1200px;height:630px;
|
|
418
|
+
display:flex;align-items:center;font-size:48px;font-weight:800;">
|
|
419
|
+
Hello from SnapAPI Python SDK
|
|
420
|
+
</div>"""
|
|
421
|
+
img = client.render(html, width=1200, height=630)
|
|
422
|
+
with open("demo_render.png", "wb") as f:
|
|
423
|
+
f.write(img)
|
|
424
|
+
print(f" Saved demo_render.png ({len(img) // 1024} KB)")
|
|
425
|
+
|
|
426
|
+
# 6. Batch
|
|
427
|
+
print(f"\n[6] Batch metadata for 2 URLs...")
|
|
428
|
+
results = client.batch(
|
|
429
|
+
["https://github.com", "https://example.com"],
|
|
430
|
+
endpoint="metadata",
|
|
431
|
+
)
|
|
432
|
+
for r in results:
|
|
433
|
+
status = r.get("status", "?")
|
|
434
|
+
title = r.get("title", "")[:50]
|
|
435
|
+
print(f" {status:5s} {r.get('url', '')} → {title}")
|
|
436
|
+
|
|
437
|
+
print("\nDone. Check the generated files.")
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: snapapi-python
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Python SDK for the SnapAPI web intelligence API — screenshots, metadata, PDF, page analysis
|
|
5
|
+
Home-page: https://snapapi.tech
|
|
6
|
+
Author: SnapAPI
|
|
7
|
+
Author-email: hello@snapapi.tech
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Documentation, https://snapapi.tech/docs
|
|
10
|
+
Project-URL: Source, https://github.com/Boehner/snapapi-python
|
|
11
|
+
Project-URL: Bug Tracker, https://github.com/Boehner/snapapi-python/issues
|
|
12
|
+
Keywords: screenshot api web-scraping metadata pdf puppeteer snapapi
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: description-content-type
|
|
31
|
+
Dynamic: home-page
|
|
32
|
+
Dynamic: keywords
|
|
33
|
+
Dynamic: license
|
|
34
|
+
Dynamic: project-url
|
|
35
|
+
Dynamic: requires-python
|
|
36
|
+
Dynamic: summary
|
|
37
|
+
|
|
38
|
+
# snapapi-python
|
|
39
|
+
|
|
40
|
+
Python SDK for the [SnapAPI](https://snapapi.tech) web intelligence API. Zero dependencies — uses only the Python standard library.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install snapapi-python
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from snapapi import SnapAPI
|
|
50
|
+
|
|
51
|
+
client = SnapAPI() # reads SNAPAPI_KEY from environment
|
|
52
|
+
|
|
53
|
+
# Screenshot
|
|
54
|
+
png = client.screenshot("https://github.com", full_page=True)
|
|
55
|
+
open("screenshot.png", "wb").write(png)
|
|
56
|
+
|
|
57
|
+
# Metadata
|
|
58
|
+
meta = client.metadata("https://github.com")
|
|
59
|
+
print(meta["og_title"]) # "GitHub: Let's build from here"
|
|
60
|
+
print(meta["og_image"]) # "https://..."
|
|
61
|
+
|
|
62
|
+
# Full page analysis
|
|
63
|
+
data = client.analyze("https://stripe.com")
|
|
64
|
+
print(data["page_type"]) # "product landing page"
|
|
65
|
+
print(data["primary_cta"]) # "Start now"
|
|
66
|
+
print(data["technologies"]) # ["React", "Next.js", "Cloudflare"]
|
|
67
|
+
|
|
68
|
+
# URL → PDF
|
|
69
|
+
pdf = client.pdf("https://github.com", format="A4")
|
|
70
|
+
open("page.pdf", "wb").write(pdf)
|
|
71
|
+
|
|
72
|
+
# HTML → image (OG cards, email previews)
|
|
73
|
+
html = '<div style="background:#0d0d0f;color:#fff;padding:80px;font-size:48px">My OG Card</div>'
|
|
74
|
+
img = client.render(html, width=1200, height=630)
|
|
75
|
+
open("og-card.png", "wb").write(img)
|
|
76
|
+
|
|
77
|
+
# Batch (multiple URLs)
|
|
78
|
+
results = client.batch(["https://a.com", "https://b.com"], endpoint="metadata")
|
|
79
|
+
for r in results:
|
|
80
|
+
print(r["url"], r.get("title"))
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## API Key
|
|
84
|
+
|
|
85
|
+
Get a free key (100 calls/month, no credit card) at **https://snapapi.tech**.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
export SNAPAPI_KEY=snap_your_key_here
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or pass it directly: `client = SnapAPI(api_key="snap_your_key_here")`
|
|
92
|
+
|
|
93
|
+
## Methods
|
|
94
|
+
|
|
95
|
+
| Method | Returns | Description |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `screenshot(url, **kwargs)` | `bytes` | PNG/JPEG/WebP image |
|
|
98
|
+
| `metadata(url)` | `dict` | OG tags, title, favicon, canonical |
|
|
99
|
+
| `analyze(url, screenshot=False)` | `dict` | Page type, CTA, tech stack, + optional screenshot |
|
|
100
|
+
| `pdf(url, **kwargs)` | `bytes` | PDF binary |
|
|
101
|
+
| `render(html, **kwargs)` | `bytes` | HTML → image |
|
|
102
|
+
| `batch(urls, endpoint, params)` | `list[dict]` | Parallel multi-URL processing |
|
|
103
|
+
|
|
104
|
+
Full parameter reference: [snapapi.tech/docs](https://snapapi.tech/docs)
|
|
105
|
+
|
|
106
|
+
## Requirements
|
|
107
|
+
|
|
108
|
+
- Python 3.8+
|
|
109
|
+
- No third-party dependencies
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
snapapi.py,sha256=oPTUOnTORqnix-zpX2GCITgq_Zdqj3CxZb19ds_BhPw,13922
|
|
2
|
+
snapapi_python-0.1.1.dist-info/METADATA,sha256=P9P9n40gUgwG382iMr0qUop9fUQvkZNuZguSVGCOdDw,3620
|
|
3
|
+
snapapi_python-0.1.1.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
|
4
|
+
snapapi_python-0.1.1.dist-info/top_level.txt,sha256=ixqJtWjxOFIoE_Mirko8xyRTH-3pKC_AYNRLLUuESoA,8
|
|
5
|
+
snapapi_python-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
snapapi
|