webcrawlerapi 2.0.11__tar.gz → 2.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.
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.4
2
+ Name: webcrawlerapi
3
+ Version: 2.1.0
4
+ Summary: Python SDK for WebCrawler API
5
+ Home-page: https://github.com/webcrawlerapi/webcrawlerapi-python-sdk
6
+ Author: Andrew
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.6
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: requests>=2.25.0
13
+ Dynamic: author
14
+ Dynamic: classifier
15
+ Dynamic: description
16
+ Dynamic: description-content-type
17
+ Dynamic: home-page
18
+ Dynamic: requires-dist
19
+ Dynamic: requires-python
20
+ Dynamic: summary
21
+
22
+ # WebCrawler API Python SDK
23
+
24
+ A Python SDK for interacting with the [WebCrawlerAPI](https://webcrawlerapi.com).
25
+
26
+ Get your API key at [dash.webcrawlerapi.com/access](https://dash.webcrawlerapi.com/access).
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install webcrawlerapi
32
+ ```
33
+
34
+ ## Crawling
35
+
36
+ ```python
37
+ from webcrawlerapi import WebCrawlerAPI
38
+
39
+ client = WebCrawlerAPI(api_key="your_api_key")
40
+
41
+ job = client.crawl(
42
+ url="https://example.com",
43
+ output_formats=["markdown"],
44
+ items_limit=10,
45
+ )
46
+
47
+ for item in job.job_items:
48
+ if item.status == "done":
49
+ print(item.original_url)
50
+ print(item.content)
51
+ ```
52
+
53
+ ## Scraping
54
+
55
+ ```python
56
+ from webcrawlerapi import WebCrawlerAPI
57
+
58
+ client = WebCrawlerAPI(api_key="your_api_key")
59
+
60
+ result = client.scrape(
61
+ url="https://example.com",
62
+ output_formats=["markdown"],
63
+ )
64
+
65
+ if result.success:
66
+ print(result.markdown)
67
+ else:
68
+ print(f"Error: {result.error_code} — {result.error_message}")
69
+ ```
70
+
71
+ ## Documentation
72
+
73
+ - [Crawling — parameters, async, job/item responses, cancellation](docs/crawling.md)
74
+ - [Scraping — parameters, async, response fields](docs/scraping.md)
75
+ - [Advanced — output formats, structured outputs, webhooks, S3 actions](docs/advanced.md)
76
+
77
+ ## Requirements
78
+
79
+ - Python 3.6+
80
+ - requests >= 2.25.0
81
+
82
+ ## License
83
+
84
+ MIT License
@@ -0,0 +1,63 @@
1
+ # WebCrawler API Python SDK
2
+
3
+ A Python SDK for interacting with the [WebCrawlerAPI](https://webcrawlerapi.com).
4
+
5
+ Get your API key at [dash.webcrawlerapi.com/access](https://dash.webcrawlerapi.com/access).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install webcrawlerapi
11
+ ```
12
+
13
+ ## Crawling
14
+
15
+ ```python
16
+ from webcrawlerapi import WebCrawlerAPI
17
+
18
+ client = WebCrawlerAPI(api_key="your_api_key")
19
+
20
+ job = client.crawl(
21
+ url="https://example.com",
22
+ output_formats=["markdown"],
23
+ items_limit=10,
24
+ )
25
+
26
+ for item in job.job_items:
27
+ if item.status == "done":
28
+ print(item.original_url)
29
+ print(item.content)
30
+ ```
31
+
32
+ ## Scraping
33
+
34
+ ```python
35
+ from webcrawlerapi import WebCrawlerAPI
36
+
37
+ client = WebCrawlerAPI(api_key="your_api_key")
38
+
39
+ result = client.scrape(
40
+ url="https://example.com",
41
+ output_formats=["markdown"],
42
+ )
43
+
44
+ if result.success:
45
+ print(result.markdown)
46
+ else:
47
+ print(f"Error: {result.error_code} — {result.error_message}")
48
+ ```
49
+
50
+ ## Documentation
51
+
52
+ - [Crawling — parameters, async, job/item responses, cancellation](docs/crawling.md)
53
+ - [Scraping — parameters, async, response fields](docs/scraping.md)
54
+ - [Advanced — output formats, structured outputs, webhooks, S3 actions](docs/advanced.md)
55
+
56
+ ## Requirements
57
+
58
+ - Python 3.6+
59
+ - requests >= 2.25.0
60
+
61
+ ## License
62
+
63
+ MIT License
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
2
2
 
3
3
  setup(
4
4
  name="webcrawlerapi",
5
- version="2.0.11",
5
+ version="2.1.0",
6
6
  packages=find_packages(),
7
7
  install_requires=[
8
8
  "requests>=2.25.0",
@@ -14,6 +14,7 @@ from webcrawlerapi.models import (
14
14
  ScrapeResponse,
15
15
  ScrapeResponseError,
16
16
  UploadS3Action,
17
+ WebCrawlerApiError,
17
18
  )
18
19
 
19
20
 
@@ -159,7 +160,7 @@ class TestWebCrawlerAPI:
159
160
  status=400,
160
161
  )
161
162
 
162
- with pytest.raises(requests.exceptions.HTTPError):
163
+ with pytest.raises(WebCrawlerApiError):
163
164
  client.crawl_async(url="invalid-url")
164
165
 
165
166
  @responses.activate
@@ -278,7 +279,6 @@ class TestWebCrawlerAPI:
278
279
  output_format="cleaned",
279
280
  clean_selectors=".ads, .footer",
280
281
  prompt="Extract main content",
281
- respect_robots_txt=True,
282
282
  )
283
283
 
284
284
  assert isinstance(result, ScrapeId)
@@ -293,7 +293,6 @@ class TestWebCrawlerAPI:
293
293
  assert payload["output_format"] == "cleaned"
294
294
  assert payload["clean_selectors"] == ".ads, .footer"
295
295
  assert payload["prompt"] == "Extract main content"
296
- assert payload["respect_robots_txt"] is True
297
296
 
298
297
  @responses.activate
299
298
  def test_scrape_async_error_response(self, client):
@@ -305,11 +304,11 @@ class TestWebCrawlerAPI:
305
304
  status=400,
306
305
  )
307
306
 
308
- with pytest.raises(requests.exceptions.HTTPError) as exc_info:
307
+ with pytest.raises(WebCrawlerApiError) as exc_info:
309
308
  client.scrape_async(url="invalid-url")
310
309
 
311
- assert "400" in str(exc_info.value)
312
- assert "Invalid URL format" in str(exc_info.value)
310
+ assert exc_info.value.status_code == 400
311
+ assert "Invalid URL format" in exc_info.value.error_message
313
312
 
314
313
  @responses.activate
315
314
  def test_get_scrape_success_done(self, client):
@@ -385,99 +384,63 @@ class TestWebCrawlerAPI:
385
384
 
386
385
  @responses.activate
387
386
  def test_scrape_with_polling_success(self, client):
388
- """Test scrape method that polls until completion."""
389
- # Mock scrape_async response
387
+ """Test scrape method returns done result from sync endpoint."""
390
388
  responses.add(
391
389
  responses.POST,
392
- "https://api.test.com/v2/scrape?async=true",
393
- json={"id": "scrape-123"},
390
+ "https://api.test.com/v2/scrape",
391
+ json={
392
+ "status": "done",
393
+ "success": True,
394
+ "markdown": "# Scraped Content",
395
+ },
394
396
  status=200,
395
397
  )
396
398
 
397
- # Mock get_scrape response with done status
398
- scrape_data = {
399
- "status": "done",
400
- "success": True,
401
- "markdown": "# Scraped Content",
402
- }
399
+ result = client.scrape(url="https://example.com")
403
400
 
404
- responses.add(
405
- responses.GET,
406
- "https://api.test.com/v2/scrape/scrape-123",
407
- json=scrape_data,
408
- status=200,
409
- )
410
-
411
- with patch("time.sleep") as mock_sleep:
412
- result = client.scrape(url="https://example.com")
413
-
414
- assert isinstance(result, ScrapeResponse)
415
- assert result.status == "done"
416
- assert result.markdown == "# Scraped Content"
417
- # Should not sleep since scrape is already done
418
- mock_sleep.assert_not_called()
401
+ assert isinstance(result, ScrapeResponse)
402
+ assert result.status == "done"
403
+ assert result.markdown == "# Scraped Content"
419
404
 
420
405
  @responses.activate
421
406
  def test_scrape_with_polling_error(self, client):
422
- """Test scrape method that polls and gets error."""
423
- # Mock scrape_async response
407
+ """Test scrape method that gets error from sync endpoint."""
424
408
  responses.add(
425
409
  responses.POST,
426
- "https://api.test.com/v2/scrape?async=true",
427
- json={"id": "scrape-123"},
410
+ "https://api.test.com/v2/scrape",
411
+ json={
412
+ "status": "error",
413
+ "success": False,
414
+ "error_code": "FETCH_ERROR",
415
+ "error_message": "Failed to fetch page",
416
+ },
428
417
  status=200,
429
418
  )
430
419
 
431
- # Mock get_scrape response with error
432
- error_data = {
433
- "status": "error",
434
- "success": False,
435
- "error_code": "FETCH_ERROR",
436
- "error_message": "Failed to fetch page",
437
- }
420
+ result = client.scrape(url="https://example.com")
438
421
 
439
- responses.add(
440
- responses.GET,
441
- "https://api.test.com/v2/scrape/scrape-123",
442
- json=error_data,
443
- status=200,
444
- )
445
-
446
- with patch("time.sleep") as mock_sleep:
447
- result = client.scrape(url="https://example.com")
448
-
449
- assert isinstance(result, ScrapeResponseError)
450
- assert result.error_code == "FETCH_ERROR"
451
- # Should not sleep since error is immediate
452
- mock_sleep.assert_not_called()
422
+ assert isinstance(result, ScrapeResponseError)
423
+ assert result.error_code == "FETCH_ERROR"
453
424
 
454
425
  @responses.activate
455
426
  def test_scrape_with_polling_max_polls(self, client):
456
- """Test scrape method that reaches max_polls."""
457
- # Mock scrape_async response
427
+ """Test scrape method returns full response data from sync endpoint."""
458
428
  responses.add(
459
429
  responses.POST,
460
- "https://api.test.com/v2/scrape?async=true",
461
- json={"id": "scrape-123"},
430
+ "https://api.test.com/v2/scrape",
431
+ json={
432
+ "status": "done",
433
+ "success": True,
434
+ "markdown": "# Content",
435
+ "page_status_code": 200,
436
+ "page_title": "Test Page",
437
+ },
462
438
  status=200,
463
439
  )
464
440
 
465
- # Mock get_scrape responses with in_progress status
466
- progress_data = {"status": "in_progress", "success": False}
467
-
468
- for _ in range(3):
469
- responses.add(
470
- responses.GET,
471
- "https://api.test.com/v2/scrape/scrape-123",
472
- json=progress_data,
473
- status=200,
474
- )
441
+ result = client.scrape(url="https://example.com")
475
442
 
476
- with patch("time.sleep") as mock_sleep:
477
- result = client.scrape(url="https://example.com", max_polls=3)
478
-
479
- assert isinstance(result, ScrapeResponse)
480
- assert result.status == "in_progress"
481
- # Should sleep 3 times (once after each poll)
482
- assert mock_sleep.call_count == 3
483
- mock_sleep.assert_called_with(5) # DEFAULT_POLL_DELAY_SECONDS
443
+ assert isinstance(result, ScrapeResponse)
444
+ assert result.status == "done"
445
+ assert result.page_status_code == 200
446
+ assert result.page_title == "Test Page"
@@ -28,17 +28,21 @@ from .models import (
28
28
  CrawlResponse,
29
29
  Job,
30
30
  JobItem,
31
+ JobMarkdownResponse,
31
32
  ScrapeId,
32
33
  ScrapeResponse,
33
34
  ScrapeResponseError,
34
35
  UploadS3Action,
36
+ WebCrawlerApiError,
35
37
  )
36
38
 
37
39
  __version__ = "1.0.0"
38
40
  __all__ = [
39
41
  "WebCrawlerAPI",
42
+ "WebCrawlerApiError",
40
43
  "Job",
41
44
  "JobItem",
45
+ "JobMarkdownResponse",
42
46
  "CrawlResponse",
43
47
  "ScrapeId",
44
48
  "ScrapeResponse",