scrapeflow-py 0.3.0__tar.gz → 0.4.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.
- scrapeflow_py-0.4.0/CHANGELOG.md +53 -0
- scrapeflow_py-0.4.0/MANIFEST.in +5 -0
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/PKG-INFO +73 -31
- scrapeflow_py-0.3.0/scrapeflow_py.egg-info/PKG-INFO → scrapeflow_py-0.4.0/README.md +43 -47
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/examples/__init__.py +0 -1
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/examples/advanced_example.py +5 -6
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/examples/authenticated_quotes_example.py +2 -1
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/examples/basic_usage.py +2 -1
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/examples/hybrid_extraction_example.py +8 -5
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/examples/llm_extraction_example.py +5 -8
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/examples/session_persistence_example.py +9 -3
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/examples/specification_driven_example.py +6 -4
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/examples/workflow_example.py +4 -4
- scrapeflow_py-0.4.0/pyproject.toml +107 -0
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/__init__.py +36 -30
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/anti_detection.py +18 -31
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/browser_runtime.py +9 -9
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/config.py +13 -19
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/content_utils.py +0 -1
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/engine.py +41 -40
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/exceptions.py +0 -1
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/extractors.py +19 -29
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/llm_extract.py +24 -27
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/mcp_backend.py +36 -26
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/monitoring.py +13 -14
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/pagination.py +8 -3
- scrapeflow_py-0.4.0/scrapeflow/ports.py +57 -0
- scrapeflow_py-0.4.0/scrapeflow/py.typed +0 -0
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/rate_limiter.py +4 -9
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/registry.py +20 -20
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/retry.py +9 -19
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/robots.py +1 -4
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/specifications.py +34 -33
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/workflow.py +18 -18
- scrapeflow_py-0.3.0/README.md → scrapeflow_py-0.4.0/scrapeflow_py.egg-info/PKG-INFO +90 -12
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow_py.egg-info/SOURCES.txt +3 -3
- scrapeflow_py-0.4.0/scrapeflow_py.egg-info/requires.txt +15 -0
- scrapeflow_py-0.4.0/scrapeflow_py.egg-info/top_level.txt +1 -0
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/tests/test_config.py +5 -7
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/tests/test_content_utils.py +0 -1
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/tests/test_hybrid_extractor.py +9 -6
- scrapeflow_py-0.4.0/tests/test_release.py +207 -0
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/tests/test_robots.py +1 -0
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/tests/test_specifications.py +1 -5
- scrapeflow_py-0.3.0/MANIFEST.in +0 -4
- scrapeflow_py-0.3.0/pyproject.toml +0 -16
- scrapeflow_py-0.3.0/requirements.txt +0 -7
- scrapeflow_py-0.3.0/scrapeflow/ports.py +0 -71
- scrapeflow_py-0.3.0/scrapeflow_py.egg-info/requires.txt +0 -6
- scrapeflow_py-0.3.0/scrapeflow_py.egg-info/top_level.txt +0 -3
- scrapeflow_py-0.3.0/setup.py +0 -38
- scrapeflow_py-0.3.0/tests/__init__.py +0 -2
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/LICENSE +0 -0
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/schema_library.py +0 -0
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow/workflow_executor.py +0 -0
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/scrapeflow_py.egg-info/dependency_links.txt +0 -0
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/setup.cfg +0 -0
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/tests/test_engine_orchestration.py +0 -0
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/tests/test_public_api_compat.py +0 -0
- {scrapeflow_py-0.3.0 → scrapeflow_py-0.4.0}/tests/test_registry.py +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.4.0] - 2026-10-31
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- LLM extraction works with current `mistralai` releases (2.x/3.x moved the client to
|
|
14
|
+
`mistralai.client.Mistral`). Previously every LLM call failed with a misleading
|
|
15
|
+
"mistralai is required" `ImportError` even though it was installed.
|
|
16
|
+
- The wheel no longer installs stray top-level `tests` and `examples` packages into
|
|
17
|
+
`site-packages`.
|
|
18
|
+
- `create_mcp_backend()` no longer silently returns a do-nothing backend: unknown backend names
|
|
19
|
+
raise `ValueError`, and the not-yet-implemented `scrapy_mcp` / `playwright_mcp` backends emit a
|
|
20
|
+
`UserWarning`.
|
|
21
|
+
- Step timeouts chain the original `asyncio.TimeoutError` (`raise ... from`).
|
|
22
|
+
- Dockerfile: browsers are installed where `PLAYWRIGHT_BROWSERS_PATH` points, and `pytest` is
|
|
23
|
+
available for the default `CMD` (the image previously could not run its own default command).
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- `py.typed` marker (PEP 561) and `Typing :: Typed` classifier.
|
|
27
|
+
- `__version__` is read from installed package metadata (`importlib.metadata`).
|
|
28
|
+
- Tests: mocked Mistral schema generation and extraction (code-fence handling, bad JSON, async,
|
|
29
|
+
new SDK layout), MCP backend factory, and real-browser tests against a local HTTP server
|
|
30
|
+
(spec-driven extraction and robots.txt enforcement). 36 tests total.
|
|
31
|
+
- CHANGELOG, SECURITY.md, CODE_OF_CONDUCT.md, issue/PR templates, Dependabot, CITATION.cff.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- Requires Python 3.10+ (3.8 and 3.9 are end-of-life); classifiers for 3.10 to 3.13.
|
|
35
|
+
- Packaging is `pyproject.toml`-only (removed `setup.py`, `pytest.ini`, `requirements*.txt`),
|
|
36
|
+
with PEP 639 `license = "MIT"` and a `dev` extra (pytest, black, ruff, mypy, build, twine).
|
|
37
|
+
- CI: lint (ruff + black) is enforced instead of `|| true`, tests run on Python 3.10 to 3.13 with
|
|
38
|
+
Chromium installed, and the Docker job now runs the test suite inside the image.
|
|
39
|
+
- Codebase formatted with black and cleaned with ruff (imports, modern type hints).
|
|
40
|
+
|
|
41
|
+
## [0.3.0] - 2026-03-09
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- Hybrid extraction, Mistral LLM extraction, session persistence, content cleaning.
|
|
45
|
+
|
|
46
|
+
## [0.2.0] - 2026-03-09
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
- Specification-driven extraction, robots.txt compliance, ethical crawling, architecture refactor.
|
|
50
|
+
|
|
51
|
+
## [0.1.2] - 2025-11-27
|
|
52
|
+
|
|
53
|
+
- Initial public releases (0.1.0 to 0.1.2).
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scrapeflow-py
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
License: MIT
|
|
8
|
-
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Opinionated Playwright scraping workflow engine: retries, rate limiting, anti-detection, robots.txt compliance, Pydantic spec-driven and LLM-powered extraction, sessions, and metrics.
|
|
5
|
+
Author-email: Irfan Ali <irfanali29@hotmail.com>
|
|
6
|
+
Maintainer-email: Irfan Ali <irfanali29@hotmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/irfanalidv/scrapeflow-py
|
|
9
|
+
Project-URL: Documentation, https://github.com/irfanalidv/scrapeflow-py#readme
|
|
10
|
+
Project-URL: Repository, https://github.com/irfanalidv/scrapeflow-py
|
|
11
|
+
Project-URL: Issues, https://github.com/irfanalidv/scrapeflow-py/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/irfanalidv/scrapeflow-py/blob/main/CHANGELOG.md
|
|
13
|
+
Keywords: web-scraping,scraping,scraper,crawler,playwright,browser-automation,workflow,data-extraction,llm-extraction,mistral,pydantic,rate-limiting,retry,robots-txt,anti-detection,asyncio
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
15
|
Classifier: Intended Audience :: Developers
|
|
10
|
-
Classifier:
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Framework :: AsyncIO
|
|
18
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
|
|
19
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
11
21
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
14
22
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
23
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
24
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
-
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.10
|
|
18
29
|
Description-Content-Type: text/markdown
|
|
19
30
|
License-File: LICENSE
|
|
20
31
|
Requires-Dist: playwright>=1.40.0
|
|
@@ -23,22 +34,34 @@ Requires-Dist: tenacity>=8.2.0
|
|
|
23
34
|
Requires-Dist: pydantic>=2.0.0
|
|
24
35
|
Requires-Dist: mistralai>=1.0.0
|
|
25
36
|
Requires-Dist: python-dotenv>=1.0.0
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
39
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
40
|
+
Requires-Dist: black>=24.0.0; extra == "dev"
|
|
41
|
+
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
42
|
+
Requires-Dist: mypy>=1.7.0; extra == "dev"
|
|
43
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
44
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
32
45
|
Dynamic: license-file
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Dynamic: summary
|
|
46
|
+
|
|
47
|
+
<div align="center">
|
|
36
48
|
|
|
37
49
|
# ScrapeFlow
|
|
38
50
|
|
|
39
|
-
**An opinionated scraping workflow engine built on Playwright
|
|
51
|
+
**An opinionated scraping workflow engine built on Playwright.**
|
|
52
|
+
|
|
53
|
+
Retries, rate limiting, anti-detection, robots.txt compliance, Pydantic spec-driven and
|
|
54
|
+
Mistral LLM-powered extraction, login sessions, workflows, and metrics in one async API.
|
|
55
|
+
|
|
56
|
+
[](https://pypi.org/project/scrapeflow-py/)
|
|
57
|
+
[](https://pypi.org/project/scrapeflow-py/)
|
|
58
|
+
[](https://github.com/irfanalidv/scrapeflow-py/actions/workflows/ci.yml)
|
|
59
|
+
[](https://pepy.tech/projects/scrapeflow-py)
|
|
60
|
+
[](https://github.com/irfanalidv/scrapeflow-py/blob/main/LICENSE)
|
|
61
|
+
[](https://github.com/psf/black)
|
|
62
|
+
[](https://playwright.dev/)
|
|
40
63
|
|
|
41
|
-
|
|
64
|
+
</div>
|
|
42
65
|
|
|
43
66
|
---
|
|
44
67
|
|
|
@@ -51,7 +74,7 @@ ScrapeFlow is a production-ready Python library that transforms Playwright into
|
|
|
51
74
|
- **⚖️ Ethical Crawling (GDPR/CCPA)**: Configurable data retention, anonymization, and consent options in the specification layer
|
|
52
75
|
- **📦 Component Registry**: Shared, versioned selectors, pagination handlers, and login flows—platform thinking over one-off scrapers
|
|
53
76
|
- **🔔 Monitoring & Alerting**: Alert callbacks on failure thresholds; rollback hooks for failed extraction runs
|
|
54
|
-
- **🔌
|
|
77
|
+
- **🔌 Pluggable Extraction Backends**: `MCPBackend` interface with a built-in Mistral LLM backend; subclass it to plug in an MCP server (Scrapy/Playwright MCP adapters are planned, not yet implemented)
|
|
55
78
|
- **🤖 Mistral LLM Extraction**: Generate schemas from natural language, extract without selectors—uses `MISTRAL_API_KEY`
|
|
56
79
|
- **🔄 Hybrid Extraction**: Selector-first, LLM fallback when validation fails—self-healing spiders
|
|
57
80
|
- **🛡️ Selector Fallback Chain**: Try multiple selectors per field (`[".price", ".cost"]`) for resilience
|
|
@@ -67,13 +90,16 @@ ScrapeFlow is a production-ready Python library that transforms Playwright into
|
|
|
67
90
|
- **📝 Type Hints**: Full type support for better IDE experience
|
|
68
91
|
|
|
69
92
|
<p align="center">
|
|
70
|
-
<img src="docs/scrapeflow-architecture.png" alt="ScrapeFlow Architecture" width="800">
|
|
93
|
+
<img src="https://raw.githubusercontent.com/irfanalidv/scrapeflow-py/main/docs/scrapeflow-architecture.png" alt="ScrapeFlow Architecture" width="800">
|
|
71
94
|
</p>
|
|
72
95
|
|
|
73
96
|
## 📦 Installation
|
|
74
97
|
|
|
98
|
+
Requires Python 3.10+.
|
|
99
|
+
|
|
75
100
|
```bash
|
|
76
101
|
pip install scrapeflow-py
|
|
102
|
+
playwright install chromium # download the browser Playwright drives
|
|
77
103
|
```
|
|
78
104
|
|
|
79
105
|
Or install from source:
|
|
@@ -82,17 +108,24 @@ Or install from source:
|
|
|
82
108
|
git clone https://github.com/irfanalidv/scrapeflow-py.git
|
|
83
109
|
cd scrapeflow-py
|
|
84
110
|
pip install -e .
|
|
111
|
+
playwright install chromium
|
|
85
112
|
```
|
|
86
113
|
|
|
87
|
-
|
|
114
|
+
On a fresh Linux machine or container, `playwright install --with-deps chromium` also installs
|
|
115
|
+
the system libraries Chromium needs. A `Dockerfile` is included.
|
|
88
116
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
117
|
+
## ⚙️ Configuration (environment variables)
|
|
118
|
+
|
|
119
|
+
| Variable | Purpose |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| `MISTRAL_API_KEY` | Required only for LLM features (`generate_schema_from_prompt`, `extract_with_schema`, `MistralLLMBackend`, `HybridExtractor` LLM fallback). Read from the environment or a `.env` file. |
|
|
122
|
+
|
|
123
|
+
Everything else (rate limits, retries, browser, anti-detection, robots.txt, ethical crawling) is
|
|
124
|
+
configured in code via `ScrapeFlowConfig`; see [Configuration](#configuration) below.
|
|
92
125
|
|
|
93
126
|
## 🎯 Real-World Use Cases
|
|
94
127
|
|
|
95
|
-
|
|
128
|
+
Typical use cases:
|
|
96
129
|
|
|
97
130
|
- **💰 E-commerce Price Monitoring** - Track competitor prices, monitor deals, and optimize pricing strategies
|
|
98
131
|
- **📰 News & Content Aggregation** - Collect articles from multiple sources for content platforms
|
|
@@ -1247,11 +1280,20 @@ scrapeflow/
|
|
|
1247
1280
|
└── exceptions.py # Custom exceptions
|
|
1248
1281
|
```
|
|
1249
1282
|
|
|
1250
|
-
For deeper design details, see [`ARCHITECTURE.md`](ARCHITECTURE.md).
|
|
1283
|
+
For deeper design details, see [`ARCHITECTURE.md`](https://github.com/irfanalidv/scrapeflow-py/blob/main/ARCHITECTURE.md). Release notes are in [`CHANGELOG.md`](https://github.com/irfanalidv/scrapeflow-py/blob/main/CHANGELOG.md).
|
|
1251
1284
|
|
|
1252
1285
|
## 🤝 Contributing
|
|
1253
1286
|
|
|
1254
|
-
Contributions are welcome!
|
|
1287
|
+
Contributions are welcome! See [CONTRIBUTING.md](https://github.com/irfanalidv/scrapeflow-py/blob/main/CONTRIBUTING.md)
|
|
1288
|
+
and the [Code of Conduct](https://github.com/irfanalidv/scrapeflow-py/blob/main/CODE_OF_CONDUCT.md).
|
|
1289
|
+
|
|
1290
|
+
```bash
|
|
1291
|
+
git clone https://github.com/irfanalidv/scrapeflow-py.git && cd scrapeflow-py
|
|
1292
|
+
pip install -e ".[dev]" && playwright install chromium
|
|
1293
|
+
ruff check . && black --check . && pytest
|
|
1294
|
+
```
|
|
1295
|
+
|
|
1296
|
+
Security issues: please follow [SECURITY.md](https://github.com/irfanalidv/scrapeflow-py/blob/main/SECURITY.md).
|
|
1255
1297
|
|
|
1256
1298
|
1. Fork the repository
|
|
1257
1299
|
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
|
@@ -1261,7 +1303,7 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
1261
1303
|
|
|
1262
1304
|
## 📝 License
|
|
1263
1305
|
|
|
1264
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
1306
|
+
This project is licensed under the MIT License - see the [LICENSE](https://github.com/irfanalidv/scrapeflow-py/blob/main/LICENSE) file for details.
|
|
1265
1307
|
|
|
1266
1308
|
## 🙏 Acknowledgments
|
|
1267
1309
|
|
|
@@ -1,44 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
Name: scrapeflow-py
|
|
3
|
-
Version: 0.3.0
|
|
4
|
-
Summary: An opinionated scraping workflow engine built on Playwright
|
|
5
|
-
Home-page: https://github.com/irfanalidv/scrapeflow-py
|
|
6
|
-
Author: Irfan Ali
|
|
7
|
-
License: MIT
|
|
8
|
-
Classifier: Development Status :: 3 - Alpha
|
|
9
|
-
Classifier: Intended Audience :: Developers
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
-
Requires-Python: >=3.8
|
|
18
|
-
Description-Content-Type: text/markdown
|
|
19
|
-
License-File: LICENSE
|
|
20
|
-
Requires-Dist: playwright>=1.40.0
|
|
21
|
-
Requires-Dist: aiohttp>=3.9.0
|
|
22
|
-
Requires-Dist: tenacity>=8.2.0
|
|
23
|
-
Requires-Dist: pydantic>=2.0.0
|
|
24
|
-
Requires-Dist: mistralai>=1.0.0
|
|
25
|
-
Requires-Dist: python-dotenv>=1.0.0
|
|
26
|
-
Dynamic: author
|
|
27
|
-
Dynamic: classifier
|
|
28
|
-
Dynamic: description
|
|
29
|
-
Dynamic: description-content-type
|
|
30
|
-
Dynamic: home-page
|
|
31
|
-
Dynamic: license
|
|
32
|
-
Dynamic: license-file
|
|
33
|
-
Dynamic: requires-dist
|
|
34
|
-
Dynamic: requires-python
|
|
35
|
-
Dynamic: summary
|
|
1
|
+
<div align="center">
|
|
36
2
|
|
|
37
3
|
# ScrapeFlow
|
|
38
4
|
|
|
39
|
-
**An opinionated scraping workflow engine built on Playwright
|
|
5
|
+
**An opinionated scraping workflow engine built on Playwright.**
|
|
40
6
|
|
|
41
|
-
|
|
7
|
+
Retries, rate limiting, anti-detection, robots.txt compliance, Pydantic spec-driven and
|
|
8
|
+
Mistral LLM-powered extraction, login sessions, workflows, and metrics in one async API.
|
|
9
|
+
|
|
10
|
+
[](https://pypi.org/project/scrapeflow-py/)
|
|
11
|
+
[](https://pypi.org/project/scrapeflow-py/)
|
|
12
|
+
[](https://github.com/irfanalidv/scrapeflow-py/actions/workflows/ci.yml)
|
|
13
|
+
[](https://pepy.tech/projects/scrapeflow-py)
|
|
14
|
+
[](https://github.com/irfanalidv/scrapeflow-py/blob/main/LICENSE)
|
|
15
|
+
[](https://github.com/psf/black)
|
|
16
|
+
[](https://playwright.dev/)
|
|
17
|
+
|
|
18
|
+
</div>
|
|
42
19
|
|
|
43
20
|
---
|
|
44
21
|
|
|
@@ -51,7 +28,7 @@ ScrapeFlow is a production-ready Python library that transforms Playwright into
|
|
|
51
28
|
- **⚖️ Ethical Crawling (GDPR/CCPA)**: Configurable data retention, anonymization, and consent options in the specification layer
|
|
52
29
|
- **📦 Component Registry**: Shared, versioned selectors, pagination handlers, and login flows—platform thinking over one-off scrapers
|
|
53
30
|
- **🔔 Monitoring & Alerting**: Alert callbacks on failure thresholds; rollback hooks for failed extraction runs
|
|
54
|
-
- **🔌
|
|
31
|
+
- **🔌 Pluggable Extraction Backends**: `MCPBackend` interface with a built-in Mistral LLM backend; subclass it to plug in an MCP server (Scrapy/Playwright MCP adapters are planned, not yet implemented)
|
|
55
32
|
- **🤖 Mistral LLM Extraction**: Generate schemas from natural language, extract without selectors—uses `MISTRAL_API_KEY`
|
|
56
33
|
- **🔄 Hybrid Extraction**: Selector-first, LLM fallback when validation fails—self-healing spiders
|
|
57
34
|
- **🛡️ Selector Fallback Chain**: Try multiple selectors per field (`[".price", ".cost"]`) for resilience
|
|
@@ -67,13 +44,16 @@ ScrapeFlow is a production-ready Python library that transforms Playwright into
|
|
|
67
44
|
- **📝 Type Hints**: Full type support for better IDE experience
|
|
68
45
|
|
|
69
46
|
<p align="center">
|
|
70
|
-
<img src="docs/scrapeflow-architecture.png" alt="ScrapeFlow Architecture" width="800">
|
|
47
|
+
<img src="https://raw.githubusercontent.com/irfanalidv/scrapeflow-py/main/docs/scrapeflow-architecture.png" alt="ScrapeFlow Architecture" width="800">
|
|
71
48
|
</p>
|
|
72
49
|
|
|
73
50
|
## 📦 Installation
|
|
74
51
|
|
|
52
|
+
Requires Python 3.10+.
|
|
53
|
+
|
|
75
54
|
```bash
|
|
76
55
|
pip install scrapeflow-py
|
|
56
|
+
playwright install chromium # download the browser Playwright drives
|
|
77
57
|
```
|
|
78
58
|
|
|
79
59
|
Or install from source:
|
|
@@ -82,17 +62,24 @@ Or install from source:
|
|
|
82
62
|
git clone https://github.com/irfanalidv/scrapeflow-py.git
|
|
83
63
|
cd scrapeflow-py
|
|
84
64
|
pip install -e .
|
|
65
|
+
playwright install chromium
|
|
85
66
|
```
|
|
86
67
|
|
|
87
|
-
|
|
68
|
+
On a fresh Linux machine or container, `playwright install --with-deps chromium` also installs
|
|
69
|
+
the system libraries Chromium needs. A `Dockerfile` is included.
|
|
88
70
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
71
|
+
## ⚙️ Configuration (environment variables)
|
|
72
|
+
|
|
73
|
+
| Variable | Purpose |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `MISTRAL_API_KEY` | Required only for LLM features (`generate_schema_from_prompt`, `extract_with_schema`, `MistralLLMBackend`, `HybridExtractor` LLM fallback). Read from the environment or a `.env` file. |
|
|
76
|
+
|
|
77
|
+
Everything else (rate limits, retries, browser, anti-detection, robots.txt, ethical crawling) is
|
|
78
|
+
configured in code via `ScrapeFlowConfig`; see [Configuration](#configuration) below.
|
|
92
79
|
|
|
93
80
|
## 🎯 Real-World Use Cases
|
|
94
81
|
|
|
95
|
-
|
|
82
|
+
Typical use cases:
|
|
96
83
|
|
|
97
84
|
- **💰 E-commerce Price Monitoring** - Track competitor prices, monitor deals, and optimize pricing strategies
|
|
98
85
|
- **📰 News & Content Aggregation** - Collect articles from multiple sources for content platforms
|
|
@@ -1247,11 +1234,20 @@ scrapeflow/
|
|
|
1247
1234
|
└── exceptions.py # Custom exceptions
|
|
1248
1235
|
```
|
|
1249
1236
|
|
|
1250
|
-
For deeper design details, see [`ARCHITECTURE.md`](ARCHITECTURE.md).
|
|
1237
|
+
For deeper design details, see [`ARCHITECTURE.md`](https://github.com/irfanalidv/scrapeflow-py/blob/main/ARCHITECTURE.md). Release notes are in [`CHANGELOG.md`](https://github.com/irfanalidv/scrapeflow-py/blob/main/CHANGELOG.md).
|
|
1251
1238
|
|
|
1252
1239
|
## 🤝 Contributing
|
|
1253
1240
|
|
|
1254
|
-
Contributions are welcome!
|
|
1241
|
+
Contributions are welcome! See [CONTRIBUTING.md](https://github.com/irfanalidv/scrapeflow-py/blob/main/CONTRIBUTING.md)
|
|
1242
|
+
and the [Code of Conduct](https://github.com/irfanalidv/scrapeflow-py/blob/main/CODE_OF_CONDUCT.md).
|
|
1243
|
+
|
|
1244
|
+
```bash
|
|
1245
|
+
git clone https://github.com/irfanalidv/scrapeflow-py.git && cd scrapeflow-py
|
|
1246
|
+
pip install -e ".[dev]" && playwright install chromium
|
|
1247
|
+
ruff check . && black --check . && pytest
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1250
|
+
Security issues: please follow [SECURITY.md](https://github.com/irfanalidv/scrapeflow-py/blob/main/SECURITY.md).
|
|
1255
1251
|
|
|
1256
1252
|
1. Fork the repository
|
|
1257
1253
|
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
|
@@ -1261,7 +1257,7 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
1261
1257
|
|
|
1262
1258
|
## 📝 License
|
|
1263
1259
|
|
|
1264
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
1260
|
+
This project is licensed under the MIT License - see the [LICENSE](https://github.com/irfanalidv/scrapeflow-py/blob/main/LICENSE) file for details.
|
|
1265
1261
|
|
|
1266
1262
|
## 🙏 Acknowledgments
|
|
1267
1263
|
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
"""Advanced example showing anti-detection, rate limiting, and structured extraction."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
|
|
4
5
|
from scrapeflow import ScrapeFlow
|
|
5
6
|
from scrapeflow.config import (
|
|
6
|
-
ScrapeFlowConfig,
|
|
7
7
|
AntiDetectionConfig,
|
|
8
8
|
RateLimitConfig,
|
|
9
9
|
RetryConfig,
|
|
10
|
+
ScrapeFlowConfig,
|
|
10
11
|
)
|
|
11
|
-
from scrapeflow.extractors import
|
|
12
|
+
from scrapeflow.extractors import Extractor, StructuredExtractor
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
async def main():
|
|
@@ -65,8 +66,7 @@ async def main():
|
|
|
65
66
|
# Extract tags separately using Extractor for each quote
|
|
66
67
|
quotes_with_tags = []
|
|
67
68
|
quote_elements = scraper.page.locator(".quote")
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
for i, quote_data in enumerate(data.get("quotes", [])[:5]): # First 5
|
|
71
71
|
quote_elem = quote_elements.nth(i)
|
|
72
72
|
tags = await Extractor.extract_texts(quote_elem, ".tag")
|
|
@@ -84,7 +84,7 @@ async def main():
|
|
|
84
84
|
|
|
85
85
|
# Get metrics
|
|
86
86
|
metrics = scraper.get_metrics()
|
|
87
|
-
print(
|
|
87
|
+
print("\nScraping completed:")
|
|
88
88
|
print(f" Success rate: {metrics.get_success_rate():.2f}%")
|
|
89
89
|
print(f" Total requests: {metrics.total_requests}")
|
|
90
90
|
print(f" Retries: {metrics.retry_count}")
|
|
@@ -92,4 +92,3 @@ async def main():
|
|
|
92
92
|
|
|
93
93
|
if __name__ == "__main__":
|
|
94
94
|
asyncio.run(main())
|
|
95
|
-
|
|
@@ -8,6 +8,7 @@ Demonstrates:
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
|
+
|
|
11
12
|
from pydantic import BaseModel
|
|
12
13
|
|
|
13
14
|
from scrapeflow import (
|
|
@@ -16,7 +17,7 @@ from scrapeflow import (
|
|
|
16
17
|
get_registry,
|
|
17
18
|
register_quotes_login_handler,
|
|
18
19
|
)
|
|
19
|
-
from scrapeflow.config import
|
|
20
|
+
from scrapeflow.config import EthicalCrawlingConfig, ScrapeFlowConfig
|
|
20
21
|
from scrapeflow.specifications import FieldSpec, ItemSpec
|
|
21
22
|
|
|
22
23
|
|
|
@@ -5,8 +5,9 @@ a real website designed for scraping practice.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
|
+
|
|
8
9
|
from scrapeflow import ScrapeFlow
|
|
9
|
-
from scrapeflow.config import
|
|
10
|
+
from scrapeflow.config import RateLimitConfig, RetryConfig, ScrapeFlowConfig
|
|
10
11
|
from scrapeflow.extractors import Extractor
|
|
11
12
|
|
|
12
13
|
|
|
@@ -11,17 +11,18 @@ Requires MISTRAL_API_KEY in .env for LLM fallback.
|
|
|
11
11
|
|
|
12
12
|
import asyncio
|
|
13
13
|
import os
|
|
14
|
-
from typing import Optional
|
|
15
14
|
|
|
16
15
|
try:
|
|
17
16
|
from dotenv import load_dotenv
|
|
17
|
+
|
|
18
18
|
load_dotenv()
|
|
19
19
|
except ImportError:
|
|
20
20
|
pass
|
|
21
21
|
|
|
22
22
|
from pydantic import BaseModel, Field
|
|
23
|
-
|
|
24
|
-
from scrapeflow
|
|
23
|
+
|
|
24
|
+
from scrapeflow import HybridExtractor, ScrapeFlow
|
|
25
|
+
from scrapeflow.config import EthicalCrawlingConfig, ScrapeFlowConfig
|
|
25
26
|
from scrapeflow.specifications import FieldSpec
|
|
26
27
|
|
|
27
28
|
|
|
@@ -30,7 +31,7 @@ class ProductSpec(BaseModel):
|
|
|
30
31
|
|
|
31
32
|
title: str = Field(..., description="Product title")
|
|
32
33
|
price: str = Field(..., description="Price string")
|
|
33
|
-
availability:
|
|
34
|
+
availability: str | None = Field(None, description="Stock status")
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
async def run_hybrid_example():
|
|
@@ -47,7 +48,9 @@ async def run_hybrid_example():
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
async with ScrapeFlow(config) as scraper:
|
|
50
|
-
await scraper.navigate(
|
|
51
|
+
await scraper.navigate(
|
|
52
|
+
"https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"
|
|
53
|
+
)
|
|
51
54
|
|
|
52
55
|
# Use HybridExtractor: selectors first, LLM fallback if validation fails
|
|
53
56
|
use_llm = bool(os.environ.get("MISTRAL_API_KEY"))
|
|
@@ -15,13 +15,14 @@ import os
|
|
|
15
15
|
# Load .env if available
|
|
16
16
|
try:
|
|
17
17
|
from dotenv import load_dotenv
|
|
18
|
+
|
|
18
19
|
load_dotenv()
|
|
19
20
|
except ImportError:
|
|
20
21
|
pass
|
|
21
22
|
|
|
22
23
|
from scrapeflow import ScrapeFlow, create_mcp_backend
|
|
23
|
-
from scrapeflow.config import
|
|
24
|
-
from scrapeflow.llm_extract import
|
|
24
|
+
from scrapeflow.config import EthicalCrawlingConfig, ScrapeFlowConfig
|
|
25
|
+
from scrapeflow.llm_extract import extract_with_schema, generate_schema_from_prompt
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
async def run_llm_extraction():
|
|
@@ -38,15 +39,11 @@ async def run_llm_extraction():
|
|
|
38
39
|
await scraper.navigate("https://quotes.toscrape.com/")
|
|
39
40
|
|
|
40
41
|
# Option 1: Generate schema from prompt
|
|
41
|
-
schema = generate_schema_from_prompt(
|
|
42
|
-
"Extract quote text, author name, and list of tags"
|
|
43
|
-
)
|
|
42
|
+
schema = generate_schema_from_prompt("Extract quote text, author name, and list of tags")
|
|
44
43
|
print("Generated schema:", schema)
|
|
45
44
|
|
|
46
45
|
# Option 2: Extract using schema + page content
|
|
47
|
-
content = await scraper.page.evaluate(
|
|
48
|
-
"() => document.body.innerText"
|
|
49
|
-
)
|
|
46
|
+
content = await scraper.page.evaluate("() => document.body.innerText")
|
|
50
47
|
data = extract_with_schema(
|
|
51
48
|
content,
|
|
52
49
|
schema,
|
|
@@ -10,11 +10,17 @@ import asyncio
|
|
|
10
10
|
import os
|
|
11
11
|
import tempfile
|
|
12
12
|
|
|
13
|
-
from scrapeflow import ScrapeFlow, SpecificationExtractor, get_registry, register_quotes_login_handler
|
|
14
|
-
from scrapeflow.config import ScrapeFlowConfig, EthicalCrawlingConfig, BrowserConfig
|
|
15
|
-
from scrapeflow.specifications import FieldSpec, ItemSpec
|
|
16
13
|
from pydantic import BaseModel
|
|
17
14
|
|
|
15
|
+
from scrapeflow import (
|
|
16
|
+
ScrapeFlow,
|
|
17
|
+
SpecificationExtractor,
|
|
18
|
+
get_registry,
|
|
19
|
+
register_quotes_login_handler,
|
|
20
|
+
)
|
|
21
|
+
from scrapeflow.config import BrowserConfig, EthicalCrawlingConfig, ScrapeFlowConfig
|
|
22
|
+
from scrapeflow.specifications import FieldSpec, ItemSpec
|
|
23
|
+
|
|
18
24
|
|
|
19
25
|
class QuoteItem(BaseModel):
|
|
20
26
|
text: str
|
|
@@ -10,16 +10,16 @@ Demonstrates:
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import asyncio
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel
|
|
14
15
|
|
|
15
16
|
from scrapeflow import ScrapeFlow, SpecificationExtractor
|
|
16
17
|
from scrapeflow.config import (
|
|
17
|
-
ScrapeFlowConfig,
|
|
18
18
|
EthicalCrawlingConfig,
|
|
19
19
|
RateLimitConfig,
|
|
20
|
+
ScrapeFlowConfig,
|
|
20
21
|
)
|
|
21
22
|
from scrapeflow.specifications import FieldSpec, ItemSpec, JobListingSpec
|
|
22
|
-
from scrapeflow.registry import get_registry, register_job_listing_schema
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class JobListingsPage(BaseModel):
|
|
@@ -73,7 +73,9 @@ async def scrape_job_listings():
|
|
|
73
73
|
print(f" {i+1}. {job.title[:50]}... | {job.company or 'N/A'}")
|
|
74
74
|
|
|
75
75
|
metrics = scraper.get_metrics()
|
|
76
|
-
print(
|
|
76
|
+
print(
|
|
77
|
+
f"\nMetrics: {metrics.total_requests} requests, {metrics.get_success_rate():.1f}% success"
|
|
78
|
+
)
|
|
77
79
|
|
|
78
80
|
|
|
79
81
|
if __name__ == "__main__":
|
|
@@ -5,6 +5,7 @@ a real e-commerce site designed for scraping practice.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
|
+
|
|
8
9
|
from scrapeflow import ScrapeFlow, Workflow
|
|
9
10
|
from scrapeflow.config import ScrapeFlowConfig
|
|
10
11
|
from scrapeflow.extractors import StructuredExtractor
|
|
@@ -20,12 +21,12 @@ async def scrape_books_page(page, context):
|
|
|
20
21
|
"title": "h3 a",
|
|
21
22
|
"price": ".price_color",
|
|
22
23
|
"availability": ".instock.availability",
|
|
23
|
-
}
|
|
24
|
+
},
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
extractor = StructuredExtractor(schema)
|
|
27
28
|
data = await extractor.extract(page)
|
|
28
|
-
|
|
29
|
+
|
|
29
30
|
context["books_data"] = data
|
|
30
31
|
return data
|
|
31
32
|
|
|
@@ -90,7 +91,7 @@ async def main():
|
|
|
90
91
|
|
|
91
92
|
# Print metrics
|
|
92
93
|
metrics = scraper.get_metrics()
|
|
93
|
-
print(
|
|
94
|
+
print("\n📊 Metrics:")
|
|
94
95
|
print(f" Total requests: {metrics.total_requests}")
|
|
95
96
|
print(f" Success rate: {metrics.get_success_rate():.2f}%")
|
|
96
97
|
print(f" Average response time: {metrics.average_response_time:.2f}s")
|
|
@@ -98,4 +99,3 @@ async def main():
|
|
|
98
99
|
|
|
99
100
|
if __name__ == "__main__":
|
|
100
101
|
asyncio.run(main())
|
|
101
|
-
|