silk-scraper 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.
- silk_scraper-0.1.0/LICENSE +19 -0
- silk_scraper-0.1.0/PKG-INFO +647 -0
- silk_scraper-0.1.0/README.md +615 -0
- silk_scraper-0.1.0/pyproject.toml +69 -0
- silk_scraper-0.1.0/src/silk/__init__.py +14 -0
- silk_scraper-0.1.0/src/silk/actions/__init__.py +48 -0
- silk_scraper-0.1.0/src/silk/actions/base.py +419 -0
- silk_scraper-0.1.0/src/silk/actions/composition.py +347 -0
- silk_scraper-0.1.0/src/silk/actions/context.py +268 -0
- silk_scraper-0.1.0/src/silk/actions/decorators.py +246 -0
- silk_scraper-0.1.0/src/silk/actions/extraction.py +654 -0
- silk_scraper-0.1.0/src/silk/actions/flow.py +458 -0
- silk_scraper-0.1.0/src/silk/actions/input.py +827 -0
- silk_scraper-0.1.0/src/silk/actions/navigation.py +470 -0
- silk_scraper-0.1.0/src/silk/actions/py.typed +0 -0
- silk_scraper-0.1.0/src/silk/browsers/__init__.py +18 -0
- silk_scraper-0.1.0/src/silk/browsers/context.py +310 -0
- silk_scraper-0.1.0/src/silk/browsers/driver.py +716 -0
- silk_scraper-0.1.0/src/silk/browsers/driver_factory.py +74 -0
- silk_scraper-0.1.0/src/silk/browsers/drivers/playwright.py +1378 -0
- silk_scraper-0.1.0/src/silk/browsers/element.py +433 -0
- silk_scraper-0.1.0/src/silk/browsers/manager.py +262 -0
- silk_scraper-0.1.0/src/silk/browsers/py.typed +0 -0
- silk_scraper-0.1.0/src/silk/models/actions.py +0 -0
- silk_scraper-0.1.0/src/silk/models/browser.py +252 -0
- silk_scraper-0.1.0/src/silk/py.typed +0 -0
- silk_scraper-0.1.0/src/silk/selectors/__init__.py +7 -0
- silk_scraper-0.1.0/src/silk/selectors/py.typed +0 -0
- silk_scraper-0.1.0/src/silk/selectors/selector.py +143 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2025 Galad Dirie
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
14
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
15
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
16
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
17
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
18
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
|
19
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: silk-scraper
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A flexible browser automation library with support for multiple drivers
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Galad Dirie
|
|
7
|
+
Author-email: hello@galad.ca
|
|
8
|
+
Requires-Python: >=3.11,<4.0
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
|
|
18
|
+
Provides-Extra: all
|
|
19
|
+
Provides-Extra: patchright
|
|
20
|
+
Provides-Extra: playwright
|
|
21
|
+
Requires-Dist: expression (>=5.6.0,<6.0.0)
|
|
22
|
+
Requires-Dist: patchright (>=1.51.0,<2.0.0) ; extra == "all"
|
|
23
|
+
Requires-Dist: patchright (>=1.51.0,<2.0.0) ; extra == "patchright"
|
|
24
|
+
Requires-Dist: playwright (>=1.51.0,<2.0.0)
|
|
25
|
+
Requires-Dist: playwright (>=1.51.0,<2.0.0) ; extra == "all"
|
|
26
|
+
Requires-Dist: playwright (>=1.51.0,<2.0.0) ; extra == "playwright"
|
|
27
|
+
Requires-Dist: pydantic (>=2.11.2,<3.0.0)
|
|
28
|
+
Requires-Dist: pydantic-settings (>=2.8.1,<3.0.0)
|
|
29
|
+
Project-URL: Bug Tracker, https://github.com/galdirie/silk-py/issues
|
|
30
|
+
Project-URL: Homepage, https://github.com/galdirie/silk-py
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# Silk
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/silk-scraper/)
|
|
36
|
+
[](https://pypi.org/project/silk-scraper/)
|
|
37
|
+
[](LICENSE)
|
|
38
|
+
[](https://github.com/psf/black)
|
|
39
|
+
[](https://github.com/python/mypy)
|
|
40
|
+
|
|
41
|
+
**Silk** is a functional web scraping framework for Python that reimagines how web automation should work. Built around composable "Actions" and the [Expression](https://github.com/dbrattli/Expression) library, Silk enables you to write elegant, maintainable, and resilient web scrapers with true functional programming patterns.
|
|
42
|
+
|
|
43
|
+
Unlike traditional scraping libraries, Silk embraces Railway-Oriented Programming for robust error handling, uses immutable data structures for predictability, and provides an expressive, composable API that makes even complex scraping workflows readable and maintainable.
|
|
44
|
+
|
|
45
|
+
## Why Silk?
|
|
46
|
+
|
|
47
|
+
Traditional web scraping approaches in Python often lead to complex, brittle code that's difficult to maintain. Silk solves these common challenges:
|
|
48
|
+
|
|
49
|
+
- **No More Callback Hell**: Replace nested try/except blocks with elegant Railway-Oriented Programming
|
|
50
|
+
- **Resilient Scraping**: Built-in retry mechanisms, fallback selectors, and error recovery
|
|
51
|
+
- **Composable Actions**: Chain operations with intuitive operators (`>>`, `&`, `|`) for cleaner code
|
|
52
|
+
- **Type-Safe**: Full typing support with Mypy and Pydantic for fewer runtime errors
|
|
53
|
+
- **Browser Agnostic**: Same API for Playwright, Selenium, or any other browser automation tool
|
|
54
|
+
- **Parallelization Made Easy**: Run operations concurrently with the `&` operator
|
|
55
|
+
|
|
56
|
+
Whether you're building a small data collection script or a large-scale scraping system, Silk's functional approach scales with your needs while keeping your codebase clean and maintainable.
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
- **Purely Functional Design**: Built on Expression library for robust functional programming in Python
|
|
61
|
+
- **Immutable Data Structures**: Uses immutable collections for thread-safety and predictability
|
|
62
|
+
- **Railway-Oriented Programming**: Elegant error handling with Result types
|
|
63
|
+
- **Functional & Composable API**: Build pipelines with intuitive operators (`>>`, `&`, `|`)
|
|
64
|
+
- **Browser Abstraction**: Works with Playwright, Selenium, or any other browser automation tool
|
|
65
|
+
- **Resilient Selectors**: Fallback mechanisms to handle changing website structures
|
|
66
|
+
- **Type Safety**: Leverages Pydantic, Mypy and Python's type hints for static type checking
|
|
67
|
+
- **Parallel Execution**: Easy concurrent scraping with functional composition
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
You can install Silk with your preferred browser driver:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Base installation (no drivers)
|
|
75
|
+
pip install silk-scraper
|
|
76
|
+
|
|
77
|
+
# With Playwright support
|
|
78
|
+
pip install silk-scraper[playwright]
|
|
79
|
+
|
|
80
|
+
# With Selenium support
|
|
81
|
+
pip install silk-scraper[selenium]
|
|
82
|
+
|
|
83
|
+
# With Puppeteer support
|
|
84
|
+
pip install silk-scraper[puppeteer]
|
|
85
|
+
|
|
86
|
+
# With all drivers
|
|
87
|
+
pip install silk-scraper[all]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Quick Start
|
|
91
|
+
|
|
92
|
+
### Basic Example
|
|
93
|
+
|
|
94
|
+
Here's a minimal example to get you started with Silk:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
import asyncio
|
|
98
|
+
from silk.actions.navigation import Navigate
|
|
99
|
+
from silk.actions.extraction import GetText
|
|
100
|
+
from silk.browsers.manager import BrowserManager
|
|
101
|
+
|
|
102
|
+
async def main():
|
|
103
|
+
# Create a browser manager (defaults to Playwright)
|
|
104
|
+
async with BrowserManager() as manager:
|
|
105
|
+
# Define a simple scraping pipeline
|
|
106
|
+
pipeline = (
|
|
107
|
+
Navigate("https://example.com")
|
|
108
|
+
>> GetText("h1")
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Execute the pipeline
|
|
112
|
+
result = await manager.execute_action(pipeline)
|
|
113
|
+
|
|
114
|
+
if result.is_ok():
|
|
115
|
+
print(f"Page title: {result.unwrap()}")
|
|
116
|
+
else:
|
|
117
|
+
print(f"Error: {result.error}")
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
asyncio.run(main())
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Configuring the Browser
|
|
124
|
+
|
|
125
|
+
Silk supports different browser drivers. You can configure them like this:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from silk.models.browser import BrowserOptions
|
|
129
|
+
from silk.browsers.manager import BrowserManager
|
|
130
|
+
|
|
131
|
+
# Configure browser options
|
|
132
|
+
options = BrowserOptions(
|
|
133
|
+
headless=False, # Set to False to see the browser UI
|
|
134
|
+
browser_name="chromium", # Choose "chromium", "firefox", or "webkit"
|
|
135
|
+
slow_mo=50, # Slow down operations by 50ms (useful for debugging)
|
|
136
|
+
viewport={"width": 1280, "height": 800}
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Create a manager with specific driver and options
|
|
140
|
+
manager = BrowserManager(driver_type="playwright", default_options=options)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Creating Custom Actions
|
|
144
|
+
|
|
145
|
+
You can easily create your own actions for reusable scraping logic:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from silk.actions.base import Action
|
|
149
|
+
from silk.actions.decorators import action
|
|
150
|
+
from expression.core import Ok, Error
|
|
151
|
+
from silk.models.browser import ActionContext
|
|
152
|
+
|
|
153
|
+
@action()
|
|
154
|
+
async def extract_price(context, selector):
|
|
155
|
+
"""Extract and parse a price from the page"""
|
|
156
|
+
page_result = await context.get_page()
|
|
157
|
+
if page_result.is_error():
|
|
158
|
+
return page_result
|
|
159
|
+
|
|
160
|
+
page = page_result.unwrap()
|
|
161
|
+
element_result = await page.query_selector(selector)
|
|
162
|
+
|
|
163
|
+
if element_result.is_error():
|
|
164
|
+
return Error(f"Element not found: {selector}")
|
|
165
|
+
|
|
166
|
+
element = element_result.unwrap()
|
|
167
|
+
text_result = await element.get_text()
|
|
168
|
+
|
|
169
|
+
if text_result.is_error():
|
|
170
|
+
return text_result
|
|
171
|
+
|
|
172
|
+
text = text_result.unwrap()
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
# Remove currency symbol and convert to float
|
|
176
|
+
price = float(text.replace('$', '').strip())
|
|
177
|
+
return Ok(price)
|
|
178
|
+
except ValueError:
|
|
179
|
+
return Error(f"Failed to parse price from: {text}")
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Core Concepts
|
|
183
|
+
|
|
184
|
+
### Actions
|
|
185
|
+
|
|
186
|
+
The fundamental building block in Silk is the `Action`. An Action represents a pure operation that can be composed with other actions using functional programming patterns. Each Action takes an `ActionContext` and returns a `Result` containing either the operation's result or an error.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
class FindElement(Action[ElementHandle]):
|
|
190
|
+
"""Action to find an element on the page"""
|
|
191
|
+
|
|
192
|
+
def __init__(self, selector: str):
|
|
193
|
+
self.selector = selector
|
|
194
|
+
|
|
195
|
+
async def execute(self, context: ActionContext) -> Result[ElementHandle, Exception]:
|
|
196
|
+
try:
|
|
197
|
+
page_result = await context.get_page()
|
|
198
|
+
if page_result.is_error():
|
|
199
|
+
return page_result
|
|
200
|
+
|
|
201
|
+
page = page_result.unwrap()
|
|
202
|
+
return await page.query_selector(self.selector)
|
|
203
|
+
except Exception as e:
|
|
204
|
+
return Error(e)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### ActionContext
|
|
208
|
+
|
|
209
|
+
The `ActionContext` carries references to the browser, page, and other execution context information. Actions use this context to interact with the browser.
|
|
210
|
+
|
|
211
|
+
### Result Type
|
|
212
|
+
|
|
213
|
+
Silk uses the `Result[T, E]` type from the Expression library for error handling. Rather than relying on exceptions, actions return `Ok(value)` for success or `Error(exception)` for failures.
|
|
214
|
+
|
|
215
|
+
### Composition Operators
|
|
216
|
+
|
|
217
|
+
Silk provides powerful operators for composing actions:
|
|
218
|
+
|
|
219
|
+
- **`>>`** (then): Chain actions sequentially
|
|
220
|
+
- **`&`** (and): Run actions in parallel
|
|
221
|
+
- **`|`** (or): Try one action, fall back to another if it fails
|
|
222
|
+
|
|
223
|
+
These operators make it easy to build complex scraping workflows with clear, readable code.
|
|
224
|
+
|
|
225
|
+
## Detailed Examples
|
|
226
|
+
|
|
227
|
+
### Handling Complex Selectors
|
|
228
|
+
|
|
229
|
+
Silk provides robust ways to handle changing website structures with selector groups. Selector groups are a collection of selectors that are tried in order until one succeeds.
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
from silk.selectors.selector import SelectorGroup, css, xpath
|
|
233
|
+
|
|
234
|
+
# Create a selector group with fallback options
|
|
235
|
+
product_price = SelectorGroup.create(
|
|
236
|
+
"product_price",
|
|
237
|
+
css(".current-price"), # Try this first
|
|
238
|
+
css(".product-price .amount"), # Fall back to this
|
|
239
|
+
xpath("//div[contains(@class, 'price')]//span") # Last resort
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Use it in an extraction action
|
|
243
|
+
extract_price = GetText(product_price)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Resilient Scraping with Retry and Fallbacks
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
from silk.actions.flow import retry, fallback
|
|
250
|
+
from silk.actions.extraction import GetText
|
|
251
|
+
from silk.actions.navigation import Navigate
|
|
252
|
+
|
|
253
|
+
# Retry navigation up to 3 times with 2s delay
|
|
254
|
+
resilient_navigation = retry(
|
|
255
|
+
Navigate("https://example.com"),
|
|
256
|
+
max_attempts=3,
|
|
257
|
+
delay_ms=2000
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Try multiple selectors for extracting data
|
|
261
|
+
extract_title = fallback(
|
|
262
|
+
GetText(".main-title"),
|
|
263
|
+
GetText("h1.title"),
|
|
264
|
+
GetText("#product-name")
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Combine into a pipeline
|
|
268
|
+
pipeline = resilient_navigation >> extract_title
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Parallel Extraction
|
|
272
|
+
|
|
273
|
+
Extract multiple pieces of information at once:
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
from silk.actions.composition import parallel
|
|
277
|
+
from silk.actions.extraction import GetText, GetAttribute
|
|
278
|
+
|
|
279
|
+
# Extract product details in parallel
|
|
280
|
+
product_details = parallel(
|
|
281
|
+
GetText(".product-name"),
|
|
282
|
+
GetText(".product-price"),
|
|
283
|
+
GetAttribute(".product-image", "src"),
|
|
284
|
+
GetText(".product-description")
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Use in a pipeline
|
|
288
|
+
pipeline = Navigate(product_url) >> product_details
|
|
289
|
+
|
|
290
|
+
# Results come back as a Block collection
|
|
291
|
+
result = await manager.execute_action(pipeline)
|
|
292
|
+
if result.is_ok():
|
|
293
|
+
[name, price, image_url, description] = result.unwrap()
|
|
294
|
+
print(f"Product: {name}, Price: {price}")
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Form Filling and Submission
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
from silk.actions.input import Fill, Click
|
|
301
|
+
from silk.actions.flow import compose
|
|
302
|
+
|
|
303
|
+
login_action = compose(
|
|
304
|
+
Navigate("https://example.com/login"),
|
|
305
|
+
Fill("#username", "user@example.com"),
|
|
306
|
+
Fill("#password", "password123"),
|
|
307
|
+
Click("button[type='submit']")
|
|
308
|
+
)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Handling Dynamic Content
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
from silk.actions.flow import wait, loop_until
|
|
315
|
+
from silk.actions.conditions import ElementExists
|
|
316
|
+
|
|
317
|
+
# Wait for dynamic content to load
|
|
318
|
+
wait_for_results = wait(1000) >> ElementExists(".search-results-item")
|
|
319
|
+
|
|
320
|
+
# Loop until a condition is met
|
|
321
|
+
load_all_results = loop_until(
|
|
322
|
+
condition=ElementExists(".no-more-results"),
|
|
323
|
+
body=Click(".load-more-button"),
|
|
324
|
+
max_iterations=10,
|
|
325
|
+
delay_ms=1000
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Use in a pipeline
|
|
329
|
+
search_pipeline = (
|
|
330
|
+
Navigate("https://example.com/search?q=example")
|
|
331
|
+
>> wait_for_results
|
|
332
|
+
>> load_all_results
|
|
333
|
+
>> GetText(".search-results-count")
|
|
334
|
+
)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Action Decorator for Custom Functions
|
|
338
|
+
|
|
339
|
+
Easily convert any function into a composable Action using the `@action` decorator:
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
from silk import action, Ok, Error
|
|
343
|
+
|
|
344
|
+
@action
|
|
345
|
+
async def scroll_to_element(driver, selector, smooth=True):
|
|
346
|
+
"""Scrolls the page to bring the element into view"""
|
|
347
|
+
try:
|
|
348
|
+
element = await driver.query_selector(selector)
|
|
349
|
+
await element.scroll_into_view({"behavior": "smooth" if smooth else "auto"})
|
|
350
|
+
return "Element scrolled into view"
|
|
351
|
+
except Exception as e:
|
|
352
|
+
raise e
|
|
353
|
+
|
|
354
|
+
# Use it in a pipeline - the function is now a composable Action!
|
|
355
|
+
pipeline = (
|
|
356
|
+
Navigate(url)
|
|
357
|
+
>> scroll_to_element("#my-element")
|
|
358
|
+
>> extract_text("#my-element")
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
result = await pipeline(browser)
|
|
362
|
+
if result.is_ok():
|
|
363
|
+
print(f"Extracted text after scrolling: {result.unwrap()}")
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Composable Operations
|
|
367
|
+
|
|
368
|
+
Silk provides intuitive operators for composable scraping:
|
|
369
|
+
|
|
370
|
+
### Sequential Operations (`>>`)
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
# Navigate to a page, then extract the title
|
|
374
|
+
Navigate(url) >> Click(title_selector)
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Parallel Operations (`&`)
|
|
378
|
+
|
|
379
|
+
```python
|
|
380
|
+
# Extract name, price, and description in parallel
|
|
381
|
+
# Each action is executed in a new context when using the & operator
|
|
382
|
+
Navigate(url) & Navigate(url2) & Navigate(url3)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
```python
|
|
386
|
+
# Combining parallel and sequential operations
|
|
387
|
+
# Each parallel branch can contain its own chain of sequential actions
|
|
388
|
+
(
|
|
389
|
+
# First website: Get product details
|
|
390
|
+
(Navigate("https://site1.com/product")
|
|
391
|
+
>> Wait(1000)
|
|
392
|
+
>> GetText(".product-name"))
|
|
393
|
+
&
|
|
394
|
+
# Second website: Search and extract first result
|
|
395
|
+
(Navigate("https://site2.com")
|
|
396
|
+
>> Fill("#search-input", "smartphone")
|
|
397
|
+
>> Click("#search-button")
|
|
398
|
+
>> Wait(2000)
|
|
399
|
+
>> GetText(".first-result .name"))
|
|
400
|
+
&
|
|
401
|
+
# Third website: Login and get account info
|
|
402
|
+
(Navigate("https://site3.com/login")
|
|
403
|
+
>> Fill("#username", "user@example.com")
|
|
404
|
+
>> Fill("#password", "password123")
|
|
405
|
+
>> Click(".login-button")
|
|
406
|
+
>> Wait(1500)
|
|
407
|
+
>> GetText(".account-info"))
|
|
408
|
+
)
|
|
409
|
+
# Results are collected as a Block of 3 items, one from each parallel branch
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Fallback Operations (`|`)
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
# Try to extract with one selector, fall back to another if it fails
|
|
416
|
+
GetText(primary_selector) | GetText(fallback_selector)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## API Reference
|
|
420
|
+
|
|
421
|
+
### Core Modules
|
|
422
|
+
|
|
423
|
+
- **`silk.actions`**: Core action classes for browser automation
|
|
424
|
+
- **`silk.actions.base`**: Base Action class and core utilities
|
|
425
|
+
- **`silk.actions.navigation`**: Actions for navigating between pages
|
|
426
|
+
- **`silk.actions.extraction`**: Actions for extracting data from pages
|
|
427
|
+
- **`silk.actions.input`**: Actions for interacting with forms and elements
|
|
428
|
+
- **`silk.actions.flow`**: Control flow actions like branch, retry, and loop
|
|
429
|
+
- **`silk.actions.composition`**: Utilities for composing actions (sequence, parallel, pipe)
|
|
430
|
+
- **`silk.actions.decorators`**: Decorators like @action for creating custom actions
|
|
431
|
+
|
|
432
|
+
- **`silk.browsers`**: Browser management and abstraction layer
|
|
433
|
+
- **`silk.browsers.manager`**: BrowserManager for session handling
|
|
434
|
+
- **`silk.browsers.driver`**: Abstract BrowserDriver interface
|
|
435
|
+
- **`silk.browsers.element`**: ElementHandle for working with DOM elements
|
|
436
|
+
|
|
437
|
+
- **`silk.selectors`**: Selector utilities
|
|
438
|
+
- **`silk.selectors.selector`**: Selector and SelectorGroup classes
|
|
439
|
+
|
|
440
|
+
- **`silk.models`**: Data models using Pydantic
|
|
441
|
+
- **`silk.models.browser`**: BrowserOptions, ActionContext, etc.
|
|
442
|
+
|
|
443
|
+
### Common Action Classes
|
|
444
|
+
|
|
445
|
+
- **Navigation**
|
|
446
|
+
- `Navigate(url)`: Navigate to a URL
|
|
447
|
+
- `Reload()`: Reload the current page
|
|
448
|
+
- `GoBack()`: Navigate back in history
|
|
449
|
+
- `GoForward()`: Navigate forward in history
|
|
450
|
+
|
|
451
|
+
- **Extraction**
|
|
452
|
+
- `Query(selector)`: Find an element
|
|
453
|
+
- `QueryAll(selector)`: Find all matching elements
|
|
454
|
+
- `GetText(selector)`: Extract text from an element
|
|
455
|
+
- `GetAttribute(selector, attribute)`: Get an attribute value
|
|
456
|
+
- `GetHtml(selector, outer=True)`: Get element HTML
|
|
457
|
+
- `ExtractTable(table_selector)`: Extract data from an HTML table
|
|
458
|
+
|
|
459
|
+
- **Input**
|
|
460
|
+
- `Click(target)`: Click an element
|
|
461
|
+
- `DoubleClick(target)`: Double-click an element
|
|
462
|
+
- `Fill(target, text)`: Fill a form field
|
|
463
|
+
- `Type(target, text)`: Type text (alias for Fill)
|
|
464
|
+
- `Select(target, value/text)`: Select an option from a dropdown
|
|
465
|
+
- `MouseMove(target)`: Move the mouse to an element
|
|
466
|
+
- `KeyPress(key, modifiers)`: Press a key or key combination
|
|
467
|
+
|
|
468
|
+
- **Flow Control**
|
|
469
|
+
- `branch(condition, if_true, if_false)`: Conditional branching
|
|
470
|
+
- `loop_until(condition, body, max_iterations)`: Loop until condition is met
|
|
471
|
+
- `retry(action, max_attempts, delay_ms)`: Retry an action on failure
|
|
472
|
+
- `retry_with_backoff(action)`: Retry with exponential backoff
|
|
473
|
+
- `with_timeout(action, timeout_ms)`: Apply a timeout to an action
|
|
474
|
+
|
|
475
|
+
- **Composition**
|
|
476
|
+
- `sequence(*actions)`: Run actions in sequence, collect all results
|
|
477
|
+
- `parallel(*actions)`: Run actions in parallel, collect all results
|
|
478
|
+
- `pipe(*actions)`: Create a pipeline where each action uses the previous result
|
|
479
|
+
- `fallback(*actions)`: Try actions in sequence until one succeeds
|
|
480
|
+
- `compose(*actions)`: Compose actions sequentially, return only the last result
|
|
481
|
+
|
|
482
|
+
For a complete API reference, please see the [API documentation](https://silk-docs.example.com).
|
|
483
|
+
|
|
484
|
+
## Best Practices
|
|
485
|
+
|
|
486
|
+
### Error Handling
|
|
487
|
+
|
|
488
|
+
Silk uses Railway-Oriented Programming for error handling. Instead of using try/except, leverage the Result type:
|
|
489
|
+
|
|
490
|
+
```python
|
|
491
|
+
result = await manager.execute_action(pipeline)
|
|
492
|
+
if result.is_ok():
|
|
493
|
+
data = result.unwrap()
|
|
494
|
+
# Process the data
|
|
495
|
+
else:
|
|
496
|
+
# Handle the error
|
|
497
|
+
error = result.error
|
|
498
|
+
logger.error(f"Scraping failed: {error}")
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Browser Resources
|
|
502
|
+
|
|
503
|
+
Always use context managers to ensure browser resources are properly cleaned up:
|
|
504
|
+
|
|
505
|
+
```python
|
|
506
|
+
async with BrowserManager() as manager:
|
|
507
|
+
# Your scraping code here
|
|
508
|
+
pass # Resources automatically cleaned up
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Selector Resilience
|
|
512
|
+
|
|
513
|
+
Use selector groups for resilient scraping that can handle UI changes:
|
|
514
|
+
|
|
515
|
+
```python
|
|
516
|
+
# Instead of a single brittle selector:
|
|
517
|
+
extract_price = GetText(".price-box .price")
|
|
518
|
+
|
|
519
|
+
# Use a group with fallbacks:
|
|
520
|
+
price_selector = SelectorGroup.create(
|
|
521
|
+
"price",
|
|
522
|
+
css(".price-box .price"),
|
|
523
|
+
css(".product-price"),
|
|
524
|
+
xpath("//span[contains(@class, 'price')]")
|
|
525
|
+
)
|
|
526
|
+
extract_price = GetText(price_selector)
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Action Composition
|
|
530
|
+
|
|
531
|
+
Build reusable pipelines through composition instead of large monolithic functions:
|
|
532
|
+
|
|
533
|
+
```python
|
|
534
|
+
# Define reusable components
|
|
535
|
+
navigate_to_product = Navigate("https://example.com/product")
|
|
536
|
+
extract_product_info = parallel(
|
|
537
|
+
GetText(".product-name"),
|
|
538
|
+
GetText(".product-price"),
|
|
539
|
+
GetText(".product-description")
|
|
540
|
+
)
|
|
541
|
+
extract_related_products = QueryAll(".related-product") >> extract_text_from_elements
|
|
542
|
+
|
|
543
|
+
# Compose them in different ways
|
|
544
|
+
full_scraper = navigate_to_product >> extract_product_info >> extract_related_products
|
|
545
|
+
minimal_scraper = navigate_to_product >> extract_product_info
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### Logging
|
|
549
|
+
|
|
550
|
+
Enable logging to better debug your scraping pipelines:
|
|
551
|
+
|
|
552
|
+
```python
|
|
553
|
+
import logging
|
|
554
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
555
|
+
logging.getLogger("silk").setLevel(logging.DEBUG)
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## Contributing
|
|
559
|
+
|
|
560
|
+
Contributions to Silk are welcome! Please feel free to submit a Pull Request.
|
|
561
|
+
|
|
562
|
+
### Development Setup
|
|
563
|
+
|
|
564
|
+
1. Clone the repository
|
|
565
|
+
```bash
|
|
566
|
+
git clone https://github.com/yourusername/silk.git
|
|
567
|
+
cd silk
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
2. Create a virtual environment
|
|
571
|
+
```bash
|
|
572
|
+
python -m venv venv
|
|
573
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
3. Install development dependencies
|
|
577
|
+
```bash
|
|
578
|
+
pip install -e ".[dev,test,all]"
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
4. Run tests
|
|
582
|
+
```bash
|
|
583
|
+
pytest
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Guidelines
|
|
587
|
+
|
|
588
|
+
- Follow PEP 8 and use Black for code formatting
|
|
589
|
+
- Write tests for new features
|
|
590
|
+
- Keep the functional programming paradigm in mind
|
|
591
|
+
- Update documentation with new features
|
|
592
|
+
|
|
593
|
+
## Acknowledgements
|
|
594
|
+
|
|
595
|
+
Silk builds upon several excellent libraries:
|
|
596
|
+
- [Expression](https://github.com/dbrattli/Expression) for functional programming patterns in Python
|
|
597
|
+
- [Playwright](https://playwright.dev/) and [Selenium](https://www.selenium.dev/) for browser automation
|
|
598
|
+
- [Pydantic](https://pydantic-docs.helpmanual.io/) for data validation and settings management
|
|
599
|
+
|
|
600
|
+
## Roadmap
|
|
601
|
+
|
|
602
|
+
- [x] Initial release with Playwright support
|
|
603
|
+
- [x] Selenium integration
|
|
604
|
+
- [ ] Puppeteer integration
|
|
605
|
+
- [ ] Data extraction DSL for declarative scraping
|
|
606
|
+
- [ ] Enhanced caching mechanisms
|
|
607
|
+
- [ ] Distributed scraping support
|
|
608
|
+
- [ ] Rate limiting and polite scraping utilities
|
|
609
|
+
- [ ] Integration with popular data processing libraries (Pandas, etc.)
|
|
610
|
+
- [ ] CLI tool for quick scraping tasks
|
|
611
|
+
|
|
612
|
+
## FAQ
|
|
613
|
+
|
|
614
|
+
### How does Silk compare to other scraping libraries?
|
|
615
|
+
|
|
616
|
+
Silk differs from traditional scraping libraries like Scrapy, Beautiful Soup, or plain Selenium/Playwright in its functional approach. While these tools focus on imperative code with callbacks and exceptions, Silk embraces functional composition, immutable data structures, and Railway-Oriented Programming for cleaner, more maintainable code.
|
|
617
|
+
|
|
618
|
+
### Can I use Silk with my existing Playwright/Selenium code?
|
|
619
|
+
|
|
620
|
+
Yes, Silk is designed to work alongside existing browser automation code. You can gradually adopt Silk's patterns while keeping your existing code.
|
|
621
|
+
|
|
622
|
+
### Is Silk suitable for large-scale scraping?
|
|
623
|
+
|
|
624
|
+
Absolutely. Silk's composable nature makes it excellent for large-scale scraping projects. Its built-in error handling, retries, and parallel execution capabilities are particularly valuable for robust production systems.
|
|
625
|
+
|
|
626
|
+
### How can I handle authentication in Silk?
|
|
627
|
+
|
|
628
|
+
You can handle authentication like any other browser interaction:
|
|
629
|
+
|
|
630
|
+
```python
|
|
631
|
+
login_action = compose(
|
|
632
|
+
Navigate("https://example.com/login"),
|
|
633
|
+
Fill("#username", "user@example.com"),
|
|
634
|
+
Fill("#password", "password123"),
|
|
635
|
+
Click("button[type='submit']"),
|
|
636
|
+
wait(1000) # Wait for login to complete
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
# Then use the authenticated context for further actions
|
|
640
|
+
pipeline = login_action >> Navigate("https://example.com/protected-content") >> GetText("#protected-data")
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
You can also save and reuse authentication state with browser context options.
|
|
644
|
+
|
|
645
|
+
## License
|
|
646
|
+
|
|
647
|
+
Silk is released under the MIT License. See the [LICENSE](LICENSE) file for details.
|