This commit is contained in:
Karim shoair
2026-04-17 23:09:43 +02:00
committed by GitHub
16 changed files with 892 additions and 35 deletions
+1 -1
View File
@@ -14,7 +14,7 @@
<p align="center"> <p align="center">
<a href="https://trendshift.io/repositories/14244" target="_blank"><img src="https://trendshift.io/api/badge/repositories/14244" alt="D4Vinci%2FScrapling | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a> <a href="https://trendshift.io/repositories/14244" target="_blank"><img src="https://trendshift.io/api/badge/repositories/14244" alt="D4Vinci%2FScrapling | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<br/> <br/>
<a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_AR.md">العربيه</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_ES.md">Español</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_FR.md">Français</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_DE.md">Deutsch</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_CN.md">简体中文</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_JP.md">日本語</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_RU.md">Русский</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_KR.md">한국어</a> <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_AR.md">العربيه</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_ES.md">Español</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_PT_BR.md">Português (Brasil)</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_FR.md">Français</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_DE.md">Deutsch</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_CN.md">简体中文</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_JP.md">日本語</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_RU.md">Русский</a> | <a href="https://github.com/D4Vinci/Scrapling/blob/main/docs/README_KR.md">한국어</a>
<br/> <br/>
<a href="https://github.com/D4Vinci/Scrapling/actions/workflows/tests.yml" alt="Tests"> <a href="https://github.com/D4Vinci/Scrapling/actions/workflows/tests.yml" alt="Tests">
<img alt="Tests" src="https://github.com/D4Vinci/Scrapling/actions/workflows/tests.yml/badge.svg"></a> <img alt="Tests" src="https://github.com/D4Vinci/Scrapling/actions/workflows/tests.yml/badge.svg"></a>
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
--- ---
name: scrapling-official name: scrapling-official
description: Scrape web pages using Scrapling with anti-bot bypass (like Cloudflare Turnstile), stealth headless browsing, spiders framework, adaptive scraping, and JavaScript rendering. Use when asked to scrape, crawl, or extract data from websites; web_fetch fails; the site has anti-bot protections; write Python code to scrape/crawl; or write spiders. description: Scrape web pages using Scrapling with anti-bot bypass (like Cloudflare Turnstile), stealth headless browsing, spiders framework, adaptive scraping, and JavaScript rendering. Use when asked to scrape, crawl, or extract data from websites; web_fetch fails; the site has anti-bot protections; write Python code to scrape/crawl; or write spiders.
version: "0.4.6" version: "0.4.7"
license: Complete terms in LICENSE.txt license: Complete terms in LICENSE.txt
metadata: metadata:
homepage: "https://scrapling.readthedocs.io/en/latest/index.html" homepage: "https://scrapling.readthedocs.io/en/latest/index.html"
@@ -40,7 +40,7 @@ Blazing fast crawls with real-time stats and streaming. Built by Web Scrapers fo
Create a virtual Python environment through any way available, like `venv`, then inside the environment do: Create a virtual Python environment through any way available, like `venv`, then inside the environment do:
`pip install "scrapling[all]>=0.4.6"` `pip install "scrapling[all]>=0.4.7"`
Then do this to download all the browsers' dependencies: Then do this to download all the browsers' dependencies:
@@ -9,7 +9,7 @@ All examples collect **all 100 quotes across 10 pages**.
Make sure Scrapling is installed: Make sure Scrapling is installed:
```bash ```bash
pip install "scrapling[all]>=0.4.6" pip install "scrapling[all]>=0.4.7"
scrapling install --force scrapling install --force
``` ```
@@ -1,8 +1,8 @@
# Scrapling MCP Server # Scrapling MCP Server
The Scrapling MCP server exposes nine web scraping tools over the MCP protocol. It supports CSS-selector-based content narrowing (reducing tokens by extracting only relevant elements before returning results), three levels of scraping capability (plain HTTP, browser-rendered, and stealth/anti-bot bypass), and persistent browser session management. The Scrapling MCP server exposes ten tools over the MCP protocol. It supports CSS-selector-based content narrowing (reducing tokens by extracting only relevant elements before returning results), three levels of scraping capability (plain HTTP, browser-rendered, and stealth/anti-bot bypass), persistent browser session management, and page screenshots returned as real image content blocks.
All scraping tools return a `ResponseModel` with fields: `status` (int), `content` (list of strings), `url` (str). All scraping tools return a `ResponseModel` with fields: `status` (int), `content` (list of strings), `url` (str). The `screenshot` tool returns a list of MCP content blocks: an `ImageContent` (the screenshot bytes) followed by a `TextContent` (the post-redirect URL).
## Tools ## Tools
@@ -99,17 +99,18 @@ Opens a browser session that stays alive across multiple fetch calls, avoiding t
**Key parameters:** **Key parameters:**
| Parameter | Type | Default | Description | | Parameter | Type | Default | Description |
|--------------------|-----------------------------|--------------|---------------------------------------------------------------------| |--------------------|-----------------------------|--------------|-------------------------------------------------------------------------------------------------------|
| `session_type` | `"dynamic"` / `"stealthy"` | required | Type of browser session to create | | `session_type` | `"dynamic"` / `"stealthy"` | required | Type of browser session to create |
| `headless` | bool | true | Run browser hidden or visible | | `session_id` | str or null | null | Custom ID for the session. If omitted, a random 12-char hex ID is generated. Raises if already in use |
| `max_pages` | int | 5 | Max concurrent browser tabs (1-50) | | `headless` | bool | true | Run browser hidden or visible |
| `proxy` | str or dict or null | null | Proxy for all requests in this session | | `max_pages` | int | 5 | Max concurrent browser tabs (1-50) |
| `timeout` | number | 30000 | Default timeout in ms | | `proxy` | str or dict or null | null | Proxy for all requests in this session |
| `solve_cloudflare` | bool | false | (Stealthy only) Auto-solve Cloudflare challenges | | `timeout` | number | 30000 | Default timeout in ms |
| `hide_canvas` | bool | false | (Stealthy only) Canvas fingerprint noise | | `solve_cloudflare` | bool | false | (Stealthy only) Auto-solve Cloudflare challenges |
| `block_webrtc` | bool | false | (Stealthy only) Block WebRTC IP leak | | `hide_canvas` | bool | false | (Stealthy only) Canvas fingerprint noise |
| `allow_webgl` | bool | true | (Stealthy only) Keep WebGL enabled | | `block_webrtc` | bool | false | (Stealthy only) Block WebRTC IP leak |
| `allow_webgl` | bool | true | (Stealthy only) Keep WebGL enabled |
Plus all other browser session parameters (`google_search`, `real_chrome`, `cdp_url`, `locale`, `timezone_id`, `useragent`, `extra_headers`, `cookies`, `disable_resources`, `network_idle`, `wait_selector`, `wait_selector_state`). Plus all other browser session parameters (`google_search`, `real_chrome`, `cdp_url`, `locale`, `timezone_id`, `useragent`, `extra_headers`, `cookies`, `disable_resources`, `network_idle`, `wait_selector`, `wait_selector_state`).
@@ -131,6 +132,25 @@ Returns a list of `SessionInfo` objects, each with `session_id`, `session_type`,
No parameters. No parameters.
### `screenshot` -- Capture a page screenshot
Navigates to a URL inside an existing browser session and returns the screenshot as an MCP `ImageContent` block (the bytes the model can see directly, not a base64 string in JSON) followed by a `TextContent` block carrying the post-redirect URL.
Requires an open browser session. Call `open_session` first, then pass the `session_id` here. Both `dynamic` and `stealthy` sessions are accepted.
| Parameter | Type | Default | Description |
|-----------------------|-----------------------|--------------|--------------------------------------------------------------------------------------|
| `url` | str | required | URL to navigate to and capture |
| `session_id` | str | required | ID of an open browser session created with `open_session` |
| `image_type` | `"png"` / `"jpeg"` | `"png"` | Image format. Use `"jpeg"` for smaller payloads |
| `full_page` | bool | false | Capture the full scrollable page instead of just the viewport |
| `quality` | int or null | null | JPEG quality 0-100. Raises if passed with `image_type="png"` |
| `wait` | number | 0 | Extra wait (ms) after page load before capture |
| `wait_selector` | str or null | null | CSS selector to wait for before capture |
| `wait_selector_state` | str | `"attached"` | State for `wait_selector`: `"attached"` / `"visible"` / `"hidden"` / `"detached"` |
| `network_idle` | bool | false | Wait until no network activity for 500ms |
| `timeout` | number | 30000 | Timeout in milliseconds |
## Tool selection guide ## Tool selection guide
| Scenario | Tool | | Scenario | Tool |
@@ -142,6 +162,7 @@ No parameters.
| Cloudflare or strong anti-bot protection | `stealthy_fetch` (with `solve_cloudflare=true` for Turnstile) | | Cloudflare or strong anti-bot protection | `stealthy_fetch` (with `solve_cloudflare=true` for Turnstile) |
| Multiple protected pages | `bulk_stealthy_fetch` | | Multiple protected pages | `bulk_stealthy_fetch` |
| Multiple pages from the same site | `open_session` + `fetch`/`stealthy_fetch` with `session_id` | | Multiple pages from the same site | `open_session` + `fetch`/`stealthy_fetch` with `session_id` |
| Need a screenshot of a page | `open_session` + `screenshot` with `session_id` |
Start with `get` (fastest, lowest resource cost). Escalate to `fetch` if content requires JS rendering. Escalate to `stealthy_fetch` only if blocked. For multiple pages from the same site, use a persistent session to avoid browser launch overhead. Start with `get` (fastest, lowest resource cost). Escalate to `fetch` if content requires JS rendering. Escalate to `stealthy_fetch` only if blocked. For multiple pages from the same site, use a persistent session to avoid browser launch overhead.
+554
View File
@@ -0,0 +1,554 @@
<!-- mcp-name: io.github.D4Vinci/Scrapling -->
<h1 align="center">
<a href="https://scrapling.readthedocs.io">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/docs/assets/cover_dark.svg?sanitize=true">
<img alt="Scrapling Poster" src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/docs/assets/cover_light.svg?sanitize=true">
</picture>
</a>
<br>
<small>Web Scraping sem esforço para a web moderna</small>
</h1>
<p align="center">
<a href="https://trendshift.io/repositories/14244" target="_blank"><img src="https://trendshift.io/api/badge/repositories/14244" alt="D4Vinci%2FScrapling | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<br/>
<a href="https://github.com/D4Vinci/Scrapling/actions/workflows/tests.yml" alt="Tests">
<img alt="Tests" src="https://github.com/D4Vinci/Scrapling/actions/workflows/tests.yml/badge.svg"></a>
<a href="https://badge.fury.io/py/Scrapling" alt="PyPI version">
<img alt="PyPI version" src="https://badge.fury.io/py/Scrapling.svg"></a>
<a href="https://clickpy.clickhouse.com/dashboard/scrapling" rel="nofollow"><img src="https://img.shields.io/pypi/dm/scrapling" alt="PyPI package downloads"></a>
<a href="https://github.com/D4Vinci/Scrapling/tree/main/agent-skill" alt="AI Agent Skill directory">
<img alt="Static Badge" src="https://img.shields.io/badge/Skill-black?style=flat&label=Agent&link=https%3A%2F%2Fgithub.com%2FD4Vinci%2FScrapling%2Ftree%2Fmain%2Fagent-skill"></a>
<a href="https://clawhub.ai/D4Vinci/scrapling-official" alt="OpenClaw Skill">
<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>
<br/>
<a href="https://discord.gg/EMgGbDceNQ" alt="Discord" target="_blank">
<img alt="Discord" src="https://img.shields.io/discord/1360786381042880532?style=social&logo=discord&link=https%3A%2F%2Fdiscord.gg%2FEMgGbDceNQ">
</a>
<a href="https://x.com/Scrapling_dev" alt="X (formerly Twitter)">
<img alt="X (formerly Twitter) Follow" src="https://img.shields.io/twitter/follow/Scrapling_dev?style=social&logo=x&link=https%3A%2F%2Fx.com%2FScrapling_dev">
</a>
<br/>
<a href="https://pypi.org/project/scrapling/" alt="Supported Python versions">
<img alt="Supported Python versions" src="https://img.shields.io/pypi/pyversions/scrapling.svg"></a>
</p>
<p align="center">
<a href="https://scrapling.readthedocs.io/en/latest/parsing/selection.html"><strong>Métodos de seleção</strong></a>
&middot;
<a href="https://scrapling.readthedocs.io/en/latest/fetching/choosing.html"><strong>Fetchers</strong></a>
&middot;
<a href="https://scrapling.readthedocs.io/en/latest/spiders/architecture.html"><strong>Spiders</strong></a>
&middot;
<a href="https://scrapling.readthedocs.io/en/latest/spiders/proxy-blocking.html"><strong>Rotação de proxy</strong></a>
&middot;
<a href="https://scrapling.readthedocs.io/en/latest/cli/overview.html"><strong>CLI</strong></a>
&middot;
<a href="https://scrapling.readthedocs.io/en/latest/ai/mcp-server.html"><strong>MCP</strong></a>
</p>
Scrapling é um framework adaptativo de Web Scraping que lida com tudo, desde uma única requisição até um crawl em larga escala.
Seu parser aprende com as mudanças nos sites e relocaliza automaticamente seus elementos quando as páginas são atualizadas. Seus fetchers contornam sistemas anti-bot como o Cloudflare Turnstile de forma nativa. E seu framework de spiders permite escalar para crawls concorrentes com múltiplas sessões, pausa/retomada e rotação automática de proxies, tudo em poucas linhas de Python. Uma biblioteca, zero concessões.
Crawls extremamente rápidos com estatísticas em tempo real e streaming. Feito por Web Scrapers para Web Scrapers e usuários comuns, há algo para todo mundo.
```python
from scrapling.fetchers import Fetcher, AsyncFetcher, StealthyFetcher, DynamicFetcher
StealthyFetcher.adaptive = True
p = StealthyFetcher.fetch('https://example.com', headless=True, network_idle=True) # Busque o site sem chamar atenção!
products = p.css('.product', auto_save=True) # Extraia dados que sobrevivem a mudanças no design do site!
products = p.css('.product', adaptive=True) # Depois, se a estrutura do site mudar, passe `adaptive=True` para encontrá-los!
```
Ou escale para crawls completos
```python
from scrapling.spiders import Spider, Response
class MySpider(Spider):
name = "demo"
start_urls = ["https://example.com/"]
async def parse(self, response: Response):
for item in response.css('.product'):
yield {"title": item.css('h2::text').get()}
MySpider().start()
```
<p align="center">
<a href="https://dataimpulse.com/?utm_source=scrapling&utm_medium=banner&utm_campaign=scrapling" target="_blank" style="display:flex; justify-content:center; padding:4px 0;">
<img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/DataImpulse.png" alt="At DataImpulse, we specialize in developing custom proxy services for your business. Make requests from anywhere, collect data, and enjoy fast connections with our premium proxies." style="max-height:60px;">
</a>
</p>
# Patrocinadores Platina
<table>
<tr>
<td width="200">
<a href="https://hypersolutions.co/?utm_source=github&utm_medium=readme&utm_campaign=scrapling" target="_blank" title="Bot Protection Bypass API for Akamai, DataDome, Incapsula & Kasada">
<img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/HyperSolutions.png">
</a>
</td>
<td> Scrapling lida com o Cloudflare Turnstile. Para proteção de nível empresarial, <a href="https://hypersolutions.co?utm_source=github&utm_medium=readme&utm_campaign=scrapling">
<b>Hyper Solutions</b>
</a> oferece endpoints de API que geram tokens antibot válidos para <b>Akamai</b>, <b>DataDome</b>, <b>Kasada</b> e <b>Incapsula</b>. Chamadas simples de API, sem necessidade de automação de navegador. </td>
</tr>
<tr>
<td width="200">
<a href="https://birdproxies.com/t/scrapling" target="_blank" title="At Bird Proxies, we eliminate your pains such as banned IPs, geo restriction, and high costs so you can focus on your work.">
<img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/BirdProxies.jpg">
</a>
</td>
<td>Nós criamos a <a href="https://birdproxies.com/t/scrapling">
<b>BirdProxies</b>
</a> porque proxies não deveriam ser complicados nem caros. Proxies residenciais e ISP rápidos em mais de 195 localidades, preços justos e suporte de verdade. <br />
<b>Experimente nosso jogo FlappyBird na landing page para ganhar dados grátis!</b>
</td>
</tr>
<tr>
<td width="200">
<a href="https://evomi.com?utm_source=github&utm_medium=banner&utm_campaign=d4vinci-scrapling" target="_blank" title="Evomi is your Swiss Quality Proxy Provider, starting at $0.49/GB">
<img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/evomi.png">
</a>
</td>
<td>
<a href="https://evomi.com?utm_source=github&utm_medium=banner&utm_campaign=d4vinci-scrapling">
<b>Evomi</b>
</a>: proxies residenciais a partir de US$0.49/GB. Navegador de scraping com Chromium totalmente spoofado, IPs residenciais, resolução automática de CAPTCHA e bypass anti-bot. </br>
<b>Scraper API para resultados sem complicação. Integrações com MCP e N8N estão disponíveis.</b>
</td>
</tr>
<tr>
<td width="200">
<a href="https://tikhub.io/?utm_source=github.com/D4Vinci/Scrapling&utm_medium=marketing_social&utm_campaign=retargeting&utm_content=carousel_ad" target="_blank" title="Unlock the Power of Social Media Data & AI">
<img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/TikHub.jpg">
</a>
</td>
<td>
<a href="https://tikhub.io/?utm_source=github.com/D4Vinci/Scrapling&utm_medium=marketing_social&utm_campaign=retargeting&utm_content=carousel_ad" target="_blank">TikHub.io</a> oferece mais de 900 APIs estáveis em mais de 16 plataformas, incluindo TikTok, X, YouTube e Instagram, com mais de 40M de datasets. <br /> Também oferece <a href="https://ai.tikhub.io/?ref=KarimShoair" target="_blank">modelos de IA com desconto</a> - Claude, GPT, GEMINI e mais com até 71% de desconto.
</td>
</tr>
<tr>
<td width="200">
<a href="https://www.nsocks.com/?keyword=2p67aivg" target="_blank" title="Scalable Web Data Access for AI Applications">
<img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/nsocks.png">
</a>
</td>
<td>
<a href="https://www.nsocks.com/?keyword=2p67aivg" target="_blank">Nsocks</a> fornece proxies residenciais e ISP rápidos para desenvolvedores e scrapers. Cobertura global de IPs, alto anonimato, rotação inteligente e desempenho confiável para automação e extração de dados. Use o <a href="https://www.xcrawl.com/?keyword=2p67aivg" target="_blank">Xcrawl</a> para simplificar o crawling web em larga escala.
</td>
</tr>
<tr>
<td width="200">
<a href="https://petrosky.io/d4vinci" target="_blank" title="PetroSky delivers cutting-edge VPS hosting.">
<img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/petrosky.png">
</a>
</td>
<td>
Feche o notebook. Seus scrapers continuam rodando. <br />
<a href="https://petrosky.io/d4vinci" target="_blank">PetroSky VPS</a> - servidores em nuvem feitos para automação ininterrupta. Máquinas Windows e Linux com controle total. A partir de €6.99/mês.
</td>
</tr>
<tr>
<td width="200">
<a href="https://substack.thewebscraping.club/p/scrapling-hands-on-guide?utm_source=github&utm_medium=repo&utm_campaign=scrapling" target="_blank" title="The #1 newsletter dedicated to Web Scraping">
<img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/TWSC.png">
</a>
</td>
<td>
Leia uma análise completa do <a href="https://substack.thewebscraping.club/p/scrapling-hands-on-guide?utm_source=github&utm_medium=repo&utm_campaign=scrapling" target="_blank">Scrapling no The Web Scraping Club</a> (nov. 2025), a newsletter número 1 dedicada a Web Scraping.
</td>
</tr>
<tr>
<td width="200">
<a href="https://proxy-seller.com/?partner=CU9CAA5TBYFFT2" target="_blank" title="Proxy-Seller provides reliable proxy infrastructure for Web Scraping">
<img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/ProxySeller.png">
</a>
</td>
<td>
<a href="https://proxy-seller.com/?partner=CU9CAA5TBYFFT2" target="_blank">Proxy-Seller</a> fornece infraestrutura de proxy confiável para web scraping, oferecendo proxies IPv4, IPv6, ISP, residenciais e móveis com desempenho estável, ampla cobertura geográfica e planos flexíveis para coleta de dados em escala empresarial.
</td>
</tr>
<tr>
<td width="200">
<a href="http://mangoproxy.com/?utm_source=D4Vinci&utm_medium=GitHub&utm_campaign=D4Vinci" target="_blank" title="Proxies You Can Rely On: Residential, Server, and Mobile">
<img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/MangoProxy.png">
</a>
</td>
<td>
<a href="http://mangoproxy.com/?utm_source=D4Vinci&utm_medium=GitHub&utm_campaign=D4Vinci" target="_blank">Proxies estáveis</a> para scraping, automação e multi-accounting. IPs limpos, resposta rápida e desempenho confiável sob carga. Feito para fluxos de trabalho escaláveis.
</td>
</tr>
<tr>
<td width="200">
<a href="https://www.swiftproxy.net/?ref=D4Vinci" target="_blank" title="Scalable Solutions for Web Data Access">
<img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/SwiftProxy.png">
</a>
</td>
<td>
<a href="https://www.swiftproxy.net/?ref=D4Vinci" target="_blank">Swiftproxy</a> fornece proxies residenciais escaláveis com mais de 80M de IPs em mais de 195 países, entregando conexões rápidas e confiáveis, rotação automática e forte desempenho anti-bloqueio. Teste grátis disponível.
</td>
</tr>
</table>
<i><sub>Quer mostrar seu anúncio aqui? Clique [aqui](https://github.com/sponsors/D4Vinci/sponsorships?tier_id=586646)</sub></i>
# Patrocinadores
<!-- sponsors -->
<a href="https://serpapi.com/?utm_source=scrapling" target="_blank" title="Scrape Google and other search engines with SerpApi"><img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/SerpApi.png"></a>
<a href="https://visit.decodo.com/Dy6W0b" target="_blank" title="Try the Most Efficient Residential Proxies for Free"><img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/decodo.png"></a>
<a href="https://hasdata.com/?utm_source=github&utm_medium=banner&utm_campaign=D4Vinci" target="_blank" title="The web scraping service that actually beats anti-bot systems!"><img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/hasdata.png"></a>
<a href="https://proxyempire.io/?ref=scrapling&utm_source=scrapling" target="_blank" title="Collect The Data Your Project Needs with the Best Residential Proxies"><img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/ProxyEmpire.png"></a>
<a href="https://www.webshare.io/?referral_code=48r2m2cd5uz1" target="_blank" title="The Most Reliable Proxy with Unparalleled Performance"><img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/webshare.png"></a>
<a href="https://www.crawleo.dev/?utm_source=github&utm_medium=sponsor&utm_campaign=scrapling" target="_blank" title="Supercharge your AI with Real-Time Web Intelligence"><img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/crawleo.png"></a>
<a href="https://www.rapidproxy.io/?ref=d4v" target="_blank" title="Affordable Access to the Proxy World bypass CAPTCHAs blocks, and avoid additional costs."><img src="https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/rapidproxy.jpg"></a>
<!-- /sponsors -->
<i><sub>Quer mostrar seu anúncio aqui? Clique [aqui](https://github.com/sponsors/D4Vinci) e escolha o plano que fizer mais sentido para você!</sub></i>
---
## Principais Recursos
### Spiders - Um Framework Completo de Crawling
- 🕷️ **API de Spider estilo Scrapy**: Defina spiders com `start_urls`, callbacks assíncronos `parse` e objetos `Request`/`Response`.
-**Crawling Concorrente**: Limites de concorrência configuráveis, throttling por domínio e delays de download.
- 🔄 **Suporte Multi-Sessão**: Interface unificada para requisições HTTP e navegadores headless furtivos em uma única spider - direcione requisições para diferentes sessões por ID.
- 💾 **Pausa e Retomada**: Persistência de crawl baseada em checkpoints. Pressione Ctrl+C para um encerramento gracioso; reinicie para continuar de onde parou.
- 📡 **Modo Streaming**: Faça streaming dos itens extraídos conforme chegam com `async for item in spider.stream()` e estatísticas em tempo real - ideal para UI, pipelines e crawls de longa duração.
- 🛡️ **Detecção de Requisições Bloqueadas**: Detecção automática e retry de requisições bloqueadas com lógica personalizável.
- 🤖 **Conformidade com robots.txt**: Flag opcional `robots_txt_obey` que respeita as diretivas `Disallow`, `Crawl-delay` e `Request-rate` com cache por domínio.
- 🧪 **Modo de Desenvolvimento**: Armazene respostas em disco na primeira execução e reproduza-as nas seguintes - itere sobre sua lógica de `parse()` sem reenviar requisições aos servidores-alvo.
- 📦 **Exportação Nativa**: Exporte resultados via hooks, seu próprio pipeline ou JSON/JSONL nativos com `result.items.to_json()` / `result.items.to_jsonl()` respectivamente.
### Busca Avançada de Sites com Suporte a Sessões
- **Requisições HTTP**: Requisições HTTP rápidas e furtivas com a classe `Fetcher`. Pode imitar fingerprint TLS de navegadores, cabeçalhos e usar HTTP/3.
- **Carregamento Dinâmico**: Busque sites dinâmicos com automação completa de navegador através da classe `DynamicFetcher`, compatível com o Chromium do Playwright e o Google Chrome.
- **Bypass Anti-Bot**: Capacidades avançadas de stealth com `StealthyFetcher` e spoofing de fingerprint. Pode contornar facilmente todos os tipos de Turnstile/Interstitial do Cloudflare com automação.
- **Gerenciamento de Sessão**: Suporte a sessões persistentes com as classes `FetcherSession`, `StealthySession` e `DynamicSession` para gerenciar cookies e estado entre requisições.
- **Rotação de Proxy**: `ProxyRotator` nativo com estratégias cíclicas ou personalizadas em todos os tipos de sessão, além de sobrescritas de proxy por requisição.
- **Bloqueio de Domínios e Anúncios**: Bloqueie requisições para domínios específicos (e seus subdomínios) ou habilite o bloqueio nativo de anúncios (~3.500 domínios conhecidos de anúncios/rastreadores) nos fetchers baseados em navegador.
- **Prevenção de Vazamento de DNS**: Suporte opcional a DNS-over-HTTPS para rotear consultas DNS através do DoH da Cloudflare, evitando vazamentos de DNS ao usar proxies.
- **Suporte Async**: Suporte assíncrono completo em todos os fetchers e classes dedicadas de sessão async.
### Scraping Adaptativo e Integração com IA
- 🔄 **Rastreamento Inteligente de Elementos**: Relocalize elementos após mudanças no site usando algoritmos inteligentes de similaridade.
- 🎯 **Seleção Flexível Inteligente**: Seletores CSS, seletores XPath, busca baseada em filtros, busca por texto, busca por regex e muito mais.
- 🔍 **Encontrar Elementos Semelhantes**: Localize automaticamente elementos parecidos com os elementos encontrados.
- 🤖 **Servidor MCP para uso com IA**: Servidor MCP nativo para Web Scraping assistido por IA e extração de dados. O servidor MCP oferece capacidades poderosas e personalizadas que usam o Scrapling para extrair conteúdo direcionado antes de passá-lo à IA (Claude/Cursor/etc), acelerando as operações e reduzindo custos ao minimizar o uso de tokens. ([vídeo demo](https://www.youtube.com/watch?v=qyFk3ZNwOxE))
### Arquitetura de Alto Desempenho e Testada em Batalha
- 🚀 **Muito Rápido**: Desempenho otimizado que supera a maioria das bibliotecas Python de scraping.
- 🔋 **Eficiente em Memória**: Estruturas de dados otimizadas e lazy loading para um uso mínimo de memória.
-**Serialização JSON Rápida**: 10x mais rápido que a biblioteca padrão.
- 🏗️ **Testado em batalha**: O Scrapling não apenas tem 92% de cobertura de testes e cobertura completa de type hints, como também vem sendo usado diariamente por centenas de Web Scrapers ao longo do último ano.
### Experiência Amigável para Desenvolvedores/Web Scrapers
- 🎯 **Shell Interativo de Web Scraping**: Shell opcional embutido em IPython com integração ao Scrapling, atalhos e novas ferramentas para acelerar o desenvolvimento de scripts de Web Scraping, como converter requisições curl em requisições Scrapling e visualizar resultados no navegador.
- 🚀 **Use diretamente no Terminal**: Opcionalmente, você pode usar o Scrapling para extrair uma URL sem escrever uma única linha de código!
- 🛠️ **API Rica de Navegação**: Travessia avançada do DOM com métodos de navegação por pais, irmãos e filhos.
- 🧬 **Processamento de Texto Aprimorado**: Métodos nativos de regex, limpeza e operações de string otimizadas.
- 📝 **Geração Automática de Seletores**: Gere seletores CSS/XPath robustos para qualquer elemento.
- 🔌 **API Familiar**: Semelhante a Scrapy/BeautifulSoup, com os mesmos pseudo-elementos usados em Scrapy/Parsel.
- 📘 **Cobertura Completa de Tipos**: Type hints completos para excelente suporte em IDEs e autocompletar de código. Todo o codebase é escaneado automaticamente com **PyRight** e **MyPy** a cada alteração.
- 🔋 **Imagem Docker Pronta**: A cada release, uma imagem Docker contendo todos os navegadores é construída e publicada automaticamente.
## Primeiros Passos
Vamos dar uma visão rápida do que o Scrapling pode fazer sem entrar em muitos detalhes.
### Uso Básico
Requisições HTTP com suporte a sessões
```python
from scrapling.fetchers import Fetcher, FetcherSession
with FetcherSession(impersonate='chrome') as session: # Use a versão mais recente da fingerprint TLS do Chrome
page = session.get('https://quotes.toscrape.com/', stealthy_headers=True)
quotes = page.css('.quote .text::text').getall()
# Ou use requisições avulsas
page = Fetcher.get('https://quotes.toscrape.com/')
quotes = page.css('.quote .text::text').getall()
```
Modo stealth avançado
```python
from scrapling.fetchers import StealthyFetcher, StealthySession
with StealthySession(headless=True, solve_cloudflare=True) as session: # Mantenha o navegador aberto até terminar
page = session.fetch('https://nopecha.com/demo/cloudflare', google_search=False)
data = page.css('#padded_content a').getall()
# Ou use o estilo de requisição avulsa, ele abre o navegador para esta requisição e o fecha ao finalizar
page = StealthyFetcher.fetch('https://nopecha.com/demo/cloudflare')
data = page.css('#padded_content a').getall()
```
Automação completa de navegador
```python
from scrapling.fetchers import DynamicFetcher, DynamicSession
with DynamicSession(headless=True, disable_resources=False, network_idle=True) as session: # Mantenha o navegador aberto até terminar
page = session.fetch('https://quotes.toscrape.com/', load_dom=False)
data = page.xpath('//span[@class="text"]/text()').getall() # Se preferir, use seletor XPath
# Ou use o estilo de requisição avulsa, ele abre o navegador para esta requisição e o fecha ao finalizar
page = DynamicFetcher.fetch('https://quotes.toscrape.com/')
data = page.css('.quote .text::text').getall()
```
### Spiders
Construa crawlers completos com requisições concorrentes, múltiplos tipos de sessão e pausa/retomada:
```python
from scrapling.spiders import Spider, Request, Response
class QuotesSpider(Spider):
name = "quotes"
start_urls = ["https://quotes.toscrape.com/"]
concurrent_requests = 10
async def parse(self, response: Response):
for quote in response.css('.quote'):
yield {
"text": quote.css('.text::text').get(),
"author": quote.css('.author::text').get(),
}
next_page = response.css('.next a')
if next_page:
yield response.follow(next_page[0].attrib['href'])
result = QuotesSpider().start()
print(f"Extraídas {len(result.items)} citações")
result.items.to_json("quotes.json")
```
Use múltiplos tipos de sessão em uma única spider:
```python
from scrapling.spiders import Spider, Request, Response
from scrapling.fetchers import FetcherSession, AsyncStealthySession
class MultiSessionSpider(Spider):
name = "multi"
start_urls = ["https://example.com/"]
def configure_sessions(self, manager):
manager.add("fast", FetcherSession(impersonate="chrome"))
manager.add("stealth", AsyncStealthySession(headless=True), lazy=True)
async def parse(self, response: Response):
for link in response.css('a::attr(href)').getall():
# Direcione páginas protegidas através da sessão stealth
if "protected" in link:
yield Request(link, sid="stealth")
else:
yield Request(link, sid="fast", callback=self.parse) # callback explícito
```
Pause e retome crawls longos com checkpoints executando a spider assim:
```python
QuotesSpider(crawldir="./crawl_data").start()
```
Pressione Ctrl+C para pausar de forma graciosa - o progresso é salvo automaticamente. Depois, quando você iniciar a spider novamente, passe o mesmo `crawldir` e ela continuará de onde parou.
### Parsing Avançado e Navegação
```python
from scrapling.fetchers import Fetcher
# Seleção rica de elementos e navegação
page = Fetcher.get('https://quotes.toscrape.com/')
# Obtenha citações com múltiplos métodos de seleção
quotes = page.css('.quote') # Seletor CSS
quotes = page.xpath('//div[@class="quote"]') # XPath
quotes = page.find_all('div', {'class': 'quote'}) # Estilo BeautifulSoup
# O mesmo que
quotes = page.find_all('div', class_='quote')
quotes = page.find_all(['div'], class_='quote')
quotes = page.find_all(class_='quote') # e assim por diante...
# Encontre elementos por conteúdo de texto
quotes = page.find_by_text('quote', tag='div')
# Navegação avançada
quote_text = page.css('.quote')[0].css('.text::text').get()
quote_text = page.css('.quote').css('.text::text').getall() # Seletores encadeados
first_quote = page.css('.quote')[0]
author = first_quote.next_sibling.css('.author::text')
parent_container = first_quote.parent
# Relações e similaridade entre elementos
similar_elements = first_quote.find_similar()
below_elements = first_quote.below_elements()
```
Você pode usar o parser imediatamente se não quiser buscar sites, como abaixo:
```python
from scrapling.parser import Selector
page = Selector("<html>...</html>")
```
E ele funciona exatamente da mesma maneira!
### Exemplos de Gerenciamento de Sessão Assíncrona
```python
import asyncio
from scrapling.fetchers import FetcherSession, AsyncStealthySession, AsyncDynamicSession
async with FetcherSession(http3=True) as session: # `FetcherSession` entende o contexto e funciona tanto em padrões sync quanto async
page1 = session.get('https://quotes.toscrape.com/')
page2 = session.get('https://quotes.toscrape.com/', impersonate='firefox135')
# Uso de sessão assíncrona
async with AsyncStealthySession(max_pages=2) as session:
tasks = []
urls = ['https://example.com/page1', 'https://example.com/page2']
for url in urls:
task = session.fetch(url)
tasks.append(task)
print(session.get_pool_stats()) # Opcional - O estado do pool de abas do navegador (ocupada/livre/erro)
results = await asyncio.gather(*tasks)
print(session.get_pool_stats())
```
## CLI e Shell Interativo
O Scrapling inclui uma poderosa interface de linha de comando:
[![asciicast](https://asciinema.org/a/736339.svg)](https://asciinema.org/a/736339)
Inicie o shell interativo de Web Scraping
```bash
scrapling shell
```
Extraia páginas diretamente para um arquivo sem programar (por padrão, extrai o conteúdo dentro da tag `body`). Se o arquivo de saída terminar com `.txt`, então o conteúdo em texto do alvo será extraído. Se terminar com `.md`, será uma representação em Markdown do conteúdo HTML; se terminar com `.html`, será o próprio conteúdo HTML.
```bash
scrapling extract get 'https://example.com' content.md
scrapling extract get 'https://example.com' content.txt --css-selector '#fromSkipToProducts' --impersonate 'chrome' # Todos os elementos que correspondem ao seletor CSS '#fromSkipToProducts'
scrapling extract fetch 'https://example.com' content.md --css-selector '#fromSkipToProducts' --no-headless
scrapling extract stealthy-fetch 'https://nopecha.com/demo/cloudflare' captchas.html --css-selector '#padded_content a' --solve-cloudflare
```
> [!NOTE]
> Existem muitos recursos adicionais, mas queremos manter esta página concisa, incluindo o servidor MCP e o Shell Interativo de Web Scraping. Confira a documentação completa [aqui](https://scrapling.readthedocs.io/en/latest/)
## Benchmarks de Desempenho
O Scrapling não é apenas poderoso - ele também é extremamente rápido. Os benchmarks abaixo comparam o parser do Scrapling com as versões mais recentes de outras bibliotecas populares.
### Teste de Velocidade de Extração de Texto (5000 elementos aninhados)
| # | Biblioteca | Tempo (ms) | vs Scrapling |
|---|:-----------------:|:----------:|:------------:|
| 1 | Scrapling | 2.02 | 1.0x |
| 2 | Parsel/Scrapy | 2.04 | 1.01 |
| 3 | Raw Lxml | 2.54 | 1.257 |
| 4 | PyQuery | 24.17 | ~12x |
| 5 | Selectolax | 82.63 | ~41x |
| 6 | MechanicalSoup | 1549.71 | ~767.1x |
| 7 | BS4 with Lxml | 1584.31 | ~784.3x |
| 8 | BS4 with html5lib | 3391.91 | ~1679.1x |
### Desempenho de Similaridade de Elementos e Busca por Texto
Os recursos de localização adaptativa de elementos do Scrapling superam significativamente as alternativas:
| Biblioteca | Tempo (ms) | vs Scrapling |
|-------------|:----------:|:------------:|
| Scrapling | 2.39 | 1.0x |
| AutoScraper | 12.45 | 5.209x |
> Todos os benchmarks representam médias de 100+ execuções. Veja [benchmarks.py](https://github.com/D4Vinci/Scrapling/blob/main/benchmarks.py) para a metodologia.
## Instalação
O Scrapling requer Python 3.10 ou superior:
```bash
pip install scrapling
```
Esta instalação inclui apenas o motor de parsing e suas dependências, sem fetchers nem dependências de linha de comando.
### Dependências Opcionais
1. Se você vai usar qualquer um dos recursos extras abaixo, os fetchers ou suas classes, precisará instalar as dependências dos fetchers e as dependências de navegador deles da seguinte forma:
```bash
pip install "scrapling[fetchers]"
scrapling install # instalação normal
scrapling install --force # forçar reinstalação
```
Isso baixa todos os navegadores, juntamente com suas dependências de sistema e dependências de manipulação de fingerprint.
Ou você pode instalá-los a partir do código em vez de executar um comando como este:
```python
from scrapling.cli import install
install([], standalone_mode=False) # instalação normal
install(["--force"], standalone_mode=False) # forçar reinstalação
```
2. Recursos extras:
- Instale o recurso do servidor MCP:
```bash
pip install "scrapling[ai]"
```
- Instale os recursos do shell (Shell de Web Scraping e o comando `extract`):
```bash
pip install "scrapling[shell]"
```
- Instale tudo:
```bash
pip install "scrapling[all]"
```
Lembre-se de que você precisa instalar as dependências de navegador com `scrapling install` depois de qualquer um desses extras (caso ainda não tenha feito isso)
### Docker
Você também pode baixar uma imagem Docker com todos os extras e navegadores com o seguinte comando a partir do DockerHub:
```bash
docker pull pyd4vinci/scrapling
```
Ou baixá-la do registro do GitHub:
```bash
docker pull ghcr.io/d4vinci/scrapling:latest
```
Essa imagem é construída e publicada automaticamente usando GitHub Actions e o branch principal do repositório.
## Contribuindo
Contribuições são bem-vindas! Leia nossas [diretrizes de contribuição](https://github.com/D4Vinci/Scrapling/blob/main/CONTRIBUTING.md) antes de começar.
## Aviso Legal
> [!CAUTION]
> Esta biblioteca é fornecida apenas para fins educacionais e de pesquisa. Ao usar esta biblioteca, você concorda em cumprir as leis locais e internacionais de scraping de dados e privacidade. Os autores e contribuidores não se responsabilizam por qualquer uso indevido deste software. Sempre respeite os termos de serviço dos sites e os arquivos robots.txt.
## 🎓 Citações
Se você usou nossa biblioteca para fins de pesquisa, cite-nos com a seguinte referência:
```text
@misc{scrapling,
author = {Karim Shoair},
title = {Scrapling},
year = {2024},
url = {https://github.com/D4Vinci/Scrapling},
note = {An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!}
}
```
## Licença
Este trabalho está licenciado sob a licença BSD-3-Clause.
## Agradecimentos
Este projeto inclui código adaptado de:
- Parsel (Licença BSD) - usado para o submódulo [translator](https://github.com/D4Vinci/Scrapling/blob/main/scrapling/core/translator.py)
---
<div align="center"><small>Projetado e desenvolvido com ❤️ por Karim Shoair.</small></div><br>
+12 -1
View File
@@ -6,7 +6,7 @@ The **Scrapling MCP Server** is a new feature that brings Scrapling's powerful W
## Features ## Features
The Scrapling MCP Server provides nine powerful tools for web scraping: The Scrapling MCP Server provides ten powerful tools for web scraping:
### 🚀 Basic HTTP Scraping ### 🚀 Basic HTTP Scraping
- **`get`**: Fast HTTP requests with browser fingerprint impersonation, generating real browser headers matching the TLS version, HTTP/3, and more! - **`get`**: Fast HTTP requests with browser fingerprint impersonation, generating real browser headers matching the TLS version, HTTP/3, and more!
@@ -20,6 +20,9 @@ The Scrapling MCP Server provides nine powerful tools for web scraping:
- **`stealthy_fetch`**: Uses our Stealthy browser to bypass Cloudflare Turnstile/Interstitial and other anti-bot systems with complete control over the request/browser! - **`stealthy_fetch`**: Uses our Stealthy browser to bypass Cloudflare Turnstile/Interstitial and other anti-bot systems with complete control over the request/browser!
- **`bulk_stealthy_fetch`**: An async version of the above tool that allows stealth scraping of multiple URLs in different browser tabs at the same time! - **`bulk_stealthy_fetch`**: An async version of the above tool that allows stealth scraping of multiple URLs in different browser tabs at the same time!
### 📸 Screenshots
- **`screenshot`**: Capture a PNG or JPEG screenshot of a page using an open browser session, returned as an image content block the model can actually see (not a base64 string blob). Supports full-page captures, JPEG quality, and the usual readiness controls (`wait`, `wait_selector`, `network_idle`).
### 🔌 Session Management ### 🔌 Session Management
- **`open_session`**: Create a persistent browser session (dynamic or stealthy) that stays open across multiple fetch calls, avoiding the overhead of launching a new browser each time. - **`open_session`**: Create a persistent browser session (dynamic or stealthy) that stays open across multiple fetch calls, avoiding the overhead of launching a new browser each time.
- **`close_session`**: Close a persistent browser session and free its resources. - **`close_session`**: Close a persistent browser session and free its resources.
@@ -331,6 +334,14 @@ This protection runs automatically on all MCP tool responses. Keep `main_content
- Always close sessions with `close_session` when done to free resources - Always close sessions with `close_session` when done to free resources
- Use `list_sessions` to check which sessions are still active - Use `list_sessions` to check which sessions are still active
- A `session_id` from a dynamic session can only be used with `fetch`/`bulk_fetch`, and a stealthy session can only be used with `stealthy_fetch`/`bulk_stealthy_fetch` - A `session_id` from a dynamic session can only be used with `fetch`/`bulk_fetch`, and a stealthy session can only be used with `stealthy_fetch`/`bulk_stealthy_fetch`
- Pass a custom `session_id` to `open_session` to give sessions meaningful names (e.g. `"search"`, `"checkout"`) instead of the random hex default. `open_session` raises if the chosen ID is already in use, so you can detect collisions up front
### 7. Capturing Screenshots
- `screenshot` only works through an existing browser session, so call `open_session` first (either `dynamic` or `stealthy` works)
- The image is returned as a real `ImageContent` block, not a base64 string in JSON, so the model sees the page directly
- Use `full_page=True` when you need everything below the fold; the default captures only the visible viewport
- Pick `image_type="jpeg"` with a `quality` value (0-100) for smaller payloads when pixel-perfect color isn't needed
- The same `wait`, `wait_selector`, `network_idle`, and `timeout` controls used by `fetch` are available here too
## Legal and Ethical Considerations ## Legal and Ethical Considerations
+3 -3
View File
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "scrapling" name = "scrapling"
# Static version instead of a dynamic version so we can get better layer caching while building docker, check the docker file to understand # Static version instead of a dynamic version so we can get better layer caching while building docker, check the docker file to understand
version = "0.4.6" version = "0.4.7"
description = "Scrapling is an undetectable, powerful, flexible, high-performance Python library that makes Web Scraping easy and effortless as it should be!" description = "Scrapling is an undetectable, powerful, flexible, high-performance Python library that makes Web Scraping easy and effortless as it should be!"
readme = {file = "README.md", content-type = "text/markdown"} readme = {file = "README.md", content-type = "text/markdown"}
license = {file = "LICENSE"} license = {file = "LICENSE"}
@@ -77,8 +77,8 @@ fetchers = [
"patchright==1.58.2", "patchright==1.58.2",
"browserforge>=1.2.4", "browserforge>=1.2.4",
"apify-fingerprint-datapoints>=0.12.0", "apify-fingerprint-datapoints>=0.12.0",
"msgspec>=0.21.0", "msgspec>=0.21.1",
"anyio>=4.12.1", "anyio>=4.13.0",
"protego>=0.6.0", "protego>=0.6.0",
] ]
ai = [ ai = [
+1 -1
View File
@@ -1,5 +1,5 @@
__author__ = "Karim Shoair (karim.shoair@pm.me)" __author__ = "Karim Shoair (karim.shoair@pm.me)"
__version__ = "0.4.6" __version__ = "0.4.7"
__copyright__ = "Copyright (c) 2024 Karim Shoair" __copyright__ = "Copyright (c) 2024 Karim Shoair"
from typing import Any, TYPE_CHECKING from typing import Any, TYPE_CHECKING
+83 -7
View File
@@ -3,7 +3,8 @@ from asyncio import gather
from datetime import datetime, timezone from datetime import datetime, timezone
from dataclasses import dataclass, field from dataclasses import dataclass, field
from mcp.server.fastmcp import FastMCP from mcp.server.fastmcp import FastMCP, Image
from mcp.types import ImageContent, TextContent
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from scrapling.core.shell import Convertor from scrapling.core.shell import Convertor
@@ -31,6 +32,7 @@ from scrapling.core._types import (
) )
SessionType = Literal["dynamic", "stealthy"] SessionType = Literal["dynamic", "stealthy"]
ScreenshotType = Literal["png", "jpeg"]
class ResponseModel(BaseModel): class ResponseModel(BaseModel):
@@ -106,14 +108,14 @@ class ScraplingMCPServer:
def __init__(self): def __init__(self):
self._sessions: Dict[str, _SessionEntry] = {} self._sessions: Dict[str, _SessionEntry] = {}
def _get_session(self, session_id: str, expected_type: SessionType) -> _SessionEntry: def _get_session(self, session_id: str, expected_type: Optional[SessionType]) -> _SessionEntry:
"""Look up a session by ID and validate its type.""" """Look up a session by ID, optionally validating its type. Pass `None` to skip the type check."""
entry = self._sessions.get(session_id) entry = self._sessions.get(session_id)
if entry is None: if entry is None:
raise ValueError(f"Session '{session_id}' not found. Use list_sessions to see active sessions.") raise ValueError(f"Session '{session_id}' not found. Use list_sessions to see active sessions.")
if not entry.session._is_alive: if not entry.session._is_alive:
raise ValueError(f"Session '{session_id}' is no longer alive. Open a new session.") raise ValueError(f"Session '{session_id}' is no longer alive. Open a new session.")
if entry.session_type != expected_type: if expected_type is not None and entry.session_type != expected_type:
raise ValueError( raise ValueError(
f"Session '{session_id}' is a '{entry.session_type}' session, but this tool requires a " f"Session '{session_id}' is a '{entry.session_type}' session, but this tool requires a "
f"'{expected_type}' session. Use the matching fetch tool for your session type." f"'{expected_type}' session. Use the matching fetch tool for your session type."
@@ -123,6 +125,7 @@ class ScraplingMCPServer:
async def open_session( async def open_session(
self, self,
session_type: SessionType, session_type: SessionType,
session_id: Optional[str] = None,
headless: bool = True, headless: bool = True,
google_search: bool = True, google_search: bool = True,
real_chrome: bool = False, real_chrome: bool = False,
@@ -152,6 +155,7 @@ class ScraplingMCPServer:
Use close_session to close the session when done, and list_sessions to see all active sessions. Use close_session to close the session when done, and list_sessions to see all active sessions.
:param session_type: The type of session to open. Use "dynamic" for standard Playwright browser, or "stealthy" for anti-bot bypass with fingerprint spoofing. :param session_type: The type of session to open. Use "dynamic" for standard Playwright browser, or "stealthy" for anti-bot bypass with fingerprint spoofing.
:param session_id: Optional custom session ID. If not provided, a random 12-character hex ID will be generated. Useful for naming sessions for easier management.
:param headless: Run the browser in headless/hidden (default), or headful/visible mode. :param headless: Run the browser in headless/hidden (default), or headful/visible mode.
:param google_search: Enabled by default, Scrapling will set a Google referer header. :param google_search: Enabled by default, Scrapling will set a Google referer header.
:param real_chrome: If you have a Chrome browser installed on your device, enable this, and the Fetcher will launch an instance of your browser and use it. :param real_chrome: If you have a Chrome browser installed on your device, enable this, and the Fetcher will launch an instance of your browser and use it.
@@ -175,6 +179,12 @@ class ScraplingMCPServer:
:param solve_cloudflare: (Stealthy only) Solves all types of the Cloudflare's Turnstile/Interstitial challenges. :param solve_cloudflare: (Stealthy only) Solves all types of the Cloudflare's Turnstile/Interstitial challenges.
:param additional_args: (Stealthy only) Additional arguments to be passed to Playwright's context as additional settings. :param additional_args: (Stealthy only) Additional arguments to be passed to Playwright's context as additional settings.
""" """
session_id = session_id or uuid4().hex[:12]
if session_id in self._sessions:
raise ValueError(
f"Session '{session_id}' already exists. Use a different ID or close the existing session first."
)
common_kwargs: Dict[str, Any] = dict( common_kwargs: Dict[str, Any] = dict(
wait=wait, wait=wait,
proxy=proxy, proxy=proxy,
@@ -211,7 +221,6 @@ class ScraplingMCPServer:
await session.start() await session.start()
session_id = uuid4().hex[:12]
entry = _SessionEntry(session=session, session_type=session_type) entry = _SessionEntry(session=session, session_type=session_type)
self._sessions[session_id] = entry self._sessions[session_id] = entry
@@ -253,6 +262,69 @@ class ScraplingMCPServer:
for sid, entry in self._sessions.items() for sid, entry in self._sessions.items()
] ]
async def screenshot(
self,
url: str,
session_id: str,
image_type: ScreenshotType = "png",
full_page: bool = False,
quality: Optional[int] = None,
wait: int | float = 0,
wait_selector: Optional[str] = None,
wait_selector_state: SelectorWaitStates = "attached",
network_idle: bool = False,
timeout: int | float = 30000,
) -> List[ImageContent | TextContent]:
"""Capture a screenshot of a web page using an existing browser session and return it as an image.
A browser session must be opened first with `open_session` (either `dynamic` or `stealthy`); the session ID is then passed here.
:param url: The URL to navigate to and capture.
:param session_id: ID of an open browser session created with `open_session`.
:param image_type: Image format. Defaults to "png". Use "jpeg" for smaller file sizes.
:param full_page: When True, captures the full scrollable page instead of just the viewport. Defaults to False.
:param quality: Image quality (0-100) for JPEG only. Raises if passed with `image_type="png"`.
:param wait: Time in milliseconds to wait after page load before capturing. Defaults to 0.
:param wait_selector: Optional CSS selector to wait for before capturing.
:param wait_selector_state: State to wait for the selector. Defaults to "attached".
:param network_idle: Wait for the page until there are no network connections for at least 500 ms.
:param timeout: Timeout in milliseconds for page operations. Defaults to 30,000.
"""
if quality is not None and image_type != "jpeg":
raise ValueError("'quality' is only valid when 'image_type' is 'jpeg'.")
entry = self._get_session(session_id, expected_type=None)
screenshot_kwargs: Dict[str, Any] = {"type": image_type, "full_page": full_page}
if quality is not None:
screenshot_kwargs["quality"] = quality
captured: Dict[str, Any] = {}
async def _capture(page: Any) -> None:
try:
captured["bytes"] = await page.screenshot(**screenshot_kwargs)
captured["url"] = page.url
except Exception as exc:
captured["error"] = exc
await entry.session.fetch(
url,
wait=wait,
timeout=timeout,
network_idle=network_idle,
wait_selector=wait_selector,
wait_selector_state=wait_selector_state,
page_action=_capture,
)
if "error" in captured:
raise captured["error"]
if "bytes" not in captured:
raise RuntimeError(f"Failed to capture screenshot for {url}")
image = Image(data=captured["bytes"], format=image_type).to_image_content()
return [image, TextContent(type="text", text=captured["url"])]
@staticmethod @staticmethod
async def get( async def get(
url: str, url: str,
@@ -291,7 +363,8 @@ class ScraplingMCPServer:
:param headers: Headers to include in the request. :param headers: Headers to include in the request.
:param cookies: Cookies to use in the request. :param cookies: Cookies to use in the request.
:param timeout: Number of seconds to wait before timing out. :param timeout: Number of seconds to wait before timing out.
:param follow_redirects: Whether to follow redirects. Defaults to "safe", which follows redirects but rejects those targeting internal/private IPs (SSRF protection). Pass True to follow all redirects without restriction. :param follow_redirects: Whether to follow redirects. Defaults to "safe", which follows redirects but rejects those targeting internal/private IPs (SSRF protection).
Pass True to follow all redirects without restriction.
:param max_redirects: Maximum number of redirects. Default 30, use -1 for unlimited. :param max_redirects: Maximum number of redirects. Default 30, use -1 for unlimited.
:param retries: Number of retry attempts. Defaults to 3. :param retries: Number of retry attempts. Defaults to 3.
:param retry_delay: Number of seconds to wait between retry attempts. Defaults to 1 second. :param retry_delay: Number of seconds to wait between retry attempts. Defaults to 1 second.
@@ -364,7 +437,8 @@ class ScraplingMCPServer:
:param headers: Headers to include in the request. :param headers: Headers to include in the request.
:param cookies: Cookies to use in the request. :param cookies: Cookies to use in the request.
:param timeout: Number of seconds to wait before timing out. :param timeout: Number of seconds to wait before timing out.
:param follow_redirects: Whether to follow redirects. Defaults to "safe", which follows redirects but rejects those targeting internal/private IPs (SSRF protection). Pass True to follow all redirects without restriction. :param follow_redirects: Whether to follow redirects. Defaults to "safe", which follows redirects but rejects those targeting internal/private IPs (SSRF protection).
Pass True to follow all redirects without restriction.
:param max_redirects: Maximum number of redirects. Default 30, use -1 for unlimited. :param max_redirects: Maximum number of redirects. Default 30, use -1 for unlimited.
:param retries: Number of retry attempts. Defaults to 3. :param retries: Number of retry attempts. Defaults to 3.
:param retry_delay: Number of seconds to wait between retry attempts. Defaults to 1 second. :param retry_delay: Number of seconds to wait between retry attempts. Defaults to 1 second.
@@ -828,4 +902,6 @@ class ScraplingMCPServer:
description=self.bulk_stealthy_fetch.__doc__, description=self.bulk_stealthy_fetch.__doc__,
structured_output=True, structured_output=True,
) )
# Screenshot tool (returns image + url content blocks, not structured JSON)
server.add_tool(self.screenshot, title="screenshot", description=self.screenshot.__doc__)
server.run(transport="stdio" if not http else "streamable-http") server.run(transport="stdio" if not http else "streamable-http")
+13 -2
View File
@@ -149,6 +149,7 @@ class _ConfigurationLogic(ABC):
# Browser session params (ignored by HTTP sessions) # Browser session params (ignored by HTTP sessions)
"extra_headers", "extra_headers",
"google_search", "google_search",
"block_ads",
} }
for k, v in method_kwargs.items(): for k, v in method_kwargs.items():
if k not in skip_keys and v is not None: if k not in skip_keys and v is not None:
@@ -718,8 +719,13 @@ class FetcherSession:
config["selector_config"] = self.selector_config config["selector_config"] = self.selector_config
config["proxy_rotator"] = self._proxy_rotator config["proxy_rotator"] = self._proxy_rotator
self._client = _SyncSessionLogic(**config) self._client = _SyncSessionLogic(**config)
try:
result = self._client.__enter__()
except Exception:
self._client = None
raise
self._is_alive = True self._is_alive = True
return self._client.__enter__() return result
raise RuntimeError("This FetcherSession instance already has an active synchronous session.") raise RuntimeError("This FetcherSession instance already has an active synchronous session.")
def __exit__(self, exc_type, exc_val, exc_tb): def __exit__(self, exc_type, exc_val, exc_tb):
@@ -739,8 +745,13 @@ class FetcherSession:
config["selector_config"] = self.selector_config config["selector_config"] = self.selector_config
config["proxy_rotator"] = self._proxy_rotator config["proxy_rotator"] = self._proxy_rotator
self._client = _ASyncSessionLogic(**config) self._client = _ASyncSessionLogic(**config)
try:
result = await self._client.__aenter__()
except Exception:
self._client = None
raise
self._is_alive = True self._is_alive = True
return await self._client.__aenter__() return result
raise RuntimeError("This FetcherSession instance already has an active asynchronous session.") raise RuntimeError("This FetcherSession instance already has an active asynchronous session.")
async def __aexit__(self, exc_type, exc_val, exc_tb): async def __aexit__(self, exc_type, exc_val, exc_tb):
+3 -1
View File
@@ -93,7 +93,9 @@ class SessionManager:
async def close(self) -> None: async def close(self) -> None:
"""Close all registered sessions.""" """Close all registered sessions."""
for session in self._sessions.values(): for sid, session in self._sessions.items():
if sid in self._lazy_sessions and not session._is_alive:
continue
_ = await session.__aexit__(None, None, None) _ = await session.__aexit__(None, None, None)
self._started = False self._started = False
+2 -2
View File
@@ -14,12 +14,12 @@
"mimeType": "image/png" "mimeType": "image/png"
} }
], ],
"version": "0.4.6", "version": "0.4.7",
"packages": [ "packages": [
{ {
"registryType": "pypi", "registryType": "pypi",
"identifier": "scrapling", "identifier": "scrapling",
"version": "0.4.6", "version": "0.4.7",
"runtimeHint": "uvx", "runtimeHint": "uvx",
"packageArguments": [ "packageArguments": [
{ {
+1 -1
View File
@@ -1,6 +1,6 @@
[metadata] [metadata]
name = scrapling name = scrapling
version = 0.4.6 version = 0.4.7
author = Karim Shoair author = Karim Shoair
author_email = karim.shoair@pm.me author_email = karim.shoair@pm.me
description = Scrapling is an undetectable, powerful, flexible, high-performance Python library that makes Web Scraping easy and effortless as it should be! description = Scrapling is an undetectable, powerful, flexible, high-performance Python library that makes Web Scraping easy and effortless as it should be!
+138
View File
@@ -1,5 +1,12 @@
import base64
import struct
from contextlib import contextmanager
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from threading import Thread
import pytest import pytest
import pytest_httpbin import pytest_httpbin
from mcp.types import ImageContent, TextContent
from scrapling.core.ai import ( from scrapling.core.ai import (
ScraplingMCPServer, ScraplingMCPServer,
@@ -177,6 +184,137 @@ class TestSessionManagement:
with pytest.raises(ValueError, match="not found"): with pytest.raises(ValueError, match="not found"):
await server.fetch(url=test_url, session_id=session_id) await server.fetch(url=test_url, session_id=session_id)
@pytest.mark.asyncio
async def test_open_session_with_custom_id(self, server):
"""Test opening a session with a custom session_id"""
result = await server.open_session(session_type="dynamic", session_id="my-session", headless=True)
assert isinstance(result, SessionCreatedModel)
assert result.session_id == "my-session"
await server.close_session("my-session")
@pytest.mark.asyncio
async def test_open_session_duplicate_id_raises(self, server):
"""Test that opening a session with a duplicate session_id raises an error"""
await server.open_session(session_type="dynamic", session_id="dupe", headless=True)
with pytest.raises(ValueError, match="already exists"):
await server.open_session(session_type="dynamic", session_id="dupe", headless=True)
await server.close_session("dupe")
def _png_height(data: bytes) -> int:
"""Read the height field from a PNG IHDR chunk."""
return struct.unpack(">I", data[20:24])[0]
@contextmanager
def _serve_html(body: bytes):
"""Serve a fixed HTML body on localhost, yielding its URL."""
class _Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *args, **kwargs):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), _Handler)
thread = Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
yield f"http://127.0.0.1:{server.server_address[1]}/"
finally:
server.shutdown()
server.server_close()
@pytest_httpbin.use_class_based_httpbin
class TestScreenshot:
"""Test the screenshot tool"""
@pytest.fixture(scope="class")
def test_url(self, httpbin):
return f"{httpbin.url}/html"
@pytest.fixture
def server(self):
return ScraplingMCPServer()
@pytest.mark.asyncio
async def test_screenshot_png_with_dynamic_session(self, server, test_url):
"""PNG screenshot via a dynamic session returns image and url content blocks"""
opened = await server.open_session(session_type="dynamic", headless=True)
try:
result = await server.screenshot(url=test_url, session_id=opened.session_id)
assert isinstance(result, list) and len(result) == 2
assert isinstance(result[0], ImageContent)
assert result[0].mimeType == "image/png"
assert isinstance(result[1], TextContent)
assert result[1].text == test_url
finally:
await server.close_session(opened.session_id)
@pytest.mark.asyncio
async def test_screenshot_jpeg_with_quality(self, server, test_url):
"""JPEG screenshot with quality parameter via a dynamic session"""
opened = await server.open_session(session_type="dynamic", headless=True)
try:
result = await server.screenshot(url=test_url, session_id=opened.session_id, image_type="jpeg", quality=80)
assert isinstance(result[0], ImageContent)
assert result[0].mimeType == "image/jpeg"
finally:
await server.close_session(opened.session_id)
@pytest.mark.asyncio
async def test_screenshot_with_stealthy_session(self, server, test_url):
"""PNG screenshot via a stealthy session"""
opened = await server.open_session(session_type="stealthy", headless=True)
try:
result = await server.screenshot(url=test_url, session_id=opened.session_id)
assert isinstance(result[0], ImageContent)
assert result[0].mimeType == "image/png"
finally:
await server.close_session(opened.session_id)
@pytest.mark.asyncio
async def test_screenshot_full_page_taller_than_viewport(self, server):
"""full_page=True produces an image taller than the viewport-only capture"""
tall_html = b"<html><body><div style='height:5000px;background:#abc'></div></body></html>"
with _serve_html(tall_html) as tall_url:
opened = await server.open_session(session_type="dynamic", headless=True)
try:
viewport_result = await server.screenshot(url=tall_url, session_id=opened.session_id, full_page=False)
full_result = await server.screenshot(url=tall_url, session_id=opened.session_id, full_page=True)
viewport_png = base64.b64decode(viewport_result[0].data)
full_png = base64.b64decode(full_result[0].data)
assert _png_height(full_png) > _png_height(viewport_png)
finally:
await server.close_session(opened.session_id)
@pytest.mark.asyncio
async def test_screenshot_invalid_session_id_raises(self, server, test_url):
"""Unknown session_id raises ValueError"""
with pytest.raises(ValueError, match="not found"):
await server.screenshot(url=test_url, session_id="does-not-exist")
@pytest.mark.asyncio
async def test_screenshot_quality_with_png_raises(self, server, test_url):
"""quality is rejected when image_type is png"""
opened = await server.open_session(session_type="dynamic", headless=True)
try:
with pytest.raises(ValueError, match="quality"):
await server.screenshot(url=test_url, session_id=opened.session_id, image_type="png", quality=90)
finally:
await server.close_session(opened.session_id)
class TestNormalizeCredentials: class TestNormalizeCredentials:
"""Test the _normalize_credentials helper""" """Test the _normalize_credentials helper"""
+44
View File
@@ -0,0 +1,44 @@
"""Tests for _merge_request_args to ensure browser-only kwargs are excluded.
Regression tests for https://github.com/D4Vinci/Scrapling/issues/247
"""
import pytest
from scrapling.engines.static import FetcherClient
class TestMergeRequestArgsSkipsBrowserParams:
"""Verify that browser-only keyword arguments are stripped before
the request dict is forwarded to curl_cffi's Session.request()."""
def _build_args(self, **extra_kwargs):
"""Helper: instantiate a FetcherClient and call _merge_request_args."""
client = FetcherClient()
return client._merge_request_args(url="https://example.com", **extra_kwargs)
def test_block_ads_excluded(self):
"""block_ads is a browser-engine param and must not leak into the
HTTP request dict (fixes #247)."""
args = self._build_args(block_ads=True)
assert "block_ads" not in args
def test_google_search_excluded(self):
"""google_search is a browser-engine param and should be stripped."""
args = self._build_args(google_search=True)
assert "google_search" not in args
def test_extra_headers_excluded(self):
"""extra_headers is a browser-engine param and should be stripped."""
args = self._build_args(extra_headers={"X-Custom": "val"})
assert "extra_headers" not in args
def test_url_present(self):
"""The url must always be present in the output dict."""
args = self._build_args()
assert args["url"] == "https://example.com"
def test_valid_kwargs_passed_through(self):
"""Arbitrary curl_cffi-compatible kwargs should survive."""
args = self._build_args(cookies={"session": "abc"})
assert args.get("cookies") == {"session": "abc"}