webclaw 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.
Binary file
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+ *.egg
9
+ .venv/
10
+ venv/
11
+ .env
12
+ .pytest_cache/
13
+ .mypy_cache/
14
+ .DS_Store
webclaw-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 webclaw
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.
webclaw-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,435 @@
1
+ Metadata-Version: 2.4
2
+ Name: webclaw
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the Webclaw web extraction API
5
+ Project-URL: Homepage, https://webclaw.io
6
+ Project-URL: Documentation, https://webclaw.io/docs
7
+ Project-URL: Repository, https://github.com/0xMassi/webclaw-python
8
+ Project-URL: Issues, https://github.com/0xMassi/webclaw-python/issues
9
+ Author: webclaw
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.9
23
+ Requires-Dist: httpx>=0.24.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
26
+ Requires-Dist: pytest>=7.0; extra == 'dev'
27
+ Requires-Dist: respx>=0.21; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ <p align="center">
31
+ <a href="https://webclaw.io">
32
+ <img src=".github/banner.png" alt="webclaw" width="600" />
33
+ </a>
34
+ </p>
35
+
36
+ <p align="center">
37
+ <strong>Python SDK for the Webclaw web extraction API</strong>
38
+ </p>
39
+
40
+ <p align="center">
41
+ <a href="https://pypi.org/project/webclaw"><img src="https://img.shields.io/pypi/v/webclaw?style=flat-square&color=212529" alt="PyPI" /></a>
42
+ <a href="https://pypi.org/project/webclaw"><img src="https://img.shields.io/pypi/pyversions/webclaw?style=flat-square&color=212529" alt="Python" /></a>
43
+ <a href="https://github.com/0xMassi/webclaw-python/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-212529?style=flat-square" alt="License" /></a>
44
+ </p>
45
+
46
+ ---
47
+
48
+ > **Note**: The webclaw Cloud API is currently in closed beta. [Request early access](https://webclaw.io) or use the [open-source CLI/MCP](https://github.com/0xMassi/webclaw) for local extraction.
49
+
50
+ ---
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install webclaw
56
+ ```
57
+
58
+ Requires Python 3.9+. The only dependency is [httpx](https://www.python-httpx.org/).
59
+
60
+ ## Quick Start
61
+
62
+ ### Sync
63
+
64
+ ```python
65
+ from webclaw import Webclaw
66
+
67
+ client = Webclaw("wc-YOUR_API_KEY")
68
+
69
+ result = client.scrape("https://example.com", formats=["markdown"])
70
+ print(result.markdown)
71
+ ```
72
+
73
+ ### Async
74
+
75
+ ```python
76
+ from webclaw import AsyncWebclaw
77
+
78
+ async with AsyncWebclaw("wc-YOUR_API_KEY") as client:
79
+ result = await client.scrape("https://example.com", formats=["markdown"])
80
+ print(result.markdown)
81
+ ```
82
+
83
+ Both clients support identical method signatures. Every sync method has an async equivalent. The examples below use the sync client for brevity.
84
+
85
+ ## Endpoints
86
+
87
+ ### Scrape
88
+
89
+ Extract content from a single URL. Supports multiple output formats: `"markdown"`, `"text"`, `"llm"`, `"json"`.
90
+
91
+ ```python
92
+ result = client.scrape(
93
+ "https://example.com",
94
+ formats=["markdown", "text", "llm"],
95
+ include_selectors=["article", ".content"],
96
+ exclude_selectors=["nav", "footer"],
97
+ only_main_content=True,
98
+ no_cache=True,
99
+ )
100
+
101
+ result.url # str
102
+ result.markdown # str | None
103
+ result.text # str | None
104
+ result.llm # str | None
105
+ result.json_data # Any | None
106
+ result.metadata # dict
107
+ result.cache # CacheInfo | None (.status: "hit" | "miss" | "bypass")
108
+ result.warning # str | None
109
+ ```
110
+
111
+ ### Search
112
+
113
+ Web search with optional topic filtering.
114
+
115
+ ```python
116
+ results = client.search("web scraping tools 2026", num_results=10, topic="tech")
117
+
118
+ for r in results["results"]:
119
+ print(r["title"], r["url"])
120
+ ```
121
+
122
+ **Parameters:** `query` (str), `num_results` (int, optional), `topic` (str, optional).
123
+
124
+ ### Map
125
+
126
+ Discover URLs via sitemap.
127
+
128
+ ```python
129
+ result = client.map("https://example.com")
130
+
131
+ print(result.count)
132
+ for url in result.urls:
133
+ print(url)
134
+ ```
135
+
136
+ ### Batch
137
+
138
+ Scrape multiple URLs in parallel.
139
+
140
+ ```python
141
+ result = client.batch(
142
+ ["https://a.com", "https://b.com", "https://c.com"],
143
+ formats=["markdown"],
144
+ concurrency=5,
145
+ )
146
+
147
+ for item in result.results:
148
+ print(item.url, item.markdown, item.error or "ok")
149
+ ```
150
+
151
+ **Parameters:** `urls` (list[str]), `formats` (optional), `concurrency` (int, default 5).
152
+
153
+ ### Extract
154
+
155
+ LLM-powered structured data extraction. Use either a JSON schema or a natural language prompt.
156
+
157
+ ```python
158
+ # Schema-based extraction
159
+ result = client.extract(
160
+ "https://example.com/pricing",
161
+ schema={
162
+ "type": "object",
163
+ "properties": {
164
+ "plans": {
165
+ "type": "array",
166
+ "items": {
167
+ "type": "object",
168
+ "properties": {
169
+ "name": {"type": "string"},
170
+ "price": {"type": "string"},
171
+ },
172
+ },
173
+ }
174
+ },
175
+ },
176
+ )
177
+ print(result.data) # dict matching your schema
178
+
179
+ # Prompt-based extraction
180
+ result = client.extract(
181
+ "https://example.com/pricing",
182
+ prompt="Extract all pricing tiers with names and monthly prices",
183
+ )
184
+ print(result.data)
185
+ ```
186
+
187
+ ### Summarize
188
+
189
+ Summarize page content with an optional sentence limit.
190
+
191
+ ```python
192
+ result = client.summarize("https://example.com", max_sentences=3)
193
+ print(result.summary)
194
+ ```
195
+
196
+ ### Diff
197
+
198
+ Detect content changes at a URL since the last check.
199
+
200
+ ```python
201
+ result = client.diff("https://example.com/status")
202
+
203
+ print(result["has_changed"]) # bool
204
+ print(result["diff"]) # str, unified diff of changes
205
+ ```
206
+
207
+ ### Brand
208
+
209
+ Extract brand identity (colors, fonts, logos) from a URL.
210
+
211
+ ```python
212
+ result = client.brand("https://example.com")
213
+ print(result.data) # dict with brand identity fields
214
+ ```
215
+
216
+ ### Agent Scrape
217
+
218
+ AI-guided scraping that navigates a page to achieve a specified goal.
219
+
220
+ ```python
221
+ result = client.agent_scrape(
222
+ "https://example.com/dashboard",
223
+ goal="Find the monthly active users count",
224
+ )
225
+
226
+ print(result["result"])
227
+ print(result["steps"])
228
+ ```
229
+
230
+ **Parameters:** `url` (str), `goal` (str), plus optional keyword arguments forwarded to the API.
231
+
232
+ ### Research
233
+
234
+ Deep research that searches, reads, and synthesizes information from multiple sources. This is an async job: the SDK starts it and polls until completion.
235
+
236
+ ```python
237
+ # Blocks until research completes (up to 600s, or 1200s with deep=True)
238
+ result = client.research(
239
+ "How do modern web crawlers handle JavaScript rendering?",
240
+ max_sources=15,
241
+ deep=True,
242
+ topic="tech",
243
+ )
244
+
245
+ print(result.report)
246
+ print(result.iterations)
247
+ print(result.elapsed_ms)
248
+
249
+ for source in result.sources:
250
+ print(source["url"], source["title"])
251
+ ```
252
+
253
+ To check status without blocking:
254
+
255
+ ```python
256
+ status = client.get_research_status("job-id-here")
257
+ print(status.status) # "running" | "completed" | "failed"
258
+ ```
259
+
260
+ **Parameters:** `query` (str), `deep` (bool, default False), `max_sources` (int, optional), `max_iterations` (int, optional), `topic` (str, optional).
261
+
262
+ ### Crawl
263
+
264
+ Start an async crawl that follows links from a seed URL.
265
+
266
+ ```python
267
+ job = client.crawl(
268
+ "https://example.com",
269
+ max_depth=3,
270
+ max_pages=100,
271
+ use_sitemap=True,
272
+ )
273
+
274
+ # Poll until complete (default timeout 300s)
275
+ status = job.wait(interval=2.0, timeout=300.0)
276
+
277
+ print(status.total, status.completed, status.errors)
278
+ for page in status.pages:
279
+ print(page.url, len(page.markdown or ""))
280
+ ```
281
+
282
+ Check status without waiting:
283
+
284
+ ```python
285
+ status = job.get_status()
286
+ print(status.status) # "running" | "completed" | "failed"
287
+ ```
288
+
289
+ Async variant:
290
+
291
+ ```python
292
+ job = await client.crawl("https://example.com", max_depth=2)
293
+ status = await job.wait()
294
+ ```
295
+
296
+ ### Watch
297
+
298
+ Monitor URLs for content changes with automatic periodic checks.
299
+
300
+ **Create a watch:**
301
+
302
+ ```python
303
+ watch = client.watch_create(
304
+ "https://example.com/pricing",
305
+ name="Pricing page monitor",
306
+ interval_minutes=60,
307
+ webhook_url="https://hooks.example.com/webclaw",
308
+ )
309
+ print(watch.id, watch.status)
310
+ ```
311
+
312
+ **List all watches:**
313
+
314
+ ```python
315
+ result = client.watch_list(limit=50, offset=0)
316
+ for w in result.watches:
317
+ print(w.id, w.url, w.name, w.last_checked)
318
+ print(result.total)
319
+ ```
320
+
321
+ **Get a single watch:**
322
+
323
+ ```python
324
+ watch = client.watch_get("watch-id-here")
325
+ print(watch.url, watch.interval_minutes)
326
+ ```
327
+
328
+ **Delete a watch:**
329
+
330
+ ```python
331
+ client.watch_delete("watch-id-here")
332
+ ```
333
+
334
+ **Trigger an immediate check:**
335
+
336
+ ```python
337
+ check = client.watch_check("watch-id-here")
338
+ print(check.has_changed) # bool
339
+ print(check.diff) # str | None
340
+ print(check.checked_at) # ISO timestamp
341
+ ```
342
+
343
+ ## Error Handling
344
+
345
+ All errors inherit from `WebclawError`, which carries the HTTP status code when available.
346
+
347
+ ```python
348
+ from webclaw import (
349
+ WebclawError,
350
+ AuthenticationError,
351
+ NotFoundError,
352
+ RateLimitError,
353
+ TimeoutError,
354
+ )
355
+
356
+ try:
357
+ result = client.scrape("https://example.com")
358
+ except AuthenticationError:
359
+ print("Invalid or missing API key")
360
+ except RateLimitError:
361
+ print("Too many requests, slow down")
362
+ except NotFoundError:
363
+ print("Resource not found")
364
+ except TimeoutError as e:
365
+ print(f"Operation timed out: {e}")
366
+ except WebclawError as e:
367
+ print(f"API error (status {e.status_code}): {e}")
368
+ ```
369
+
370
+ | Exception | HTTP Status | When |
371
+ |-----------|-------------|------|
372
+ | `AuthenticationError` | 401 / 403 | Invalid or missing API key |
373
+ | `NotFoundError` | 404 | Resource does not exist |
374
+ | `RateLimitError` | 429 | Too many requests |
375
+ | `TimeoutError` | -- | Crawl/research polling exceeded timeout |
376
+ | `WebclawError` | Any | Base class for all other API errors |
377
+
378
+ ## Configuration
379
+
380
+ ```python
381
+ import os
382
+ from webclaw import Webclaw
383
+
384
+ client = Webclaw(
385
+ os.environ["WEBCLAW_API_KEY"],
386
+ base_url="https://api.webclaw.io", # default
387
+ timeout=60.0, # seconds, default 30
388
+ )
389
+ ```
390
+
391
+ Both `Webclaw` and `AsyncWebclaw` support context managers for automatic cleanup:
392
+
393
+ ```python
394
+ # Sync
395
+ with Webclaw("wc-YOUR_API_KEY") as client:
396
+ result = client.scrape("https://example.com")
397
+
398
+ # Async
399
+ async with AsyncWebclaw("wc-YOUR_API_KEY") as client:
400
+ result = await client.scrape("https://example.com")
401
+ ```
402
+
403
+ ## Async Usage
404
+
405
+ Every endpoint is available on `AsyncWebclaw` with identical parameters. Use `await` on all method calls and `async with` for the context manager.
406
+
407
+ ```python
408
+ import asyncio
409
+ from webclaw import AsyncWebclaw
410
+
411
+ async def main():
412
+ async with AsyncWebclaw("wc-YOUR_API_KEY") as client:
413
+ # Run multiple scrapes concurrently
414
+ results = await asyncio.gather(
415
+ client.scrape("https://a.com", formats=["markdown"]),
416
+ client.scrape("https://b.com", formats=["markdown"]),
417
+ client.scrape("https://c.com", formats=["markdown"]),
418
+ )
419
+ for r in results:
420
+ print(r.url, len(r.markdown or ""))
421
+
422
+ asyncio.run(main())
423
+ ```
424
+
425
+ ## Type Support
426
+
427
+ This package ships with a `py.typed` marker (PEP 561). Type checkers like mypy and pyright will pick up all type annotations automatically. All response types are dataclasses importable from the top-level package:
428
+
429
+ ```python
430
+ from webclaw import ScrapeResponse, CrawlStatus, MapResponse, ExtractResponse
431
+ ```
432
+
433
+ ## License
434
+
435
+ MIT