Skip to content

Commit 6322ad5

Browse files
authored
Merge branch 'dev' into fix/static-zero-retries-skips-request
2 parents 8ee69a5 + 63cdc99 commit 6322ad5

54 files changed

Lines changed: 3486 additions & 930 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1169 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
<a href="https://hub.docker.com/r/pyd4vinci/scrapling" target="_blank">
2424
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/pyd4vinci/scrapling?labelColor=%20%23FDB062&logo=Docker&labelColor=%20%23528bff"></a>
2525
<a href="https://clickpy.clickhouse.com/dashboard/scrapling" rel="nofollow"><img src="https://img.shields.io/pypi/dm/scrapling" alt="PyPI package downloads"></a>
26-
<a href="https://github.com/D4Vinci/Scrapling/tree/main/agent-skill" alt="AI Agent Skill directory">
27-
<img alt="Static Badge" src="https://img.shields.io/badge/Skill-black?style=flat&label=Agent&link=https%3A%2F%2Fgithub.qkg1.top%2FD4Vinci%2FScrapling%2Ftree%2Fmain%2Fagent-skill"></a>
26+
<a href="https://scrapling.readthedocs.io/en/latest/ai/agent-skill.html" alt="AI Agent Skill">
27+
<img alt="Static Badge" src="https://img.shields.io/badge/Skill-black?style=flat&label=Agent&link=https%3A%2F%2Fscrapling.readthedocs.io%2Fen%2Flatest%2Fai%2Fagent-skill.html"></a>
2828
<a href="https://clawhub.ai/D4Vinci/scrapling-official" alt="OpenClaw Skill">
2929
<img alt="OpenClaw Skill" src="https://img.shields.io/badge/Clawhub-darkred?style=flat&label=OpenClaw&link=https%3A%2F%2Fclawhub.ai%2FD4Vinci%2Fscrapling-official"></a>
3030
<br/>
@@ -55,7 +55,7 @@
5555

5656
Scrapling is an adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl.
5757

58-
Its parser learns from website changes and automatically relocates your elements when pages update. Its fetchers bypass anti-bot systems like Cloudflare Turnstile out of the box. And its spider framework lets you scale up to concurrent, multi-session crawls with pause/resume and automatic proxy rotation - all in a few lines of Python. One library, zero compromises.
58+
Its parser learns from website changes and automatically relocates your elements when pages update. Its fetchers bypass anti-bot systems like Cloudflare Turnstile out of the box. And its spider framework lets you scale up to concurrent, multi-session crawls with pause/resume, automatic proxy rotation, and a crawl speed that adapts to how fast each website responds and backs off when it starts blocking you - all in a few lines of Python. One library, zero compromises.
5959

6060
Blazing fast crawls with real-time stats and streaming. Built by Web Scrapers for Web Scrapers and regular users, there's something for everyone.
6161

@@ -91,12 +91,12 @@ MySpider().start()
9191
<table>
9292
<tr>
9393
<td width="200">
94-
<a href="https://go.nodemaven.com/scraplingjuly" target="_blank" title="Proxies with the Highest IP Scores">
94+
<a href="https://go.nodemaven.com/scraplingaugust" target="_blank" title="Proxies with the Highest IP Scores">
9595
<img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/NodeMaven.jpg" width="240" height="100">
9696
</a>
9797
</td>
9898
<td>
99-
<a href="https://go.nodemaven.com/scraplingjuly" target="_blank">NodeMaven</a> - reliable proxy provider with the highest quality IP on the market. Use promo code SCRAPLING35 for 35% discount on proxies.
99+
<a href="https://go.nodemaven.com/scraplingaugust" target="_blank">NodeMaven</a> - The most efficient proxy provider for Web Scraping and Automation with the Highest Quality IP on the market. Use code SCRAPLING35 for 35% discount.
100100
</td>
101101
</tr>
102102
<tr>
@@ -221,6 +221,7 @@ MySpider().start()
221221
<!-- sponsors -->
222222

