scrapeunblocker 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.
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.8", "3.11", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - run: python -m pip install --upgrade pip
20
+ - run: pip install -e ".[dev]"
21
+ - run: pytest -q
@@ -0,0 +1,41 @@
1
+ name: Publish to PyPI
2
+
3
+ # Publishes on every GitHub Release. Uses PyPI Trusted Publishing (OIDC) -
4
+ # no API tokens stored anywhere. Configure the trusted publisher once at
5
+ # https://pypi.org/manage/account/publishing/ pointing at this repo +
6
+ # workflow (publish.yml) and environment "pypi".
7
+
8
+ on:
9
+ release:
10
+ types: [published]
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ build:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.12"
23
+ - run: python -m pip install --upgrade build
24
+ - run: python -m build
25
+ - uses: actions/upload-artifact@v4
26
+ with:
27
+ name: dist
28
+ path: dist/
29
+
30
+ publish:
31
+ needs: build
32
+ runs-on: ubuntu-latest
33
+ environment: pypi
34
+ permissions:
35
+ id-token: write # required for trusted publishing
36
+ steps:
37
+ - uses: actions/download-artifact@v4
38
+ with:
39
+ name: dist
40
+ path: dist/
41
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,22 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+ env/
11
+
12
+ # Tooling
13
+ .pytest_cache/
14
+ .mypy_cache/
15
+ .ruff_cache/
16
+ .coverage
17
+ htmlcov/
18
+
19
+ # OS / editor
20
+ .DS_Store
21
+ .idea/
22
+ .vscode/
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ Initial release.
6
+
7
+ - Sync `Client` and async `AsyncClient`.
8
+ - `get_page_source`, `get_parsed`, `get_page_with_cookies`, `serp`, `get_image`.
9
+ - Skyscanner plugins: flights, hotels, car hire (quotes + locations).
10
+ - Typed exception hierarchy and automatic retry on transient failures.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ScrapeUnblocker
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.
@@ -0,0 +1,208 @@
1
+ Metadata-Version: 2.4
2
+ Name: scrapeunblocker
3
+ Version: 0.1.0
4
+ Summary: Official Python client for the ScrapeUnblocker web scraping API - JavaScript-rendered pages that bypass Cloudflare, DataDome, PerimeterX, Akamai and more.
5
+ Project-URL: Homepage, https://scrapeunblocker.com
6
+ Project-URL: Documentation, https://developers.scrapeunblocker.com
7
+ Project-URL: Source, https://github.com/ScrapeUnblocker/scrapeunblocker-python
8
+ Project-URL: Changelog, https://github.com/ScrapeUnblocker/scrapeunblocker-python/blob/main/CHANGELOG.md
9
+ Author-email: ScrapeUnblocker <support@scrapeunblocker.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: anti-bot,captcha,cloudflare,crawler,datadome,proxy,scraping api,web scraping
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Internet :: WWW/HTTP
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.8
28
+ Requires-Dist: httpx>=0.23
29
+ Provides-Extra: dev
30
+ Requires-Dist: mypy>=1.0; extra == 'dev'
31
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
32
+ Requires-Dist: pytest>=7; extra == 'dev'
33
+ Requires-Dist: respx>=0.20; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # ScrapeUnblocker Python client
37
+
38
+ Official Python client for the [ScrapeUnblocker](https://scrapeunblocker.com) web scraping API.
39
+
40
+ Every request is fully JavaScript-rendered in a real browser and routed through premium proxies, so it bypasses Cloudflare, DataDome, PerimeterX, Akamai, Kasada and similar anti-bot systems - from one simple call. You are only billed for successful requests.
41
+
42
+ - **Highest success rate on the market** (95%+ on live production traffic)
43
+ - **Rendered HTML or parsed JSON** - no per-site parsers to maintain
44
+ - Sync **and** async clients, fully type-hinted
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install scrapeunblocker
50
+ ```
51
+
52
+ Requires Python 3.8+.
53
+
54
+ ## Quickstart
55
+
56
+ ```python
57
+ from scrapeunblocker import Client
58
+
59
+ su = Client(api_key="su_live_...") # or set the SCRAPEUNBLOCKER_KEY env var
60
+
61
+ # Rendered HTML for any URL
62
+ html = su.get_page_source("https://example.com")
63
+
64
+ # Structured JSON instead of HTML (products, listings, search results, ...)
65
+ product = su.get_parsed("https://www.amazon.com/dp/B08N5WRWNW")
66
+ print(product.page_type) # "product"
67
+ print(product.data) # {...}
68
+ ```
69
+
70
+ Get your API key at [app.scrapeunblocker.com](https://app.scrapeunblocker.com). The free trial does not require a credit card.
71
+
72
+ ## Authentication
73
+
74
+ Pass the key directly, or set an environment variable and omit it:
75
+
76
+ ```bash
77
+ export SCRAPEUNBLOCKER_KEY="su_live_..."
78
+ ```
79
+
80
+ ```python
81
+ from scrapeunblocker import Client
82
+ su = Client() # reads SCRAPEUNBLOCKER_KEY
83
+ ```
84
+
85
+ ## Fetch rendered HTML
86
+
87
+ ```python
88
+ html = su.get_page_source(
89
+ "https://www.nordstrom.com/browse/women/clothing/dresses",
90
+ proxy_country="US", # route through a specific country
91
+ time_sleep=3, # wait extra seconds after load
92
+ )
93
+ ```
94
+
95
+ ## Get parsed JSON
96
+
97
+ Pass a URL and get back structured data extracted via Schema.org, `__NEXT_DATA__` or AI-generated rules:
98
+
99
+ ```python
100
+ result = su.get_parsed("https://www.walmart.com/ip/12345")
101
+ print(result.page_type) # e.g. "product"
102
+ print(result.source) # how it was extracted
103
+ print(result.data) # the fields
104
+
105
+ # If a parse ever comes back wrong, force a fresh set of rules:
106
+ result = su.get_parsed(url, refresh_rules=True, rules_hint="price is missing")
107
+ ```
108
+
109
+ ## Google search (SERP)
110
+
111
+ ```python
112
+ serp = su.serp("web scraping api", pages_to_check=2, proxy_country="US")
113
+ ```
114
+
115
+ ## Cookies and the serving proxy
116
+
117
+ ```python
118
+ page = su.get_page_with_cookies("https://example.com")
119
+ print(page.html, page.cookies, page.proxy)
120
+ ```
121
+
122
+ ## Images
123
+
124
+ ```python
125
+ data = su.get_image("https://example.com/photo.jpg")
126
+ open("photo.jpg", "wb").write(data)
127
+ ```
128
+
129
+ ## Skyscanner plugins
130
+
131
+ ```python
132
+ # Resolve a place name to entity IDs, then search
133
+ locs = su.skyscanner.flight_locations("London")
134
+ flights = su.skyscanner.flights(
135
+ origin="London", dest="New York",
136
+ depart_date="2026-09-01", adults=1, currency="USD",
137
+ )
138
+
139
+ hotels = su.skyscanner.hotels(destination="Madrid", checkin="2026-09-01", checkout="2026-09-03")
140
+ cars = su.skyscanner.carhire(pickup="Madrid", pickup_datetime="2026-09-01T10:00", dropoff_datetime="2026-09-03T10:00")
141
+ ```
142
+
143
+ ## Async
144
+
145
+ Every method has an async twin on `AsyncClient`:
146
+
147
+ ```python
148
+ import asyncio
149
+ from scrapeunblocker import AsyncClient
150
+
151
+ async def main():
152
+ async with AsyncClient(api_key="su_live_...") as su:
153
+ html = await su.get_page_source("https://example.com")
154
+
155
+ asyncio.run(main())
156
+ ```
157
+
158
+ ## Error handling
159
+
160
+ Non-2xx responses raise typed exceptions, all subclasses of `ScrapeUnblockerError`:
161
+
162
+ ```python
163
+ from scrapeunblocker import Client, BlockedError, RateLimitError, UpstreamOutageError
164
+
165
+ su = Client()
166
+ try:
167
+ html = su.get_page_source("https://example.com")
168
+ except BlockedError:
169
+ ... # 403: the target blocked every bypass path (not billed)
170
+ except RateLimitError:
171
+ ... # 429: slow down
172
+ except UpstreamOutageError:
173
+ ... # 503: the target site itself is down - retry later
174
+ ```
175
+
176
+ | Exception | Status | Meaning |
177
+ |---|---|---|
178
+ | `InvalidRequestError` | 400 | Bad URL or unsupported scheme |
179
+ | `AuthenticationError` | 401 | Missing or invalid API key |
180
+ | `BlockedError` | 403 | Blocked by bot protection on every path |
181
+ | `RateLimitError` | 429 | Too many requests |
182
+ | `UpstreamOutageError` | 503 | The target origin is down |
183
+ | `ServerError` | 5xx | Unexpected server error |
184
+ | `ScrapeTimeoutError` | - | Request exceeded the timeout |
185
+ | `ConnectionError` | - | Could not reach the API |
186
+
187
+ Transient failures (429, 502, 503, 504 and network errors) are retried automatically with exponential backoff; tune with `Client(max_retries=...)`.
188
+
189
+ ## Configuration
190
+
191
+ ```python
192
+ Client(
193
+ api_key=None, # or SCRAPEUNBLOCKER_KEY env var
194
+ base_url="https://api.scrapeunblocker.com",
195
+ timeout=180.0, # seconds; protected pages can be slow
196
+ max_retries=2,
197
+ )
198
+ ```
199
+
200
+ ## Links
201
+
202
+ - Documentation: https://developers.scrapeunblocker.com
203
+ - Website: https://scrapeunblocker.com
204
+ - Dashboard: https://app.scrapeunblocker.com
205
+
206
+ ## License
207
+
208
+ MIT
@@ -0,0 +1,173 @@
1
+ # ScrapeUnblocker Python client
2
+
3
+ Official Python client for the [ScrapeUnblocker](https://scrapeunblocker.com) web scraping API.
4
+
5
+ Every request is fully JavaScript-rendered in a real browser and routed through premium proxies, so it bypasses Cloudflare, DataDome, PerimeterX, Akamai, Kasada and similar anti-bot systems - from one simple call. You are only billed for successful requests.
6
+
7
+ - **Highest success rate on the market** (95%+ on live production traffic)
8
+ - **Rendered HTML or parsed JSON** - no per-site parsers to maintain
9
+ - Sync **and** async clients, fully type-hinted
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install scrapeunblocker
15
+ ```
16
+
17
+ Requires Python 3.8+.
18
+
19
+ ## Quickstart
20
+
21
+ ```python
22
+ from scrapeunblocker import Client
23
+
24
+ su = Client(api_key="su_live_...") # or set the SCRAPEUNBLOCKER_KEY env var
25
+
26
+ # Rendered HTML for any URL
27
+ html = su.get_page_source("https://example.com")
28
+
29
+ # Structured JSON instead of HTML (products, listings, search results, ...)
30
+ product = su.get_parsed("https://www.amazon.com/dp/B08N5WRWNW")
31
+ print(product.page_type) # "product"
32
+ print(product.data) # {...}
33
+ ```
34
+
35
+ Get your API key at [app.scrapeunblocker.com](https://app.scrapeunblocker.com). The free trial does not require a credit card.
36
+
37
+ ## Authentication
38
+
39
+ Pass the key directly, or set an environment variable and omit it:
40
+
41
+ ```bash
42
+ export SCRAPEUNBLOCKER_KEY="su_live_..."
43
+ ```
44
+
45
+ ```python
46
+ from scrapeunblocker import Client
47
+ su = Client() # reads SCRAPEUNBLOCKER_KEY
48
+ ```
49
+
50
+ ## Fetch rendered HTML
51
+
52
+ ```python
53
+ html = su.get_page_source(
54
+ "https://www.nordstrom.com/browse/women/clothing/dresses",
55
+ proxy_country="US", # route through a specific country
56
+ time_sleep=3, # wait extra seconds after load
57
+ )
58
+ ```
59
+
60
+ ## Get parsed JSON
61
+
62
+ Pass a URL and get back structured data extracted via Schema.org, `__NEXT_DATA__` or AI-generated rules:
63
+
64
+ ```python
65
+ result = su.get_parsed("https://www.walmart.com/ip/12345")
66
+ print(result.page_type) # e.g. "product"
67
+ print(result.source) # how it was extracted
68
+ print(result.data) # the fields
69
+
70
+ # If a parse ever comes back wrong, force a fresh set of rules:
71
+ result = su.get_parsed(url, refresh_rules=True, rules_hint="price is missing")
72
+ ```
73
+
74
+ ## Google search (SERP)
75
+
76
+ ```python
77
+ serp = su.serp("web scraping api", pages_to_check=2, proxy_country="US")
78
+ ```
79
+
80
+ ## Cookies and the serving proxy
81
+
82
+ ```python
83
+ page = su.get_page_with_cookies("https://example.com")
84
+ print(page.html, page.cookies, page.proxy)
85
+ ```
86
+
87
+ ## Images
88
+
89
+ ```python
90
+ data = su.get_image("https://example.com/photo.jpg")
91
+ open("photo.jpg", "wb").write(data)
92
+ ```
93
+
94
+ ## Skyscanner plugins
95
+
96
+ ```python
97
+ # Resolve a place name to entity IDs, then search
98
+ locs = su.skyscanner.flight_locations("London")
99
+ flights = su.skyscanner.flights(
100
+ origin="London", dest="New York",
101
+ depart_date="2026-09-01", adults=1, currency="USD",
102
+ )
103
+
104
+ hotels = su.skyscanner.hotels(destination="Madrid", checkin="2026-09-01", checkout="2026-09-03")
105
+ cars = su.skyscanner.carhire(pickup="Madrid", pickup_datetime="2026-09-01T10:00", dropoff_datetime="2026-09-03T10:00")
106
+ ```
107
+
108
+ ## Async
109
+
110
+ Every method has an async twin on `AsyncClient`:
111
+
112
+ ```python
113
+ import asyncio
114
+ from scrapeunblocker import AsyncClient
115
+
116
+ async def main():
117
+ async with AsyncClient(api_key="su_live_...") as su:
118
+ html = await su.get_page_source("https://example.com")
119
+
120
+ asyncio.run(main())
121
+ ```
122
+
123
+ ## Error handling
124
+
125
+ Non-2xx responses raise typed exceptions, all subclasses of `ScrapeUnblockerError`:
126
+
127
+ ```python
128
+ from scrapeunblocker import Client, BlockedError, RateLimitError, UpstreamOutageError
129
+
130
+ su = Client()
131
+ try:
132
+ html = su.get_page_source("https://example.com")
133
+ except BlockedError:
134
+ ... # 403: the target blocked every bypass path (not billed)
135
+ except RateLimitError:
136
+ ... # 429: slow down
137
+ except UpstreamOutageError:
138
+ ... # 503: the target site itself is down - retry later
139
+ ```
140
+
141
+ | Exception | Status | Meaning |
142
+ |---|---|---|
143
+ | `InvalidRequestError` | 400 | Bad URL or unsupported scheme |
144
+ | `AuthenticationError` | 401 | Missing or invalid API key |
145
+ | `BlockedError` | 403 | Blocked by bot protection on every path |
146
+ | `RateLimitError` | 429 | Too many requests |
147
+ | `UpstreamOutageError` | 503 | The target origin is down |
148
+ | `ServerError` | 5xx | Unexpected server error |
149
+ | `ScrapeTimeoutError` | - | Request exceeded the timeout |
150
+ | `ConnectionError` | - | Could not reach the API |
151
+
152
+ Transient failures (429, 502, 503, 504 and network errors) are retried automatically with exponential backoff; tune with `Client(max_retries=...)`.
153
+
154
+ ## Configuration
155
+
156
+ ```python
157
+ Client(
158
+ api_key=None, # or SCRAPEUNBLOCKER_KEY env var
159
+ base_url="https://api.scrapeunblocker.com",
160
+ timeout=180.0, # seconds; protected pages can be slow
161
+ max_retries=2,
162
+ )
163
+ ```
164
+
165
+ ## Links
166
+
167
+ - Documentation: https://developers.scrapeunblocker.com
168
+ - Website: https://scrapeunblocker.com
169
+ - Dashboard: https://app.scrapeunblocker.com
170
+
171
+ ## License
172
+
173
+ MIT
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "scrapeunblocker"
7
+ version = "0.1.0"
8
+ description = "Official Python client for the ScrapeUnblocker web scraping API - JavaScript-rendered pages that bypass Cloudflare, DataDome, PerimeterX, Akamai and more."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = "MIT"
12
+ authors = [{ name = "ScrapeUnblocker", email = "support@scrapeunblocker.com" }]
13
+ keywords = [
14
+ "web scraping",
15
+ "scraping api",
16
+ "anti-bot",
17
+ "proxy",
18
+ "cloudflare",
19
+ "datadome",
20
+ "captcha",
21
+ "crawler",
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 4 - Beta",
25
+ "Intended Audience :: Developers",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Operating System :: OS Independent",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.8",
30
+ "Programming Language :: Python :: 3.9",
31
+ "Programming Language :: Python :: 3.10",
32
+ "Programming Language :: Python :: 3.11",
33
+ "Programming Language :: Python :: 3.12",
34
+ "Programming Language :: Python :: 3.13",
35
+ "Topic :: Internet :: WWW/HTTP",
36
+ "Topic :: Software Development :: Libraries :: Python Modules",
37
+ "Typing :: Typed",
38
+ ]
39
+ dependencies = ["httpx>=0.23"]
40
+
41
+ [project.urls]
42
+ Homepage = "https://scrapeunblocker.com"
43
+ Documentation = "https://developers.scrapeunblocker.com"
44
+ Source = "https://github.com/ScrapeUnblocker/scrapeunblocker-python"
45
+ Changelog = "https://github.com/ScrapeUnblocker/scrapeunblocker-python/blob/main/CHANGELOG.md"
46
+
47
+ [project.optional-dependencies]
48
+ dev = ["pytest>=7", "pytest-asyncio>=0.21", "respx>=0.20", "mypy>=1.0"]
49
+
50
+ [tool.hatch.build.targets.wheel]
51
+ packages = ["src/scrapeunblocker"]
52
+
53
+ [tool.pytest.ini_options]
54
+ asyncio_mode = "auto"
55
+ testpaths = ["tests"]
@@ -0,0 +1,47 @@
1
+ """Official Python client for the ScrapeUnblocker web scraping API.
2
+
3
+ Basic usage::
4
+
5
+ from scrapeunblocker import Client
6
+
7
+ su = Client(api_key="su_live_...") # or set SCRAPEUNBLOCKER_KEY
8
+ html = su.get_page_source("https://example.com")
9
+ product = su.get_parsed("https://www.amazon.com/dp/B08N5WRWNW")
10
+
11
+ See https://developers.scrapeunblocker.com for the full API reference.
12
+ """
13
+
14
+ from ._async_client import AsyncClient
15
+ from ._client import Client
16
+ from .exceptions import (
17
+ APIError,
18
+ AuthenticationError,
19
+ BlockedError,
20
+ ConnectionError,
21
+ InvalidRequestError,
22
+ RateLimitError,
23
+ ScrapeTimeoutError,
24
+ ScrapeUnblockerError,
25
+ ServerError,
26
+ UpstreamOutageError,
27
+ )
28
+ from .models import PageResult, ParsedPage
29
+ from .version import __version__
30
+
31
+ __all__ = [
32
+ "Client",
33
+ "AsyncClient",
34
+ "ParsedPage",
35
+ "PageResult",
36
+ "ScrapeUnblockerError",
37
+ "APIError",
38
+ "AuthenticationError",
39
+ "InvalidRequestError",
40
+ "BlockedError",
41
+ "RateLimitError",
42
+ "UpstreamOutageError",
43
+ "ServerError",
44
+ "ScrapeTimeoutError",
45
+ "ConnectionError",
46
+ "__version__",
47
+ ]