diff --git a/README.md b/README.md index 81d7848..537d496 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@
- العربيه | Español | Français | Deutsch | 简体中文 | 日本語 | Русский | 한국어
+ العربيه | Español | Português (Brasil) | Français | Deutsch | 简体中文 | 日本語 | Русский | 한국어
@@ -97,18 +97,6 @@ MySpider().start()
Hyper Solutions
provides API endpoints that generate valid antibot tokens for Akamai, DataDome, Kasada, and Incapsula. Simple API calls, no browser automation required.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Métodos de seleção + · + Fetchers + · + Spiders + · + Rotação de proxy + · + CLI + · + MCP +
+ +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() +``` + +
+
+
+
+
+
+
+
+ |
+ Scrapling lida com o Cloudflare Turnstile. Para proteção de nível empresarial, + Hyper Solutions + oferece endpoints de API que geram tokens antibot válidos para Akamai, DataDome, Kasada e Incapsula. Chamadas simples de API, sem necessidade de automação de navegador. | +
+
+
+
+ |
+ Nós criamos a
+ BirdProxies
+ 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. + Experimente nosso jogo FlappyBird na landing page para ganhar dados grátis! + |
+
+
+
+
+ |
+ + + Evomi + : 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. + Scraper API para resultados sem complicação. Integrações com MCP e N8N estão disponíveis. + | +
+
+
+
+ |
+
+ TikHub.io oferece mais de 900 APIs estáveis em mais de 16 plataformas, incluindo TikTok, X, YouTube e Instagram, com mais de 40M de datasets. Também oferece modelos de IA com desconto - Claude, GPT, GEMINI e mais com até 71% de desconto. + |
+
+
+
+
+ |
+ + Nsocks 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 Xcrawl para simplificar o crawling web em larga escala. + | +
+
+
+
+ |
+
+ Feche o notebook. Seus scrapers continuam rodando. + PetroSky VPS - servidores em nuvem feitos para automação ininterrupta. Máquinas Windows e Linux com controle total. A partir de €6.99/mês. + |
+
+
+
+
+ |
+ + Leia uma análise completa do Scrapling no The Web Scraping Club (nov. 2025), a newsletter número 1 dedicada a Web Scraping. + | +
+
+
+
+ |
+ + Proxy-Seller 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. + | +
+
+
+
+ |
+ + Proxies estáveis 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. + | +
+
+
+
+ |
+ + Swiftproxy 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. + | +
+
+
+
+
+
+
+
+
+
+
+Quer mostrar seu anúncio aqui? Clique [aqui](https://github.com/sponsors/D4Vinci) e escolha o plano que fizer mais sentido para você!
+
+---
+
+## 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("...")
+```
+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:
+
+[](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)
+
+---
+
-
-
-
-
-
-
-
@@ -77,9 +74,6 @@ MySpider().start()
-
-
-
diff --git a/images/BirdProxies.jpg b/images/BirdProxies.jpg
deleted file mode 100644
index 3a58d4e..0000000
Binary files a/images/BirdProxies.jpg and /dev/null differ
diff --git a/images/ProxySeller.png b/images/ProxySeller.png
deleted file mode 100644
index 4704070..0000000
Binary files a/images/ProxySeller.png and /dev/null differ
diff --git a/pyproject.toml b/pyproject.toml
index e8a5500..ada11a4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
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
-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!"
readme = {file = "README.md", content-type = "text/markdown"}
license = {file = "LICENSE"}
@@ -77,8 +77,8 @@ fetchers = [
"patchright==1.58.2",
"browserforge>=1.2.4",
"apify-fingerprint-datapoints>=0.12.0",
- "msgspec>=0.21.0",
- "anyio>=4.12.1",
+ "msgspec>=0.21.1",
+ "anyio>=4.13.0",
"protego>=0.6.0",
]
ai = [
diff --git a/scrapling/__init__.py b/scrapling/__init__.py
index c0c66ff..97af0c5 100644
--- a/scrapling/__init__.py
+++ b/scrapling/__init__.py
@@ -1,5 +1,5 @@
__author__ = "Karim Shoair (karim.shoair@pm.me)"
-__version__ = "0.4.6"
+__version__ = "0.4.7"
__copyright__ = "Copyright (c) 2024 Karim Shoair"
from typing import Any, TYPE_CHECKING
diff --git a/scrapling/core/ai.py b/scrapling/core/ai.py
index 060cb20..315733f 100644
--- a/scrapling/core/ai.py
+++ b/scrapling/core/ai.py
@@ -123,6 +123,7 @@ class ScraplingMCPServer:
async def open_session(
self,
session_type: SessionType,
+ session_id: Optional[str] = None,
headless: bool = True,
google_search: bool = True,
real_chrome: bool = False,
@@ -152,6 +153,7 @@ class ScraplingMCPServer:
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_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 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.
@@ -175,6 +177,12 @@ class ScraplingMCPServer:
: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.
"""
+ 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(
wait=wait,
proxy=proxy,
@@ -211,7 +219,6 @@ class ScraplingMCPServer:
await session.start()
- session_id = uuid4().hex[:12]
entry = _SessionEntry(session=session, session_type=session_type)
self._sessions[session_id] = entry
diff --git a/scrapling/engines/static.py b/scrapling/engines/static.py
index 033e20e..9b3e951 100644
--- a/scrapling/engines/static.py
+++ b/scrapling/engines/static.py
@@ -719,8 +719,13 @@ class FetcherSession:
config["selector_config"] = self.selector_config
config["proxy_rotator"] = self._proxy_rotator
self._client = _SyncSessionLogic(**config)
+ try:
+ result = self._client.__enter__()
+ except Exception:
+ self._client = None
+ raise
self._is_alive = True
- return self._client.__enter__()
+ return result
raise RuntimeError("This FetcherSession instance already has an active synchronous session.")
def __exit__(self, exc_type, exc_val, exc_tb):
@@ -740,8 +745,13 @@ class FetcherSession:
config["selector_config"] = self.selector_config
config["proxy_rotator"] = self._proxy_rotator
self._client = _ASyncSessionLogic(**config)
+ try:
+ result = await self._client.__aenter__()
+ except Exception:
+ self._client = None
+ raise
self._is_alive = True
- return await self._client.__aenter__()
+ return result
raise RuntimeError("This FetcherSession instance already has an active asynchronous session.")
async def __aexit__(self, exc_type, exc_val, exc_tb):
diff --git a/scrapling/spiders/session.py b/scrapling/spiders/session.py
index 536be6d..5799e8c 100644
--- a/scrapling/spiders/session.py
+++ b/scrapling/spiders/session.py
@@ -93,7 +93,9 @@ class SessionManager:
async def close(self) -> None:
"""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)
self._started = False
diff --git a/server.json b/server.json
index 36f60e7..5415056 100644
--- a/server.json
+++ b/server.json
@@ -14,12 +14,12 @@
"mimeType": "image/png"
}
],
- "version": "0.4.6",
+ "version": "0.4.7",
"packages": [
{
"registryType": "pypi",
"identifier": "scrapling",
- "version": "0.4.6",
+ "version": "0.4.7",
"runtimeHint": "uvx",
"packageArguments": [
{
diff --git a/setup.cfg b/setup.cfg
index 0794d59..72d64fc 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = scrapling
-version = 0.4.6
+version = 0.4.7
author = Karim Shoair
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!
diff --git a/tests/ai/test_ai_mcp.py b/tests/ai/test_ai_mcp.py
index d897bb5..4806088 100644
--- a/tests/ai/test_ai_mcp.py
+++ b/tests/ai/test_ai_mcp.py
@@ -177,6 +177,25 @@ class TestSessionManagement:
with pytest.raises(ValueError, match="not found"):
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")
+
class TestNormalizeCredentials:
"""Test the _normalize_credentials helper"""