olostep 0.9.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.
- olostep-0.9.0/LICENSE +21 -0
- olostep-0.9.0/PKG-INFO +360 -0
- olostep-0.9.0/README.md +344 -0
- olostep-0.9.0/olostep/.DS_Store +0 -0
- olostep-0.9.0/olostep/__init__.py +60 -0
- olostep-0.9.0/olostep/_log.py +238 -0
- olostep-0.9.0/olostep/backend/api_endpoints.py +323 -0
- olostep-0.9.0/olostep/backend/caller.py +394 -0
- olostep-0.9.0/olostep/backend/transport.py +211 -0
- olostep-0.9.0/olostep/backend/transport_protocol.py +45 -0
- olostep-0.9.0/olostep/clients/async_client.py +55 -0
- olostep-0.9.0/olostep/clients/sync_client.py +78 -0
- olostep-0.9.0/olostep/config.py +23 -0
- olostep-0.9.0/olostep/errors.py +411 -0
- olostep-0.9.0/olostep/frontend/_id_storage.py +74 -0
- olostep-0.9.0/olostep/frontend/batch_menu.py +187 -0
- olostep-0.9.0/olostep/frontend/client_state.py +1037 -0
- olostep-0.9.0/olostep/frontend/crawl_menu.py +200 -0
- olostep-0.9.0/olostep/frontend/input_coersion.py +36 -0
- olostep-0.9.0/olostep/frontend/retrieve_menu.py +89 -0
- olostep-0.9.0/olostep/frontend/scrape_menu.py +182 -0
- olostep-0.9.0/olostep/frontend/sitemap_menu.py +107 -0
- olostep-0.9.0/olostep/models/base.py +270 -0
- olostep-0.9.0/olostep/models/request.py +844 -0
- olostep-0.9.0/olostep/models/response.py +363 -0
- olostep-0.9.0/pyproject.toml +38 -0
olostep-0.9.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Olostep
|
|
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.
|
olostep-0.9.0/PKG-INFO
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: olostep
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Olostep Python SDK - Programmatic access to the web
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: Olostep Team
|
|
7
|
+
Author-email: team@olostep.com
|
|
8
|
+
Requires-Python: >=3.11,<3.14
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Requires-Dist: httpx[http2] (>=0.28.1,<0.29.0)
|
|
14
|
+
Requires-Dist: pydantic (>=2.11.3,<3.0.0)
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Olostep Python SDK (official)
|
|
18
|
+
|
|
19
|
+
Olostep provides a **programmatic layer to access and interact with the web**.
|
|
20
|
+
Fetch clean, ready-to-use data for your AI from any website in **1–5 seconds**, and scale up to **100K parallel requests in minutes**. You can:
|
|
21
|
+
|
|
22
|
+
- **Scrape** websites with various output formats incl. structured data.
|
|
23
|
+
- **Batch** scrape hundreds of websites
|
|
24
|
+
- **Crawl** websites
|
|
25
|
+
- Create massive **sitemaps** in seconds
|
|
26
|
+
|
|
27
|
+
[Get a free API key here](https://www.olostep.com/?utm_source=python_readme)
|
|
28
|
+
|
|
29
|
+
## Python SDK vs API
|
|
30
|
+
|
|
31
|
+
The Olostep Python SDK offers features and ergonomics beyond the [REST API](https://docs.olostep.com):
|
|
32
|
+
|
|
33
|
+
It streamlines web data access with a type-safe, stateful, and ergonomic experience not available in the REST API.
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
- **Discoverable and predictable interface**: Dot-notation namespaces, stateful return objects (IDs, cursors), convenience methods and rich `__repr__`s.
|
|
37
|
+
- **Async-first, Sync-friendly**: The SDK is async by default (but also provides a sync facade) This allows you async performance over an otherwise high latency sync API.
|
|
38
|
+
- **Type Safety & Input Coercion**: The client uses Pydantic to validate inputs and coerce them where possible. It also provides you with full type hints. It also catches errors before the request is being sent to the API (can be disabled).
|
|
39
|
+
- **Elegant Pagination**: `async for x in y.(batch_size=10):` for `batch` and `crawl` and `sitemap` handles all pagination.
|
|
40
|
+
- **Wait Methods**: `.wait_till_done()` for `batch` and `crawl` workflows.
|
|
41
|
+
- **Automatic retries**, improved errors and connection handling for high latency and throughput.
|
|
42
|
+
- **Python 3.11+**.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
$ pip install olostep
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
"""
|
|
55
|
+
The quickstart uses the async/await interface as it's the default and generally preferred.
|
|
56
|
+
* If you need a blocking interface scroll to the end of this codeblock.
|
|
57
|
+
* If you want to see the full interfaces scroll to the next section.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
from olostep import OlostepClient
|
|
61
|
+
|
|
62
|
+
# Provide the API key either via passing in the 'api_key' parameter or
|
|
63
|
+
# by setting the OLOSTEP_API_KEY environment variable
|
|
64
|
+
client = OlostepClient(api_key="YOUR_REAL_KEY")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# MINIMAL SCRAPE EXAMPLE
|
|
69
|
+
|
|
70
|
+
scrape_result = await client.scrape("https://example.com")
|
|
71
|
+
# -> ScrapeResult(id='scrape_123', available=['html_content', 'markdown_content'])
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# MINIMAL BATCH EXAMPLE
|
|
76
|
+
|
|
77
|
+
batch = await client.batch(["https://site1.com", "https://site2.com"])
|
|
78
|
+
# -> Batch(id='batch_123', urls=2)
|
|
79
|
+
|
|
80
|
+
# waits for all the batch jobs to finish, then starts fetching the results in batches
|
|
81
|
+
async for item in batch.items():
|
|
82
|
+
content = await item.retrieve(["html"])
|
|
83
|
+
print(f"{item.url}: {len(content.html_content)} bytes")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# MINIMAL CRAWL EXAMPLE
|
|
88
|
+
|
|
89
|
+
crawl = await client.crawl("https://example.com", max_pages=100)
|
|
90
|
+
# -> Crawl(id='crawl_123', urls=100)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async for page in crawl.pages():
|
|
94
|
+
content = await page.retrieve(["html"])
|
|
95
|
+
print(f"{page.url}: {len(content.html_content)} bytes")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# SYNC (FACADE) CLIENT
|
|
100
|
+
# this client is just a wrapper around the async client.
|
|
101
|
+
# The interface is the same, just don't use await.
|
|
102
|
+
# If you can use the OlostepClient instead, do so.
|
|
103
|
+
from olostep import SyncOlostepClient
|
|
104
|
+
|
|
105
|
+
client = SyncOlostepClient(api_key="YOUR_REAL_KEY")
|
|
106
|
+
|
|
107
|
+
scrape_result = client.scrape("https://example.com")
|
|
108
|
+
# -> ScrapeResult(id='scrape_123', available=['html_content', 'markdown_content'])
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## API Design
|
|
112
|
+
|
|
113
|
+
The SDK provides a clean, Pythonic interface organized into logical namespaces. Each operation returns stateful objects with ergonomic methods for follow-up operations.
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
#### Scraping
|
|
117
|
+
```python
|
|
118
|
+
from olostep import OlostepClient
|
|
119
|
+
from olostep import Country, FillInputAction, Format, LLMExtract, LinksOnPage, ScreenSize, Transformer, WaitAction
|
|
120
|
+
|
|
121
|
+
client = OlostepClient(api_key="YOUR_REAL_KEY")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# Minimal: Just scrape a URL
|
|
125
|
+
result = await client.scrape("https://example.com")
|
|
126
|
+
# ScrapeResult(id='scrape_123', available=['html_content', 'markdown_content'])
|
|
127
|
+
|
|
128
|
+
# Maximal: Full control over scraping behavior
|
|
129
|
+
result = await client.scrape(
|
|
130
|
+
"https://example.com",
|
|
131
|
+
wait_before_scraping=3000,
|
|
132
|
+
formats=[Format.HTML, Format.MARKDOWN],
|
|
133
|
+
remove_css_selectors=["script", ".popup"],
|
|
134
|
+
actions=[
|
|
135
|
+
WaitAction(milliseconds=1500),
|
|
136
|
+
FillInputAction(selector="searchbox", value="olostep")
|
|
137
|
+
],
|
|
138
|
+
country=Country.US,
|
|
139
|
+
transformer=Transformer("postlight"),
|
|
140
|
+
remove_images=True,
|
|
141
|
+
remove_class_names=["ad"],
|
|
142
|
+
parser="VALID_PARSER", #check website for valid parsers
|
|
143
|
+
llm_extract=LLMExtract(schema="YOUR_SCHEMA"),
|
|
144
|
+
links_on_page=LinksOnPage(
|
|
145
|
+
absolute_links=False,
|
|
146
|
+
query_to_order_links_by='cars',
|
|
147
|
+
include_links=["/events/**", "/offers/**"],
|
|
148
|
+
exclude_links=[".pdf"]
|
|
149
|
+
),
|
|
150
|
+
screen_size=ScreenSize(screen_width=1920, screen_height=1080),
|
|
151
|
+
metadata={"custom": "sidecart_data"} # Not supported yet
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Batch Processing
|
|
156
|
+
```python
|
|
157
|
+
from olostep import OlostepClient
|
|
158
|
+
from olostep import BatchItem, Country
|
|
159
|
+
|
|
160
|
+
client = OlostepClient(api_key="YOUR_REAL_KEY")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# Minimal: Process a list of URLs
|
|
164
|
+
batch = await client.batch(["https://site1.com", "https://site2.com"])
|
|
165
|
+
# Batch(id='batch_123', urls=2)
|
|
166
|
+
|
|
167
|
+
# Maximal: Advanced batch with custom IDs and options
|
|
168
|
+
batch = await client.batch(
|
|
169
|
+
[
|
|
170
|
+
BatchItem(url="https://www.google.com/search?q=olostep"),
|
|
171
|
+
BatchItem(url="https://www.google.com/search?q=olostep+api", custom_id="news_2")
|
|
172
|
+
],
|
|
173
|
+
country=Country.US,
|
|
174
|
+
parser_id="@olostep/google-search"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# This is optional but you can check on the process of your batch at any time with:
|
|
178
|
+
info = await batch.info()
|
|
179
|
+
# -> BatchInfo(id='batch_123', status='in_progress', completed=1/2, age=2h ago)
|
|
180
|
+
|
|
181
|
+
# Also optional: Wait for completion.
|
|
182
|
+
# Pass in `check_every_n_secs=` to change interval, default 10
|
|
183
|
+
await batch.wait_till_done()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# Note: batch.items() automatically checks if the batch is completed before starting to return elements (can be disabled by passing in `wait_for_completion=False`)
|
|
187
|
+
async for item in batch.items(batch_size=10):
|
|
188
|
+
content = await item.retrieve(["html", "json"]) # json from the parser
|
|
189
|
+
print(f"{item.custom_id}: {len(content.html_content)} bytes")
|
|
190
|
+
|
|
191
|
+
# Alternative: Direct API access (stateless)
|
|
192
|
+
async for item in client.batch.items(batch_id='a_batch_id', batch_size=10):
|
|
193
|
+
content = await item.retrieve(["html", "json"])
|
|
194
|
+
print(f"{item.custom_id}: {len(content.html_content)} bytes")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### Web Crawling
|
|
198
|
+
```python
|
|
199
|
+
# Minimal: Crawl a site with default settings
|
|
200
|
+
crawl = await client.crawl("https://example.com", max_pages=100)
|
|
201
|
+
# Crawl(id='crawl_123', urls=100)
|
|
202
|
+
|
|
203
|
+
# Maximal: Advanced crawling with filters and limits
|
|
204
|
+
crawl = await client.crawl(
|
|
205
|
+
"https://example.com",
|
|
206
|
+
max_pages=1000,
|
|
207
|
+
max_depth=3,
|
|
208
|
+
include_urls=["/articles/**", "/news/**"],
|
|
209
|
+
exclude_urls=["/ads/**", "/tracking/**"],
|
|
210
|
+
include_external=False,
|
|
211
|
+
include_subdomain=True,
|
|
212
|
+
search_query="hot shingles",
|
|
213
|
+
top_n=50
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# This is optional but you can check on the process of your crawl at any time with:
|
|
217
|
+
info = await crawl.info() # CrawlInfo(id='crawl_123', status='in_progress', pages_count=42, age=15m ago)
|
|
218
|
+
|
|
219
|
+
# Also optional: Wait for completion.
|
|
220
|
+
# Pass in `check_every_n_secs=` to change interval, default 10
|
|
221
|
+
await crawl.wait_till_done()
|
|
222
|
+
|
|
223
|
+
# Note: crawl.pages automatically checks if the batch is completed before starting to return elements (can be disabled by passing in `wait_for_completion=False`)
|
|
224
|
+
async for page in crawl.pages():
|
|
225
|
+
content = await page.retrieve(["html"])
|
|
226
|
+
print(f"{page.url}: {len(content.html_content)} bytes")
|
|
227
|
+
|
|
228
|
+
# Alternative: Direct API access (stateless)
|
|
229
|
+
async for page in client.crawl.pages(crawl_id='a_crawl_id'):
|
|
230
|
+
content = await page.retrieve(["html"])
|
|
231
|
+
print(f"{page.url}: {len(content.html_content)} bytes")
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### Site Mapping
|
|
236
|
+
```python
|
|
237
|
+
# Minimal: Extract all links from a site
|
|
238
|
+
sitemap = await client.sitemap("https://example.com")
|
|
239
|
+
# Sitemap(id='map_123', urls_count=150, has_more=True)
|
|
240
|
+
|
|
241
|
+
# Maximal: Advanced link extraction with filters
|
|
242
|
+
sitemap = await client.sitemap(
|
|
243
|
+
"https://example.com",
|
|
244
|
+
search_query="documentation",
|
|
245
|
+
top_n=500,
|
|
246
|
+
include_subdomain=True,
|
|
247
|
+
include_urls=["/docs/**", "/api/**"],
|
|
248
|
+
exclude_urls=["/admin/**", "/private/**"]
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Seamless iteration over all URLs (auto-pagination)
|
|
252
|
+
all_urls = []
|
|
253
|
+
async for url in sitemap.urls(): # async generator
|
|
254
|
+
print(f"Found URL: {url}")
|
|
255
|
+
all_urls.append(url)
|
|
256
|
+
# Note: This can yield tens of thousands of URLs. If you can don't
|
|
257
|
+
# create a list but use the generator as such.
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### Data Retrieval
|
|
263
|
+
```python
|
|
264
|
+
# Notes:
|
|
265
|
+
# * You should generally not need to use this endpoint as the other endpoints generate stateful return objects that can retrieve content.
|
|
266
|
+
# * Not all formats are available all the time
|
|
267
|
+
|
|
268
|
+
# Minimal: Get content by retrieve ID
|
|
269
|
+
result = await client.retrieve("ret_123")
|
|
270
|
+
# ScrapeResult(id='ret_123', available=[...])
|
|
271
|
+
|
|
272
|
+
# Maximal: Get multiple formats
|
|
273
|
+
result = await client.retrieve("ret_123", ["html", "markdown", "text", "json"])
|
|
274
|
+
# ScrapeResult(id='ret_123', available=['html_content', 'markdown_content', 'text_content', 'json_content'])
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
### Method Shorthands
|
|
282
|
+
|
|
283
|
+
The SDK provides convenient shorthand methods for common operations:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
# These are equivalent:
|
|
287
|
+
await client.scrape("https://example.com") # shorthand
|
|
288
|
+
await client.scrape.create("https://example.com") # explicit method
|
|
289
|
+
|
|
290
|
+
await client.batch(["url1", "url2"]) # shorthand
|
|
291
|
+
await client.batch.start(["url1", "url2"]) # explicit method
|
|
292
|
+
|
|
293
|
+
await client.crawl("https://example.com") # shorthand
|
|
294
|
+
await client.crawl.start("https://example.com") # explicit method
|
|
295
|
+
|
|
296
|
+
await client.sitemap("https://example.com") # shorthand
|
|
297
|
+
await client.sitemap.create("https://example.com") # explicit method
|
|
298
|
+
|
|
299
|
+
await client.retrieve("ret_123") # shorthand
|
|
300
|
+
await client.retrieve.get("ret_123") # explicit method
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Smart Input Coercion
|
|
304
|
+
|
|
305
|
+
The SDK intelligently handles various input formats for maximum convenience:
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
# Formats: string, list, or enum
|
|
309
|
+
await client.scrape("https://example.com", formats="html")
|
|
310
|
+
await client.scrape("https://example.com", formats=["html", "markdown"])
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# Countries: case-insensitive strings or enums
|
|
314
|
+
await client.scrape("https://example.com", country="us")
|
|
315
|
+
await client.scrape("https://example.com", country=Country.US)
|
|
316
|
+
|
|
317
|
+
# Lists: single values or lists
|
|
318
|
+
await client.batch("https://example.com") # Single URL
|
|
319
|
+
await client.batch(["https://a.com", "https://b.com"]) # Multiple URLs
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
### Logging & Error Handling
|
|
325
|
+
|
|
326
|
+
The SDK root logger is predictably called `olostep`. Granular sub-loggers exist.
|
|
327
|
+
The recommended log level is `INFO` or higher.
|
|
328
|
+
`DEBUG` really means debug and is very detailed.
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
Handling the error behavior of the API can quite complex. The SDK handles error detection for you and returns the following errors:
|
|
332
|
+
|
|
333
|
+
Our exception hierarchy:
|
|
334
|
+
```
|
|
335
|
+
* Olostep_BaseError -------------------------------------- <- Catch base class for all errors
|
|
336
|
+
x Olostep_APIConnectionError --------------------------- <- No connection to the API
|
|
337
|
+
x OlostepServerError_BaseError ------------------------- <- Server-issued errors (still detected in client ofc)
|
|
338
|
+
+ OlostepServerError_TemporaryIssue
|
|
339
|
+
- OlostepServerError_NetworkBusy
|
|
340
|
+
- OlostepServerError_InternalNetworkIssue
|
|
341
|
+
+ OlostepServerError_RequestUnprocessable
|
|
342
|
+
- OlostepServerError_ParserNotFound
|
|
343
|
+
- OlostepServerError_OutOfResources
|
|
344
|
+
+ OlostepServerError_BlacklistedDomain
|
|
345
|
+
+ OlostepServerError_FeatureApprovalRequired
|
|
346
|
+
+ OlostepServerError_AuthFailed
|
|
347
|
+
+ OlostepServerError_CreditsExhausted
|
|
348
|
+
+ OlostepServerError_InvalidEndpointCalled
|
|
349
|
+
+ OlostepServerError_ResourceNotFound
|
|
350
|
+
+ OlostepServerError_NoResultInResponse
|
|
351
|
+
+ OlostepServerError_UnknownIssue
|
|
352
|
+
x OlostepClientError_BaseError ------------------------- <- Client-issued errors
|
|
353
|
+
+ OlostepClientError_RequestValidationFailed
|
|
354
|
+
+ OlostepClientError_ResponseValidationFailed
|
|
355
|
+
+ OlostepClientError_NoAPIKey
|
|
356
|
+
+ OlostepClientError_AsyncContext
|
|
357
|
+
+ OlostepClientError_BetaFeatureAccessRequired
|
|
358
|
+
+ OlostepClientError_Timeout
|
|
359
|
+
|
|
360
|
+
```
|