223223
<a href="https://www.novada.com/?d4vinci-scrapling" target="_blank" title="The All-in-One Solution for Every Data Scraping Scenario"><img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/novada.jpg"></a>
224+
<a href="https://cloro.dev/?utm_source=referral&utm_medium=scrapling" target="_blank" title="The search API for the AI era"><img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/cloro.jpg"></a>
224225

225226
<br/>
226227

@@ -265,12 +266,15 @@ MySpider().start()
265266
- **Background API Capture**: Pass a URL pattern to `capture_xhr`, and all matching XHR/fetch responses the page makes while loading are collected for you as `Response` objects in `response.captured_xhr` - grab a site's API data without reverse-engineering the requests yourself.
266267
- **Async Support**: Complete async support across all fetchers and dedicated async session classes.
267268

268-
### Adaptive Scraping & AI Integration
269+
### Adaptive Scraping
269270
- 🔄 **Smart Element Tracking**: Relocate elements after website changes using intelligent similarity algorithms.
270271
- 🎯 **Smart Flexible Selection**: CSS selectors, XPath selectors, filter-based search, text search, regex search, and more.
271272
- 🔍 **Find Similar Elements**: Automatically locate elements similar to found elements.
272-
- 🤖 **MCP Server to be used with AI**: Built-in MCP server for AI-assisted Web Scraping and data extraction. The MCP server features powerful, custom capabilities that leverage Scrapling to extract targeted content before passing it to the AI (Claude/Cursor/etc), thereby speeding up operations and reducing costs by minimizing token usage. ([demo video](https://www.youtube.com/watch?v=qyFk3ZNwOxE)) It can also keep browser sessions open across calls, take page screenshots, and drive remote browsers over CDP.
273-
- 🧠 **Agent Skill**: A ready-to-install [Agent Skill](https://github.com/D4Vinci/Scrapling/tree/main/agent-skill) that teaches coding agents the whole library, so the code they write with Scrapling matches the current API instead of guessing.
273+
274+
### AI Features
275+
- 🤖 **MCP Server**: Let AI chatbots and agents (Claude/Cursor/etc) scrape through Scrapling with one-shot or session-based tools covering plain HTTP requests (any method), browser fetches, and stealth fetches that bypass Cloudflare. Pages are narrowed with CSS selectors and stripped of prompt-injection content before the AI sees them, so the agent reads less, costs less, and can't be hijacked by hidden text. Screenshots, remote browsers over CDP, and a secure-by-default HTTP transport are included. ([demo video](https://www.youtube.com/watch?v=qyFk3ZNwOxE))
276+
- 🧠 **Agent Skill**: A ready-to-install [Agent Skill](https://scrapling.readthedocs.io/en/latest/ai/agent-skill.html) that teaches coding agents the whole library, so the code they write with Scrapling matches the current API instead of guessing.
277+
- 📚 **RAG-ready Markdown**: Turn any page into clean, sanitized, LLM-ready Markdown with one line (`page.markdown()`), or crawl a whole website into a Markdown corpus with the `SiteToMarkdownSpider` template, all without an LLM in the loop. ([docs](https://scrapling.readthedocs.io/en/latest/ai/building-rag-systems.html))
274278

275279
### High-Performance & battle-tested Architecture
276280
- 🚀 **Lightning Fast**: Optimized performance outperforming most Python scraping libraries.
@@ -540,6 +544,10 @@ pip install scrapling
540544
```bash
541545
pip install "scrapling[ai]"
542546
```
547+
- Install dependencies for ([building RAG systems](https://scrapling.readthedocs.io/en/latest/ai/building-rag-systems.html)):
548+
```bash
549+
pip install "scrapling[rag]"
550+
```
543551
- Install shell features (Web Scraping shell and the `extract` command):
544552
```bash
545553
pip install "scrapling[shell]"

agent-skill/Scrapling-Skill.zip

4.01 KB
Binary file not shown.

agent-skill/Scrapling-Skill/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ async with AsyncDynamicSession(capture_xhr=r"https://api\.example\.com/.*") as s
398398
## References
399399
You already had a good glimpse of what the library can do. Use the references below to dig deeper when needed
400400
- `references/mcp-server.md` - MCP server tools, persistent session management, remote browsers over CDP, authentication, and capabilities
401+
- `references/building-rag-systems.md` - Converting pages/websites to LLM-ready Markdown with `Response.markdown()` and `SiteToMarkdownSpider` for RAG pipelines
401402
- `references/parsing` - Everything you need for parsing HTML
402403
- `references/fetching` - Everything you need to fetch websites and session persistence
403404
- `references/spiders` - Everything you need to write spiders, proxy rotation, and advanced features. It follows a Scrapy-like format
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Building RAG Systems
2+
3+
RAG pipelines are only as good as the text you feed them. Raw HTML wastes tokens on markup, navigation, scripts, and tracking noise, and it can even carry hidden prompt-injection content straight into your LLM. Scrapling turns pages and whole websites into clean, sanitized Markdown with no LLM in the loop, so your ingestion runs fast and costs nothing per page.
4+
5+
## Installation
6+
7+
```bash
8+
pip install "scrapling[rag]"
9+
10+
scrapling install
11+
```
12+
13+
The `rag` extra installs the fetchers with Markdown conversion support (the `ai`, `shell`, and `all` extras include it too). The `scrapling install` command downloads the browser dependencies, which you only need for the browser-based fetchers.
14+
15+
## One page to Markdown
16+
17+
Every [Response](fetching/choosing.md) has a `markdown()` method:
18+
19+
```python
20+
from scrapling.fetchers import Fetcher
21+
22+
markdown = Fetcher.get("https://example.com").markdown(main_content_only=True)
23+
```
24+
25+
It works with all fetchers, so pages behind Cloudflare are one line away too:
26+
27+
```python
28+
from scrapling.fetchers import StealthyFetcher
29+
30+
markdown = StealthyFetcher.fetch("https://protected.example.com", solve_cloudflare=True).markdown(main_content_only=True)
31+
```
32+
33+
Two arguments control the output:
34+
35+
- `main_content_only`: Convert only the content inside the page's `<body>` tag.
36+
- `css_selector`: Convert only the elements matching a CSS selector (all matches are concatenated). Use it to extract exactly the part your pipeline needs and save tokens:
37+
38+
```python
39+
markdown = Fetcher.get("https://example.com/docs/page").markdown(css_selector="article")
40+
```
41+
42+
Whatever you pass, scripts, styles, and hidden content are always removed before conversion. This is the same cleaning the [MCP server](mcp-server.md) uses to protect AI agents from prompt injection: CSS-hidden elements, `aria-hidden` elements, `<template>` tags, HTML comments, and zero-width characters never reach your model.
43+
44+
## A whole website to a Markdown corpus
45+
46+
The `SiteToMarkdownSpider` template crawls a website and converts every page, powered by the [spiders framework](spiders/architecture.md), so you get concurrency, autothrottle, robots.txt compliance, and pause/resume for free:
47+
48+
```python
49+
from scrapling.spiders import SiteToMarkdownSpider
50+
51+
class DocsSpider(SiteToMarkdownSpider):
52+
name = "docs"
53+
start_urls = ["https://example.com/docs/"]
54+
allowed_domains = {"example.com"}
55+
output_dir = "docs_markdown"
56+
max_pages = 200
57+
58+
result = DocsSpider().start()
59+
result.items.to_jsonl("docs.jsonl")
60+
```
61+
62+
Each crawled page becomes one item with `url`, `title`, and `markdown` keys. With `output_dir` set, each page is also written to a Markdown file named after its URL, so the run above gives you both a folder of `.md` files and a `docs.jsonl` ready for ingestion.
63+
64+
The template requires `allowed_domains` so the crawl stays bound to the target website. The options:
65+
66+
- `css_selector` / `main_content_only`: Passed to `markdown()` for every page, with `main_content_only` enabled by default.
67+
- `output_dir`: When set, writes one Markdown file per page.
68+
- `max_pages`: Maximum number of pages to convert. Requests already queued when the cap hits may still be fetched, but they aren't converted. `0` (the default) disables it.
69+
70+
Every page link inside `allowed_domains` is followed by default. Since the template builds on [CrawlSpider](spiders/generic-templates.md), override `rules()` with your own [LinkExtractor](spiders/generic-templates.md) to control the crawl: `allow` narrows it to the URL patterns you want, and `deny` drops the patterns you don't (login pages, tag listings, print views, etc.):
71+
72+
```python
73+
from scrapling.spiders import CrawlRule, LinkExtractor, SiteToMarkdownSpider
74+
75+
class DocsSpider(SiteToMarkdownSpider):
76+
name = "docs"
77+
start_urls = ["https://example.com/"]
78+
allowed_domains = {"example.com"}
79+
80+
def rules(self):
81+
return [CrawlRule(LinkExtractor(allow=r"/docs/", deny=[r"/docs/changelog/", r"\?print="]))]
82+
```
83+
84+
`deny` wins over `allow`, and a rule can carry a `priority` or a `process_request` hook as with any **CrawlSpider**.
85+
86+
## Feeding a vector store
87+
88+
The JSONL output plugs into any embedding pipeline. A minimal example:
89+
90+
```python
91+
import json
92+
93+
with open("docs.jsonl") as f:
94+
for line in f:
95+
page = json.loads(line)
96+
for chunk in split_into_chunks(page["markdown"]):
97+
vector_store.add(text=chunk, metadata={"url": page["url"], "title": page["title"]})
98+
```
99+
100+
Use `css_selector` on the spider to cut boilerplate before chunking instead of cleaning it downstream. The less noise you embed, the better your retrieval.
101+
102+
## Interactive alternatives
103+
104+
For conversational scraping instead of pipelines, the [MCP server](mcp-server.md) gives your AI chatbot the same Markdown extraction as tools, and this agent skill teaches coding agents to write this code themselves.

agent-skill/Scrapling-Skill/references/fetching/choosing.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,10 @@ page.captured_xhr # List of captured XHR/fetch responses (when capture_xhr is
7575
```
7676
All fetchers return the `Response` object.
7777

78+
The `Response` object can also convert the page to clean, LLM-ready Markdown in one line:
79+
```python
80+
markdown = Fetcher.get('https://example.com').markdown(main_content_only=True)
81+
```
82+
Scripts, styles, and hidden/prompt-injection content are always removed before conversion, and you can pass `css_selector` to convert specific elements only. It requires the `rag` extra (included in `ai`/`shell`/`all` too). See `../building-rag-systems.md` for the full guide.
83+
7884
**Note:** Unlike the [Selector](parsing/main_classes.md#selector) class, the `Response` class's body is always bytes since v0.4.

agent-skill/Scrapling-Skill/references/fetching/dynamic.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,14 +325,19 @@ async def scrape_multiple_sites():
325325
return pages
326326
```
327327

328-
You may have noticed the `max_pages` argument. This is a new argument that enables the fetcher to create a **rotating pool of Browser tabs**. Instead of using a single tab for all your requests, you set a limit on the maximum number of pages that can be displayed at once. With each request, the library will close all tabs that have finished their task and check if the number of the current tabs is lower than the maximum allowed number of pages/tabs, then:
328+
You may have noticed the `max_pages` argument. It enables the fetcher to keep a **pool of Browser tabs**, and you set the maximum number of tabs that can be open at once. Tabs stay open after their request finishes, so with each request, the library will:
329329

330-
1. If you are within the allowed range, the fetcher will create a new tab for you, and then all is as normal.
331-
2. Otherwise, it will keep checking every subsecond if creating a new tab is allowed or not for 60 seconds, then raise `TimeoutError`. This can happen when the website you are fetching becomes unresponsive.
330+
1. Reuse a free tab if there's one. Every request applies its own tab-level settings (`timeout`, `extra_headers`, `disable_resources`, `blocked_domains`, etc.) to the tab it gets, so nothing leaks from the previous request.
331+
2. Otherwise, open a new tab if the number of open tabs is lower than `max_pages`.
332+
3. Otherwise, keep checking every subsecond for a tab to become free for 60 seconds, then raise `TimeoutError`. This can happen when the website you are fetching becomes unresponsive.
333+
334+
Tabs that hit an error are closed and replaced, and you can close all the open tabs yourself at any point with `session.close_pages()`, then the next request opens a fresh one.
332335

333336
This logic allows for multiple URLs to be fetched at the same time in the same browser, which saves a lot of resources, but most importantly, is so fast :)
334337

335-
In versions 0.3 and 0.3.1, the pool was reusing finished tabs to save more resources/time. That logic proved flawed, as it's nearly impossible to protect pages/tabs from contamination by the previous configuration used in the request before this one.
338+
Keeping the tabs open also means the page you fetched is still there for the next request, so a `page_setup` function on the next request runs on it before navigating away. That's the building block for chaining automation across requests.
339+
340+
Versions 0.3.2 to 0.4.14 closed every tab after its request because reusing tabs used to leak settings between requests. Since 0.4.15, the settings are reset on every reuse, so the tabs stay open.
336341

337342
### Session Benefits
338343

agent-skill/Scrapling-Skill/references/fetching/stealthy.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,14 +228,19 @@ async def scrape_multiple_sites():
228228
return pages
229229
```
230230

231-
You may have noticed the `max_pages` argument. This is a new argument that enables the fetcher to create a **rotating pool of Browser tabs**. Instead of using a single tab for all your requests, you set a limit on the maximum number of pages that can be displayed at once. With each request, the library will close all tabs that have finished their task and check if the number of the current tabs is lower than the maximum allowed number of pages/tabs, then:
231+
You may have noticed the `max_pages` argument. It enables the fetcher to keep a **pool of Browser tabs**, and you set the maximum number of tabs that can be open at once. Tabs stay open after their request finishes, so with each request, the library will:
232232

233-
1. If you are within the allowed range, the fetcher will create a new tab for you, and then all is as normal.
234-
2. Otherwise, it will keep checking every subsecond if creating a new tab is allowed or not for 60 seconds, then raise `TimeoutError`. This can happen when the website you are fetching becomes unresponsive.
233+
1. Reuse a free tab if there's one. Every request applies its own tab-level settings (`timeout`, `extra_headers`, `disable_resources`, `blocked_domains`, etc.) to the tab it gets, so nothing leaks from the previous request.
234+
2. Otherwise, open a new tab if the number of open tabs is lower than `max_pages`.
235+
3. Otherwise, keep checking every subsecond for a tab to become free for 60 seconds, then raise `TimeoutError`. This can happen when the website you are fetching becomes unresponsive.
236+
237+
Tabs that hit an error are closed and replaced, and you can close all the open tabs yourself at any point with `session.close_pages()`, then the next request opens a fresh one.
235238

236239
This logic allows for multiple URLs to be fetched at the same time in the same browser, which saves a lot of resources, but most importantly, is so fast :)
237240

238-
In versions 0.3 and 0.3.1, the pool was reusing finished tabs to save more resources/time. That logic proved flawed, as it's nearly impossible to protect pages/tabs from contamination by the previous configuration used in the request before this one.
241+
Keeping the tabs open also means the page you fetched is still there for the next request, so a `page_setup` function on the next request runs on it before navigating away. That's the building block for chaining automation across requests.
242+
243+
Versions 0.3.2 to 0.4.14 closed every tab after its request because reusing tabs used to leak settings between requests. Since 0.4.15, the settings are reset on every reuse, so the tabs stay open.
239244

240245
### Session Benefits
241246

0 commit comments

Comments
 (0)