scrapenest 0.3.0__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.
- scrapenest/__init__.py +28 -0
- scrapenest/_async_client.py +573 -0
- scrapenest/_client.py +572 -0
- scrapenest/_http.py +113 -0
- scrapenest/_place_id.py +29 -0
- scrapenest/errors.py +52 -0
- scrapenest/py.typed +0 -0
- scrapenest/types/__init__.py +46 -0
- scrapenest/types/audio.py +65 -0
- scrapenest/types/auth.py +31 -0
- scrapenest/types/gas_prices.py +107 -0
- scrapenest/types/maps.py +97 -0
- scrapenest/types/pdf.py +47 -0
- scrapenest/types/runs.py +58 -0
- scrapenest/types/scrape.py +54 -0
- scrapenest/types/screenshot.py +51 -0
- scrapenest/types/search.py +221 -0
- scrapenest-0.3.0.dist-info/METADATA +175 -0
- scrapenest-0.3.0.dist-info/RECORD +21 -0
- scrapenest-0.3.0.dist-info/WHEEL +4 -0
- scrapenest-0.3.0.dist-info/licenses/LICENSE +21 -0
scrapenest/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from ._async_client import AsyncClient
|
|
2
|
+
from ._client import Client
|
|
3
|
+
from .errors import (
|
|
4
|
+
AuthenticationError,
|
|
5
|
+
ForbiddenError,
|
|
6
|
+
NotFoundError,
|
|
7
|
+
PaymentRequiredError,
|
|
8
|
+
RateLimitError,
|
|
9
|
+
ScrapeNestError,
|
|
10
|
+
ServerError,
|
|
11
|
+
UnexpectedResponseError,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__version__ = "0.3.0"
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Client",
|
|
18
|
+
"AsyncClient",
|
|
19
|
+
"ScrapeNestError",
|
|
20
|
+
"AuthenticationError",
|
|
21
|
+
"PaymentRequiredError",
|
|
22
|
+
"ForbiddenError",
|
|
23
|
+
"NotFoundError",
|
|
24
|
+
"RateLimitError",
|
|
25
|
+
"ServerError",
|
|
26
|
+
"UnexpectedResponseError",
|
|
27
|
+
"__version__",
|
|
28
|
+
]
|
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import time
|
|
5
|
+
from typing import Literal, Optional
|
|
6
|
+
from urllib.parse import quote
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from ._http import (
|
|
11
|
+
DEFAULT_BASE_URL,
|
|
12
|
+
DEFAULT_TIMEOUT,
|
|
13
|
+
_build_headers,
|
|
14
|
+
_detect_stream_error,
|
|
15
|
+
_parse_error,
|
|
16
|
+
_wrap_network_error,
|
|
17
|
+
)
|
|
18
|
+
from ._place_id import normalize_place_id
|
|
19
|
+
from .types.audio import AudioTranscriptResponse
|
|
20
|
+
from .types.auth import MeResponse
|
|
21
|
+
from .types.gas_prices import GasPricesResponse
|
|
22
|
+
from .types.maps import MapsReviewsResponse
|
|
23
|
+
from .types.runs import RunCreatedResponse, RunStatusResponse
|
|
24
|
+
from .types.pdf import PdfResponse
|
|
25
|
+
from .types.scrape import ScrapeUrlResponse
|
|
26
|
+
from .types.screenshot import ScreenshotResponse
|
|
27
|
+
from .types.search import (
|
|
28
|
+
AnswerResponse,
|
|
29
|
+
DeepSearchResponse,
|
|
30
|
+
SearchResponse,
|
|
31
|
+
TranslateResponse,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
_TERMINAL = {"completed", "failed", "cancelled"}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _build_search_payload(
|
|
38
|
+
*,
|
|
39
|
+
query: str,
|
|
40
|
+
num_results: int,
|
|
41
|
+
language: str,
|
|
42
|
+
time_range: Optional[str],
|
|
43
|
+
categories: Optional[str],
|
|
44
|
+
include_domains: Optional[list],
|
|
45
|
+
exclude_domains: Optional[list],
|
|
46
|
+
topic: Optional[str],
|
|
47
|
+
country: Optional[str],
|
|
48
|
+
start_date: Optional[str],
|
|
49
|
+
end_date: Optional[str],
|
|
50
|
+
include_images: bool,
|
|
51
|
+
include_image_descriptions: bool,
|
|
52
|
+
image_sources: Optional[list],
|
|
53
|
+
engines: Optional[list],
|
|
54
|
+
include_usage: bool,
|
|
55
|
+
cache_ttl_seconds: Optional[int],
|
|
56
|
+
) -> dict:
|
|
57
|
+
payload: dict = {"query": query, "num_results": num_results, "language": language}
|
|
58
|
+
if time_range is not None:
|
|
59
|
+
payload["time_range"] = time_range
|
|
60
|
+
if categories is not None:
|
|
61
|
+
payload["categories"] = categories
|
|
62
|
+
if include_domains is not None:
|
|
63
|
+
payload["include_domains"] = list(include_domains)
|
|
64
|
+
if exclude_domains is not None:
|
|
65
|
+
payload["exclude_domains"] = list(exclude_domains)
|
|
66
|
+
if topic is not None:
|
|
67
|
+
payload["topic"] = topic
|
|
68
|
+
if country is not None:
|
|
69
|
+
payload["country"] = country
|
|
70
|
+
if start_date is not None:
|
|
71
|
+
payload["start_date"] = start_date
|
|
72
|
+
if end_date is not None:
|
|
73
|
+
payload["end_date"] = end_date
|
|
74
|
+
if include_images:
|
|
75
|
+
payload["include_images"] = True
|
|
76
|
+
if include_image_descriptions:
|
|
77
|
+
payload["include_image_descriptions"] = True
|
|
78
|
+
if image_sources is not None:
|
|
79
|
+
payload["image_sources"] = list(image_sources)
|
|
80
|
+
if engines is not None:
|
|
81
|
+
payload["engines"] = list(engines)
|
|
82
|
+
if include_usage:
|
|
83
|
+
payload["include_usage"] = True
|
|
84
|
+
if cache_ttl_seconds is not None:
|
|
85
|
+
payload["cache_ttl_seconds"] = cache_ttl_seconds
|
|
86
|
+
return payload
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class AsyncClient:
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
api_key: str,
|
|
93
|
+
*,
|
|
94
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
95
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
96
|
+
extra_headers: Optional[dict[str, str]] = None,
|
|
97
|
+
) -> None:
|
|
98
|
+
self._api_key = api_key
|
|
99
|
+
self._base_url = base_url.rstrip("/")
|
|
100
|
+
merged: dict[str, str] = {}
|
|
101
|
+
if extra_headers:
|
|
102
|
+
merged.update({str(k): str(v) for k, v in extra_headers.items()})
|
|
103
|
+
merged.update(_build_headers(api_key))
|
|
104
|
+
self._http = httpx.AsyncClient(
|
|
105
|
+
base_url=self._base_url,
|
|
106
|
+
headers=merged,
|
|
107
|
+
timeout=timeout,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
async def _request(self, method: str, path: str, **kwargs) -> dict:
|
|
111
|
+
try:
|
|
112
|
+
response = await self._http.request(method, path, **kwargs)
|
|
113
|
+
except httpx.RequestError as exc:
|
|
114
|
+
raise _wrap_network_error(exc) from exc
|
|
115
|
+
if response.is_error:
|
|
116
|
+
try:
|
|
117
|
+
body = response.json()
|
|
118
|
+
except Exception:
|
|
119
|
+
body = {}
|
|
120
|
+
raise _parse_error(response.status_code, body)
|
|
121
|
+
data = response.json()
|
|
122
|
+
stream_error = _detect_stream_error(data)
|
|
123
|
+
if stream_error is not None:
|
|
124
|
+
raise _parse_error(*stream_error)
|
|
125
|
+
return data
|
|
126
|
+
|
|
127
|
+
async def close(self) -> None:
|
|
128
|
+
await self._http.aclose()
|
|
129
|
+
|
|
130
|
+
async def __aenter__(self) -> "AsyncClient":
|
|
131
|
+
return self
|
|
132
|
+
|
|
133
|
+
async def __aexit__(self, *args) -> None:
|
|
134
|
+
await self.close()
|
|
135
|
+
|
|
136
|
+
async def health(self) -> dict:
|
|
137
|
+
return await self._request("GET", "/health")
|
|
138
|
+
|
|
139
|
+
async def me(self) -> MeResponse:
|
|
140
|
+
return MeResponse.from_dict(await self._request("GET", "/v1/me"))
|
|
141
|
+
|
|
142
|
+
async def scrape_url(
|
|
143
|
+
self,
|
|
144
|
+
url: str,
|
|
145
|
+
*,
|
|
146
|
+
render: str = "auto",
|
|
147
|
+
force_proxy: bool = False,
|
|
148
|
+
block_assets: bool = True,
|
|
149
|
+
extract: bool = True,
|
|
150
|
+
ai_query: Optional[str] = None,
|
|
151
|
+
ai_extract_schema: Optional[dict] = None,
|
|
152
|
+
include_usage: bool = False,
|
|
153
|
+
include_html: bool = False,
|
|
154
|
+
return_markdown: bool = False,
|
|
155
|
+
extract_rules: Optional[dict] = None,
|
|
156
|
+
wait_ms: Optional[int] = None,
|
|
157
|
+
wait_for_selector: Optional[str] = None,
|
|
158
|
+
wait_for_timeout_ms: Optional[int] = None,
|
|
159
|
+
window_width: Optional[int] = None,
|
|
160
|
+
window_height: Optional[int] = None,
|
|
161
|
+
country: Optional[str] = None,
|
|
162
|
+
device: str = "desktop",
|
|
163
|
+
block_ads: bool = False,
|
|
164
|
+
simulate_behavior: bool = False,
|
|
165
|
+
cache_ttl_seconds: Optional[int] = None,
|
|
166
|
+
session_id: Optional[str] = None,
|
|
167
|
+
cookies: Optional[dict] = None,
|
|
168
|
+
headers: Optional[dict] = None,
|
|
169
|
+
) -> ScrapeUrlResponse:
|
|
170
|
+
payload: dict = {
|
|
171
|
+
"url": url,
|
|
172
|
+
"render": render,
|
|
173
|
+
"force_proxy": force_proxy,
|
|
174
|
+
"block_assets": block_assets,
|
|
175
|
+
"extract": extract,
|
|
176
|
+
}
|
|
177
|
+
if ai_query is not None:
|
|
178
|
+
payload["ai_query"] = ai_query
|
|
179
|
+
if ai_extract_schema is not None:
|
|
180
|
+
payload["ai_extract_schema"] = ai_extract_schema
|
|
181
|
+
if include_usage:
|
|
182
|
+
payload["include_usage"] = True
|
|
183
|
+
if include_html:
|
|
184
|
+
payload["include_html"] = True
|
|
185
|
+
if return_markdown:
|
|
186
|
+
payload["return_markdown"] = True
|
|
187
|
+
if extract_rules is not None:
|
|
188
|
+
payload["extract_rules"] = extract_rules
|
|
189
|
+
if wait_ms is not None:
|
|
190
|
+
payload["wait_ms"] = wait_ms
|
|
191
|
+
if wait_for_selector is not None:
|
|
192
|
+
payload["wait_for_selector"] = wait_for_selector
|
|
193
|
+
if wait_for_timeout_ms is not None:
|
|
194
|
+
payload["wait_for_timeout_ms"] = wait_for_timeout_ms
|
|
195
|
+
if window_width is not None:
|
|
196
|
+
payload["window_width"] = window_width
|
|
197
|
+
if window_height is not None:
|
|
198
|
+
payload["window_height"] = window_height
|
|
199
|
+
if country is not None:
|
|
200
|
+
payload["country"] = country
|
|
201
|
+
if device != "desktop":
|
|
202
|
+
payload["device"] = device
|
|
203
|
+
if block_ads:
|
|
204
|
+
payload["block_ads"] = True
|
|
205
|
+
if simulate_behavior:
|
|
206
|
+
payload["simulate_behavior"] = True
|
|
207
|
+
if cache_ttl_seconds is not None:
|
|
208
|
+
payload["cache_ttl_seconds"] = cache_ttl_seconds
|
|
209
|
+
if session_id is not None:
|
|
210
|
+
payload["session_id"] = session_id
|
|
211
|
+
if cookies is not None:
|
|
212
|
+
payload["cookies"] = dict(cookies)
|
|
213
|
+
if headers is not None:
|
|
214
|
+
payload["headers"] = dict(headers)
|
|
215
|
+
return ScrapeUrlResponse.from_dict(await self._request("POST", "/v1/scrape/url", json=payload))
|
|
216
|
+
|
|
217
|
+
async def screenshot(
|
|
218
|
+
self,
|
|
219
|
+
url: str,
|
|
220
|
+
*,
|
|
221
|
+
viewport_width: int = 1280,
|
|
222
|
+
viewport_height: int = 720,
|
|
223
|
+
full_page: bool = False,
|
|
224
|
+
selector: Optional[str] = None,
|
|
225
|
+
format: str = "png",
|
|
226
|
+
jpeg_quality: int = 85,
|
|
227
|
+
wait_extra_ms: int = 0,
|
|
228
|
+
session_id: Optional[str] = None,
|
|
229
|
+
cache_ttl_seconds: Optional[int] = None,
|
|
230
|
+
render: str = "auto",
|
|
231
|
+
cookies: Optional[dict] = None,
|
|
232
|
+
headers: Optional[dict] = None,
|
|
233
|
+
include_usage: bool = False,
|
|
234
|
+
) -> ScreenshotResponse:
|
|
235
|
+
payload: dict = {
|
|
236
|
+
"url": url,
|
|
237
|
+
"render": render,
|
|
238
|
+
"viewport_width": viewport_width,
|
|
239
|
+
"viewport_height": viewport_height,
|
|
240
|
+
"full_page": full_page,
|
|
241
|
+
"format": format,
|
|
242
|
+
"jpeg_quality": jpeg_quality,
|
|
243
|
+
"wait_extra_ms": wait_extra_ms,
|
|
244
|
+
}
|
|
245
|
+
if selector is not None:
|
|
246
|
+
payload["selector"] = selector
|
|
247
|
+
if session_id is not None:
|
|
248
|
+
payload["session_id"] = session_id
|
|
249
|
+
if cache_ttl_seconds is not None:
|
|
250
|
+
payload["cache_ttl_seconds"] = cache_ttl_seconds
|
|
251
|
+
if cookies is not None:
|
|
252
|
+
payload["cookies"] = dict(cookies)
|
|
253
|
+
if headers is not None:
|
|
254
|
+
payload["headers"] = dict(headers)
|
|
255
|
+
if include_usage:
|
|
256
|
+
payload["include_usage"] = True
|
|
257
|
+
return ScreenshotResponse.from_dict(await self._request("POST", "/v1/scrape/screenshot", json=payload))
|
|
258
|
+
|
|
259
|
+
async def pdf(
|
|
260
|
+
self,
|
|
261
|
+
url: str,
|
|
262
|
+
*,
|
|
263
|
+
format: str = "Letter",
|
|
264
|
+
landscape: bool = False,
|
|
265
|
+
viewport_width: int = 1280,
|
|
266
|
+
viewport_height: int = 720,
|
|
267
|
+
wait_extra_ms: int = 0,
|
|
268
|
+
cookies: Optional[dict] = None,
|
|
269
|
+
headers: Optional[dict] = None,
|
|
270
|
+
session_id: Optional[str] = None,
|
|
271
|
+
include_usage: bool = False,
|
|
272
|
+
cache_ttl_seconds: Optional[int] = None,
|
|
273
|
+
) -> PdfResponse:
|
|
274
|
+
payload: dict = {
|
|
275
|
+
"url": url,
|
|
276
|
+
"format": format,
|
|
277
|
+
"landscape": landscape,
|
|
278
|
+
"viewport_width": viewport_width,
|
|
279
|
+
"viewport_height": viewport_height,
|
|
280
|
+
"wait_extra_ms": wait_extra_ms,
|
|
281
|
+
}
|
|
282
|
+
if cookies is not None:
|
|
283
|
+
payload["cookies"] = dict(cookies)
|
|
284
|
+
if headers is not None:
|
|
285
|
+
payload["headers"] = dict(headers)
|
|
286
|
+
if session_id is not None:
|
|
287
|
+
payload["session_id"] = session_id
|
|
288
|
+
if include_usage:
|
|
289
|
+
payload["include_usage"] = True
|
|
290
|
+
if cache_ttl_seconds is not None:
|
|
291
|
+
payload["cache_ttl_seconds"] = cache_ttl_seconds
|
|
292
|
+
return PdfResponse.from_dict(await self._request("POST", "/v1/scrape/pdf", json=payload))
|
|
293
|
+
|
|
294
|
+
async def search(
|
|
295
|
+
self,
|
|
296
|
+
query: str,
|
|
297
|
+
*,
|
|
298
|
+
num_results: int = 10,
|
|
299
|
+
depth: str = "balanced",
|
|
300
|
+
language: str = "en",
|
|
301
|
+
time_range: Optional[str] = None,
|
|
302
|
+
categories: Optional[str] = None,
|
|
303
|
+
include_domains: Optional[list] = None,
|
|
304
|
+
exclude_domains: Optional[list] = None,
|
|
305
|
+
topic: Optional[str] = None,
|
|
306
|
+
country: Optional[str] = None,
|
|
307
|
+
start_date: Optional[str] = None,
|
|
308
|
+
end_date: Optional[str] = None,
|
|
309
|
+
include_images: bool = False,
|
|
310
|
+
include_image_descriptions: bool = False,
|
|
311
|
+
image_sources: Optional[list] = None,
|
|
312
|
+
engines: Optional[list] = None,
|
|
313
|
+
include_usage: bool = False,
|
|
314
|
+
cache_ttl_seconds: Optional[int] = None,
|
|
315
|
+
page: int = 1,
|
|
316
|
+
) -> SearchResponse:
|
|
317
|
+
payload = _build_search_payload(
|
|
318
|
+
query=query, num_results=num_results, language=language,
|
|
319
|
+
time_range=time_range, categories=categories,
|
|
320
|
+
include_domains=include_domains, exclude_domains=exclude_domains,
|
|
321
|
+
topic=topic, country=country, start_date=start_date, end_date=end_date,
|
|
322
|
+
include_images=include_images,
|
|
323
|
+
include_image_descriptions=include_image_descriptions,
|
|
324
|
+
image_sources=image_sources,
|
|
325
|
+
engines=engines,
|
|
326
|
+
include_usage=include_usage, cache_ttl_seconds=cache_ttl_seconds,
|
|
327
|
+
)
|
|
328
|
+
payload["depth"] = depth
|
|
329
|
+
if page != 1:
|
|
330
|
+
payload["page"] = page
|
|
331
|
+
return SearchResponse.from_dict(await self._request("POST", "/v1/search", json=payload))
|
|
332
|
+
|
|
333
|
+
async def search_deep(
|
|
334
|
+
self,
|
|
335
|
+
query: str,
|
|
336
|
+
*,
|
|
337
|
+
num_results: int = 10,
|
|
338
|
+
fetch_top: int = 5,
|
|
339
|
+
depth: str = "balanced",
|
|
340
|
+
render: str = "auto",
|
|
341
|
+
language: str = "en",
|
|
342
|
+
time_range: Optional[str] = None,
|
|
343
|
+
categories: Optional[str] = None,
|
|
344
|
+
include_domains: Optional[list] = None,
|
|
345
|
+
exclude_domains: Optional[list] = None,
|
|
346
|
+
topic: Optional[str] = None,
|
|
347
|
+
country: Optional[str] = None,
|
|
348
|
+
start_date: Optional[str] = None,
|
|
349
|
+
end_date: Optional[str] = None,
|
|
350
|
+
include_images: bool = False,
|
|
351
|
+
include_image_descriptions: bool = False,
|
|
352
|
+
image_sources: Optional[list] = None,
|
|
353
|
+
engines: Optional[list] = None,
|
|
354
|
+
include_usage: bool = False,
|
|
355
|
+
cache_ttl_seconds: Optional[int] = None,
|
|
356
|
+
) -> DeepSearchResponse:
|
|
357
|
+
if fetch_top < 1:
|
|
358
|
+
raise ValueError("fetch_top must be >= 1 (use client.search() if you only need SERP)")
|
|
359
|
+
if fetch_top > 10:
|
|
360
|
+
raise ValueError("fetch_top must be <= 10")
|
|
361
|
+
payload = _build_search_payload(
|
|
362
|
+
query=query, num_results=num_results, language=language,
|
|
363
|
+
time_range=time_range, categories=categories,
|
|
364
|
+
include_domains=include_domains, exclude_domains=exclude_domains,
|
|
365
|
+
topic=topic, country=country, start_date=start_date, end_date=end_date,
|
|
366
|
+
include_images=include_images,
|
|
367
|
+
include_image_descriptions=include_image_descriptions,
|
|
368
|
+
image_sources=image_sources,
|
|
369
|
+
engines=engines,
|
|
370
|
+
include_usage=include_usage, cache_ttl_seconds=cache_ttl_seconds,
|
|
371
|
+
)
|
|
372
|
+
payload["fetch_top"] = fetch_top
|
|
373
|
+
payload["render"] = render
|
|
374
|
+
payload["depth"] = depth
|
|
375
|
+
return DeepSearchResponse.from_dict(await self._request("POST", "/v1/search/deep", json=payload))
|
|
376
|
+
|
|
377
|
+
async def search_answer(
|
|
378
|
+
self,
|
|
379
|
+
query: str,
|
|
380
|
+
*,
|
|
381
|
+
num_results: int = 10,
|
|
382
|
+
max_sources: int = 5,
|
|
383
|
+
depth: str = "balanced",
|
|
384
|
+
render: str = "auto",
|
|
385
|
+
language: str = "en",
|
|
386
|
+
time_range: Optional[str] = None,
|
|
387
|
+
categories: Optional[str] = None,
|
|
388
|
+
include_domains: Optional[list] = None,
|
|
389
|
+
exclude_domains: Optional[list] = None,
|
|
390
|
+
topic: Optional[str] = None,
|
|
391
|
+
country: Optional[str] = None,
|
|
392
|
+
start_date: Optional[str] = None,
|
|
393
|
+
end_date: Optional[str] = None,
|
|
394
|
+
engines: Optional[list] = None,
|
|
395
|
+
include_usage: bool = False,
|
|
396
|
+
cache_ttl_seconds: Optional[int] = None,
|
|
397
|
+
) -> AnswerResponse:
|
|
398
|
+
payload = _build_search_payload(
|
|
399
|
+
query=query, num_results=num_results, language=language,
|
|
400
|
+
time_range=time_range, categories=categories,
|
|
401
|
+
include_domains=include_domains, exclude_domains=exclude_domains,
|
|
402
|
+
topic=topic, country=country, start_date=start_date, end_date=end_date,
|
|
403
|
+
include_images=False,
|
|
404
|
+
include_image_descriptions=False,
|
|
405
|
+
image_sources=None,
|
|
406
|
+
engines=engines,
|
|
407
|
+
include_usage=include_usage, cache_ttl_seconds=cache_ttl_seconds,
|
|
408
|
+
)
|
|
409
|
+
payload["max_sources"] = max_sources
|
|
410
|
+
payload["render"] = render
|
|
411
|
+
payload["depth"] = depth
|
|
412
|
+
return AnswerResponse.from_dict(await self._request("POST", "/v1/search/answer", json=payload))
|
|
413
|
+
|
|
414
|
+
async def translate(
|
|
415
|
+
self,
|
|
416
|
+
text: str,
|
|
417
|
+
target: str,
|
|
418
|
+
*,
|
|
419
|
+
source: str = "auto",
|
|
420
|
+
include_usage: bool = False,
|
|
421
|
+
) -> TranslateResponse:
|
|
422
|
+
payload: dict = {"text": text, "target": target, "source": source}
|
|
423
|
+
if include_usage:
|
|
424
|
+
payload["include_usage"] = True
|
|
425
|
+
return TranslateResponse.from_dict(
|
|
426
|
+
await self._request("POST", "/v1/translate", json=payload)
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
async def audio_transcript(
|
|
430
|
+
self,
|
|
431
|
+
url: str,
|
|
432
|
+
*,
|
|
433
|
+
cache_ttl_seconds: Optional[int] = None,
|
|
434
|
+
language: Optional[str] = None,
|
|
435
|
+
cookies: Optional[str] = None,
|
|
436
|
+
include_usage: bool = False,
|
|
437
|
+
) -> AudioTranscriptResponse:
|
|
438
|
+
payload: dict = {"url": url}
|
|
439
|
+
if cache_ttl_seconds is not None:
|
|
440
|
+
payload["cache_ttl_seconds"] = cache_ttl_seconds
|
|
441
|
+
if language is not None:
|
|
442
|
+
payload["language"] = language
|
|
443
|
+
if cookies is not None:
|
|
444
|
+
payload["cookies"] = cookies
|
|
445
|
+
if include_usage:
|
|
446
|
+
payload["include_usage"] = True
|
|
447
|
+
return AudioTranscriptResponse.from_dict(
|
|
448
|
+
await self._request("POST", "/v1/audio/transcript", json=payload)
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
async def audio_transcript_async(
|
|
452
|
+
self,
|
|
453
|
+
url: str,
|
|
454
|
+
*,
|
|
455
|
+
cache_ttl_seconds: Optional[int] = None,
|
|
456
|
+
language: Optional[str] = None,
|
|
457
|
+
cookies: Optional[str] = None,
|
|
458
|
+
include_usage: bool = False,
|
|
459
|
+
) -> RunCreatedResponse:
|
|
460
|
+
payload: dict = {"url": url}
|
|
461
|
+
if cache_ttl_seconds is not None:
|
|
462
|
+
payload["cache_ttl_seconds"] = cache_ttl_seconds
|
|
463
|
+
if language is not None:
|
|
464
|
+
payload["language"] = language
|
|
465
|
+
if cookies is not None:
|
|
466
|
+
payload["cookies"] = cookies
|
|
467
|
+
if include_usage:
|
|
468
|
+
payload["include_usage"] = True
|
|
469
|
+
return RunCreatedResponse.from_dict(
|
|
470
|
+
await self._request("POST", "/v1/audio/transcript/async", json=payload)
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
async def maps_reviews(
|
|
474
|
+
self,
|
|
475
|
+
place_id: str,
|
|
476
|
+
*,
|
|
477
|
+
language: str = "en",
|
|
478
|
+
sort: Literal["newest", "most_relevant", "highest_rating", "lowest_rating"] = "newest",
|
|
479
|
+
max_reviews: int = 200,
|
|
480
|
+
place_url: Optional[str] = None,
|
|
481
|
+
include_usage: bool = False,
|
|
482
|
+
cache_ttl_seconds: Optional[int] = None,
|
|
483
|
+
) -> MapsReviewsResponse:
|
|
484
|
+
payload: dict = {
|
|
485
|
+
"place_id": normalize_place_id(place_id),
|
|
486
|
+
"language": language,
|
|
487
|
+
"sort": sort,
|
|
488
|
+
"max_reviews": max_reviews,
|
|
489
|
+
}
|
|
490
|
+
if place_url is not None:
|
|
491
|
+
payload["place_url"] = place_url
|
|
492
|
+
if include_usage:
|
|
493
|
+
payload["include_usage"] = True
|
|
494
|
+
if cache_ttl_seconds is not None:
|
|
495
|
+
payload["cache_ttl_seconds"] = cache_ttl_seconds
|
|
496
|
+
return MapsReviewsResponse.from_dict(
|
|
497
|
+
await self._request("POST", "/v1/scrape/google-maps/reviews", json=payload)
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
async def maps_reviews_async(
|
|
501
|
+
self,
|
|
502
|
+
place_id: str,
|
|
503
|
+
*,
|
|
504
|
+
language: str = "en",
|
|
505
|
+
sort: Literal["newest", "most_relevant", "highest_rating", "lowest_rating"] = "newest",
|
|
506
|
+
max_reviews: int = 200,
|
|
507
|
+
place_url: Optional[str] = None,
|
|
508
|
+
include_usage: bool = False,
|
|
509
|
+
cache_ttl_seconds: Optional[int] = None,
|
|
510
|
+
) -> RunCreatedResponse:
|
|
511
|
+
payload: dict = {
|
|
512
|
+
"place_id": normalize_place_id(place_id),
|
|
513
|
+
"language": language,
|
|
514
|
+
"sort": sort,
|
|
515
|
+
"max_reviews": max_reviews,
|
|
516
|
+
}
|
|
517
|
+
if place_url is not None:
|
|
518
|
+
payload["place_url"] = place_url
|
|
519
|
+
if include_usage:
|
|
520
|
+
payload["include_usage"] = True
|
|
521
|
+
if cache_ttl_seconds is not None:
|
|
522
|
+
payload["cache_ttl_seconds"] = cache_ttl_seconds
|
|
523
|
+
return RunCreatedResponse.from_dict(
|
|
524
|
+
await self._request("POST", "/v1/scrape/google-maps/reviews/async", json=payload)
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
async def gas_prices(
|
|
528
|
+
self,
|
|
529
|
+
location: str,
|
|
530
|
+
*,
|
|
531
|
+
grade: str = "regular",
|
|
532
|
+
limit: int = 20,
|
|
533
|
+
include_usage: bool = False,
|
|
534
|
+
cache_ttl_seconds: Optional[int] = None,
|
|
535
|
+
) -> GasPricesResponse:
|
|
536
|
+
payload: dict = {
|
|
537
|
+
"location": location,
|
|
538
|
+
"grade": grade,
|
|
539
|
+
"limit": limit,
|
|
540
|
+
}
|
|
541
|
+
if include_usage:
|
|
542
|
+
payload["include_usage"] = True
|
|
543
|
+
if cache_ttl_seconds is not None:
|
|
544
|
+
payload["cache_ttl_seconds"] = cache_ttl_seconds
|
|
545
|
+
return GasPricesResponse.from_dict(
|
|
546
|
+
await self._request("POST", "/v1/scrape/gas-prices", json=payload)
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
async def get_run(self, run_id: str) -> RunStatusResponse:
|
|
550
|
+
return RunStatusResponse.from_dict(
|
|
551
|
+
await self._request("GET", f"/v1/scrape/runs/{quote(run_id, safe='')}")
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
async def cancel_run(self, run_id: str) -> RunStatusResponse:
|
|
555
|
+
return RunStatusResponse.from_dict(
|
|
556
|
+
await self._request("POST", f"/v1/scrape/runs/{quote(run_id, safe='')}/cancel")
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
async def wait_for_run(
|
|
560
|
+
self,
|
|
561
|
+
run_id: str,
|
|
562
|
+
*,
|
|
563
|
+
poll_interval: float = 3.0,
|
|
564
|
+
timeout: float = 300.0,
|
|
565
|
+
) -> RunStatusResponse:
|
|
566
|
+
deadline = time.monotonic() + timeout
|
|
567
|
+
while True:
|
|
568
|
+
run = await self.get_run(run_id)
|
|
569
|
+
if run.status in _TERMINAL:
|
|
570
|
+
return run
|
|
571
|
+
if time.monotonic() >= deadline:
|
|
572
|
+
raise TimeoutError(f"Run {run_id} did not complete within {timeout}s")
|
|
573
|
+
await asyncio.sleep(poll_interval)
|