All notable changes to Scrapling, taken from the GitHub releases.
v0.4.15 - 2026-08-23
One of the biggest releases this year: a reworked MCP server, RAG-ready Markdown in one line, an improved Cloudflare solver, and browser tabs that stay open for automation 🚀
Warning
This release introduces breaking changes to the MCP server. Check the breaking changes section before updating.
- Browser tabs now stay open and get reused across requests (Check the docs):
- All browser sessions keep their tabs after a request, and the next request reuses a free tab instead of opening a new one.
- Every request re-applies its own settings (timeouts, headers, resource blocking) to the tab it gets, so nothing leaks between requests.
- Tabs that hit an error are closed and replaced, and the new
close_pages()method closes every open tab. - The page you fetched stays loaded, so a
page_setupfunction on the next request runs on it before navigating away. That's the building block for chaining automation across requests.
- Turn any page into clean, LLM-ready Markdown with
Response.markdown()(Check the docs):from scrapling.fetchers import Fetcher markdown = Fetcher.get("https://example.com").markdown(main_content_only=True)
- Scripts, styles, and hidden prompt-injection content are always stripped first, the same cleaning the MCP server does.
- Pass
css_selectorto convert only the elements you need. - Available through the new
ragextra (pip install "scrapling[rag]"), which theai/shell/allextras include too.
- New
SiteToMarkdownSpidertemplate to crawl a whole website into a Markdown corpus for RAG pipelines (Check the docs):from scrapling.spiders import SiteToMarkdownSpider class DocsSpider(SiteToMarkdownSpider): name = "docs" start_urls = ["https://example.com/docs/"] allowed_domains = {"example.com"} output_dir = "docs_markdown" result = DocsSpider().start() result.items.to_jsonl("docs.jsonl")
- Yields one item per page with
url/title/markdown, and the optionaloutput_dirwrites one Markdown file per page. max_pagescaps the crawl, and since it builds onCrawlSpider, overridingrules()gives you full control over which links get followed.
- Yields one item per page with
- The MCP server is reworked (breaking) (Check the breaking changes and the docs):
- The 13 tools are now split into two modes: one-shot tools (
fetch,bulk_fetch,stealthy_fetch,bulk_stealthy_fetch) that always launch their own browser and show their real defaults, and session tools that work through a session opened once. - The new
session_fetchtool fetches through a browser session, whileopen_sessionnow holds the browser-level settings only and returns the session's effective settings for the AI agent. - The
gettool is renamed tomake_request, and it now supports any HTTP method. - The new
open_request_sessionandsession_make_requesttools give the AI persistent HTTP sessions that keep cookies and the browser fingerprint between requests. - This also ends fetches resetting the session's settings, first fixed by @Yigtwxx in #418.
- The 13 tools are now split into two modes: one-shot tools (
- The MCP server's HTTP transport now requires authentication and binds to localhost by default (breaking) by @yamantaka-singh in #414 (Fixes #413, check the docs):
- Pass
--auth-token(or theSCRAPLING_MCP_AUTH_TOKENenvironment variable) to require a bearer token, or--no-authto serve it unauthenticated on purpose. - Pass
--host 0.0.0.0to accept connections from the network.
- Pass
- Cloudflare Turnstile/Interstitial solving now works regardless of the browser locale, and no longer loops forever on interactive challenges in headless mode. Stealth pages also stop crashing mid-solve, with contributions by @subediparas5 in #412. (Fixes #411 and #422)
- Fixed
find/find_allwithclass_silently missing multi-class elements by @yetval in #410, and blankclass_values and unescaped CSS string values by @yamantaka-singh in #417. - Fixed cached responses in
development_modelosing the request meta on replay by @Yigtwxx in #419. - Fixed HTTP requests with
retriesbelow 1 failing without sending the request by @Yigtwxx in #420.
- The website sections are restructured: a new "Using with AI" section holds the MCP server, the new Agent skill page, and the Building RAG systems guide, and the BeautifulSoup migration guide moved next to the Scrapy integration under "Integrations and migrations".
- Added a CHANGELOG.md to the repository with the notes of every release so far.
v0.4.14 - 2026-08-10
A quick maintenance release to fix installation with uv 🔧
- Fixed
uvrefusing to install v0.4.13 by default and silently falling back to an older version. The previous release required a prerelease version ofcurl_cffi, whichuvdoesn't allow unless explicitly enabled. All dependencies now resolve to stable releases. (Fixes #407)
v0.4.13 - 2026-08-09
A new update bringing feed spiders and a smarter MCP server 🎉
- New feed spider templates.
XMLFeedSpideriterates over the nodes of any XML feed (RSS, Atom, product feeds, etc.), andCSVFeedSpideriterates over CSV rows as dictionaries. Both decompress gzipped feeds automatically. (Check the docs)from scrapling.spiders import XMLFeedSpider class RSSSpider(XMLFeedSpider): name = "rss" start_urls = ["https://example.com/feed.xml"] async def parse_node(self, response, node): yield {"title": node.findtext("title"), "link": node.findtext("link")} result = RSSSpider().start()
- Upgraded the MCP server to MCP SDK v2 and made it smarter. The server now ships instructions that teach your AI agent how to use the tools efficiently; every tool declares annotations so clients like Claude Code can auto-approve the read-only ones; tool descriptions are leaner to save tokens; and the server advertises its version and logo to MCP clients. (Check the docs)
- Added a
scrapling-mcpcommand that maps directly toscrapling mcp, so registering Scrapling with MCP clients and registries that expect a single command is now a one-liner. - Unpinned Playwright/Patchright and browser versions. The generated browser User-Agent now always matches the exact Chromium version your installed Playwright/Patchright drives, so Scrapling no longer pins their versions and you can upgrade them freely. Run
scrapling install --forceafter updating to refresh the browsers.
- Fixed importing Scrapling crashing with a
browserforgeValueError when the fingerprints data package lags behind the browser versions. (Fixes #394, #396, and #400) - Fixed the MCP bulk browser tools mis-sizing their page pools, which made
bulk_fetchfail on batches of more than 50 URLs andbulk_stealthy_fetchfetch all URLs through a single tab, by @Yigtwxx in #393.
- New AI Contribution Policy: AI-assisted contributions are welcome but must be disclosed in the PR or issue; submissions that look like undisclosed AI output get labeled and closed.
v0.4.12 - 2026-07-26
A release focused on making your spiders smarter about the websites they crawl
-
Spiders can now tune their own speed with AutoThrottle. Instead of guessing a
download_delaythat's either too slow or gets you banned, the spider measures how fast each website answers and adjusts the delay of every domain on its own. When a website starts blocking or rate-limiting you, it doubles the delay (or waits exactly what theRetry-Afterheader asks for) until that stops, then speeds back up. Yourdownload_delayand any robots.txtCrawl-delayare still respected as the minimum. (Check the docs)class MySpider(Spider): name = "adaptive" start_urls = ["https://example.com"] autothrottle_enabled = True autothrottle_start_delay = 2.0 autothrottle_max_delay = 30.0 autothrottle_block_backoff = True
-
Export your results to CSV and XML, next to the JSON/JSONL exporters you already had. Items that don't all share the same keys are still exported without losing anything, and nested values are written as JSON. (Check the docs)
result = MySpider().start() result.items.to_csv("products.csv") result.items.to_xml("products.xml")
-
The MCP server can now require authentication, so you can safely expose it instead of keeping it on your own machine. Any request without the token is rejected, and you can also restrict which hostnames the server answers to. (Check the docs)
scrapling mcp --http --auth-token "$(openssl rand -hex 32)" -
Browsers now accept CDP URLs over HTTP, not just WebSocket ones. So next to the
wss://endpoints managed browser providers hand out, you can now point any browser fetcher or MCP session at a Chrome you started yourself with--remote-debugging-port=9222. -
Published Docker images are now tagged with their release version instead of only
latest, so you can pin the exact version you want, by @JanRK in #384.
-
Fixed cached responses losing all their cookies when the response came from a browser engine, which silently broke any session or auth logic relying on them while using the spiders' development mode, by @amitvijapur in #379. (Fixes #376)
-
Fixed
StealthyFetcherforcing theen-USlocale on every browser instead of following your system's, which made websites see a mismatch between your locale and your IP address and treat you as suspicious, like Google answering with 429s. (Fixes #381) -
Fixed a misleading error message in the storage system and removed a dead call left after inserts, by @fix2015 in #377.
get_all_text()is now O(nodes) instead of walking up the ancestors of every single text node, which makes it around 5-6x faster on deeply nested pages, by @yetval in #378.
v0.4.11 - 2026-07-12
A solid update bringing the first platform spider template, a faster parser, and important fixes 🎉
- Added
ShopifySpider, the first platform spider template! Extract every product from any Shopify-powered store through its JSON API without touching the website's HTML. Subclass it, set the store's domain, and you are done (Check the docs)from scrapling.spiders import ShopifySpider class MyStore(ShopifySpider): target_website = "example.com" result = MyStore().start()
- Added
--executable-pathto the CLI browser commands. Bothscrapling extract fetchandscrapling extract stealthy-fetchnow accept a custom Chromium-compatible browser executable, and fall back to theSCRAPLING_EXECUTABLE_PATHenvironment variable when the option isn't passed, bringing full parity with the MCP server (Solves #371)scrapling extract fetch "https://example.com" page.html --executable-path "/path/to/chromium"
- Made
find_by_textandfind_by_regexup to ~2x faster whenfirst_matchis enabled (the default) by wrapping elements lazily so the search stops at the first match, by @yetval in #370 - Updated the benchmarks with the new numbers against the latest versions of all libraries.
- Updated contribution rules
- Fixed the MCP server's fetch tools crashing on pages containing control characters with the error
All strings must be XML compatible, by @yetval in #368 (Fixes #366)
v0.4.10 - 2026-07-04
A new update with a brand-new Scrapy integration and a batch of community fixes 🎉
-
Added a Scrapy integration so you can use Scrapling's parsing API inside your existing Scrapy projects without rewriting them. Put the
scrapling_responsedecorator on any spider callback, and the response it receives becomes a ScraplingResponsewhile Scrapy keeps handling the crawling (Check the docs):import scrapy from scrapling.integrations.scrapy import scrapling_response class QuotesSpider(scrapy.Spider): name = "quotes" start_urls = ["https://quotes.toscrape.com"] @scrapling_response def parse(self, response): # `response` is now a Scrapling Response first_quote = response.find_by_text("The world as we have created it", partial=True) for quote in [first_quote, *first_quote.find_similar()]: yield {"text": quote.get_all_text(strip=True)}
-
The MCP server can now use a custom Chromium-compatible browser for all browser-based tools. Set it once with
scrapling mcp --executable-path "/path/to/chromium"or theSCRAPLING_EXECUTABLE_PATHenvironment variable, or per request with theexecutable_pathargument, by @samrusani in #360 (Solves #347) -
Updated all browsers and fingerprints. Run
scrapling install --forceafter updating to refresh them.
- Fixed garbled text (mojibake) from browser fetchers on non-UTF-8 websites by @yehudalevy-collab in #365 (Fixes #364).
- Fixed
LinkExtractornot filtering compound file extensions like.tar.gzby @renbkna in #359 (Fixes #349). - Fixed paused crawls losing their in-flight requests from checkpoints, so resuming no longer skips them by @yetval in #358.
- Fixed spiders calculating wrong crawl delays from robots.txt
Request-ratedirectives through the Protego upgrade, with tests aligned by @Disaster-Terminator in #355.
- Clarified how
init_scriptinteracts with Patchright's isolated execution context in stealth mode by @mturac in #353 (Solves #350). - Added the skills.sh install method for the agent skill by @ob-aion in #363.
v0.4.9 - 2026-06-07
A maintenance update packed with community-reported fixes 🛠️
- Updated all browsers and fingerprints. Run
scrapling install --forceafter updating to refresh them. - Added a
--versionflag to the CLI by @ETM-Code in #303 (Solves #299)
- Fixed the session-level
proxyargument being silently ignored in HTTP sessions, which could leak your real IP (Solves #295). Note that mixing a session-levelproxywith a per-requestproxiesargument (or vice versa) now raises an error instead of one being silently dropped. - Fixed browser navigations failing when combining
init_scriptwithuser_data_dir(Solves #294). - Fixed encoding detection when websites quote the charset value in the
Content-Typeheader by @Bortlesboat in #323. - Fixed an
IndexErrorin adaptive element relocation whenauto_saveis enabled by @Mubashirrrr in #340. - Fixed spiders' checkpoint and cache saving crashing on Windows by @MrStarkEG in #344.
- Fixed incorrect similarity scoring in
find_similarfor elements with mismatched attribute counts (Solves #322).
- Clarified that the default installation includes the parser engine only, and the fetchers/spiders need the extras (Solves #343).
- Fixed the Docker image name in the remaining examples by @evanclan in #315.
- Fixed a broken link in the contribution guide by @Bortlesboat in #320.
v0.4.8 - 2026-05-11
A big spider update that takes the crawling framework to the next level 🕷️
-
Added a
LinkExtractorprimitive inscrapling.spiders.LinkExtractorto pull URLs out of aResponse. There are a lot of controls (Check the docs)from scrapling.spiders import LinkExtractor extractor = LinkExtractor(allow=r"/posts/", deny_domains=["ads.example.com"])
-
Added
CrawlSpiderandCrawlRulegeneric spider templates so you no longer have to hand-write the same "follow links matching this pattern" boilerplate. Overriderules()to return a list ofCrawlRuleobjects, each pairing aLinkExtractor. (Check the docs)from scrapling.spiders import CrawlSpider, CrawlRule, LinkExtractor class QuotesSpider(CrawlSpider): name = "blog" start_urls = ["https://quotes.toscrape.com/"] def rules(self): return [ CrawlRule(LinkExtractor(allow=r"/author/"), callback=self.parse_author), CrawlRule(LinkExtractor(allow=r"/page/\d+/")), # pagination, no callback ] async def parse_author(self, response): yield { "name": response.css(".author-title::text").get(), "birthday": response.css(".author-born-date::text").get(), "url": response.url, }
-
Added a
SitemapSpidertemplate that seeds a crawl directly from a sitemap, orrobots.txtURLs. Handles gzip-compressed sitemaps, and a lot of controls and options. URLs are dispatched via the crawl rules as shown above for CrawlSpider. (Check the docs)from scrapling.spiders import SitemapSpider, CrawlRule, LinkExtractor class NewsSitemap(SitemapSpider): name = "news" sitemap_urls = ["https://example.com/robots.txt"] def rules(self): return [ CrawlRule(LinkExtractor(allow=r"/articles/"), callback=self.parse_article), ] async def parse_article(self, response): yield {"url": response.url, "title": response.css("h1::text").get()}
-
Adaptive relocation now defaults to a 40% similarity threshold instead of
0across all methods. This will make the adaptive feature work better. When nothing crosses the threshold, a warning now tells you the top score it did see, so you can lowerpercentagedeliberately if needed. -
Updated all browsers and fingerprints. Run a new
scrapling install --forceafter updating to refresh the browsers and fingerprints.
- Fixed
Fetcher.configure(...)not applying to per-request calls. Same fix applied toAsyncFetcher. - Fixed incorrect request fingerprinting that caused duplicate requests in spiders by @yetval in #255.
- Fixed the Adaptive scraping engine staying silent on weak matches. Combined with the threshold change above, you now get a warning instead of a misleading "best guess" element when relocation fails.
- Refreshed older code examples across the documentation to match the current version.
- Improved the code copy-paste experience on the docs site and trimmed the agent skill so it uses fewer tokens per invocation.
v0.4.7 - 2026-04-17
A focused update bringing eyes to your AI agents 📸
- Added a
screenshotMCP tool that captures a page and returns it as a real MCPImageContentblock so the model can actually see it. The tool requires an open browser session, so you callopen_sessionfirst (eitherdynamicorstealthy) and pass thesession_idhere. Supports PNG and JPEG, full-page captures, JPEG quality, and the usual readiness controls (wait,wait_selector,network_idle,timeout). (implements #244) - Added a custom
session_idparameter toopen_sessionso you can name sessions meaningfully ("search","checkout") instead of the random 12-character hex default. By @hauntedhost in #243
- Fixed
FetcherSessionstate corruption and a lazy session close crash. By @yetval in #245 - Fixed
TypeError: Session.request() got an unexpected keyword argument 'block_ads'when using the CLI's--ai-targetedflag with HTTP commands. By @voidborne-d in #249 (Fixes #247)
- Added a Brazilian Portuguese README translation By @rgomids in #250
v0.4.6 - 2026-04-13
A focused update on browser stealth, privacy, and developer experience 🔒
- Added built-in ad blocking for browser fetchers. Pass
block_ads=Trueto block requests to ~3,500 known ad and tracker domains at the route interception level -- no DNS, no TCP, instant abort. Can be combined withblocked_domainsfor custom lists. The MCP server and CLI--ai-targetedmode enable this automatically to save tokens and speed up page loads.page = StealthyFetcher.fetch('https://example.com', block_ads=True)
- Added DNS-over-HTTPS support to prevent DNS leaks when using proxies. Pass
dns_over_https=Trueto route DNS queries through Cloudflare's DoH, so your real location isn't exposed through DNS resolution even when your HTTP traffic goes through a proxy.page = StealthyFetcher.fetch('https://example.com', proxy='http://proxy:8080', dns_over_https=True)
- Added
page_setupcallback for browser fetchers. A function that runs beforepage.goto(), letting you register event listeners, routes, or scripts that must be set up before the page navigates. Pairs withpage_action(which runs after navigation). (Solves #237)def capture_websockets(page): page.on("websocket", lambda ws: print(f"WS: {ws.url}")) page = DynamicFetcher.fetch('https://example.com', page_setup=capture_websockets)
- Added
--block-adsand--dns-over-httpsCLI options to bothfetchandstealthy-fetchcommands.
- Fixed
Secondstype alias rejecting float values. Passingwait=1.5ortimeout=500.0to browser fetchers would fail with a type error because the type alias incorrectly treatedfloatas metadata instead of a type. by @kuishou68 in #240 - Fixed duplicate ID segments in full-path selector generation. Elements with
idattributes had their selector appended twice when generating full CSS/XPath paths, producing selectors likebody > #main > #main > #target > #target. Also fixed full-path XPath emitting bare[@id='x']predicates (invalid XPath) instead of*[@id='x']. by @sjhddh in #241 - Fixed missing shell signature parameters. The interactive shell was missing
blocked_domains,block_ads,retries,retry_delay,capture_xhr,executable_path, anddns_over_httpsfrom its function signatures.
v0.4.5 - 2026-04-07
A focused update with one big quality-of-life feature for spider developers and a couple of important fixes 🎉
-
Spider Development Mode: Iterating on a spider's
parse()logic used to mean re-hitting the target servers on every run, which is slow, noisy, and a great way to get rate-limited while you're still figuring out your selectors. The new development mode caches every response to disk on the first run and replays them from disk on every subsequent run, so you can tweak your callbacks and re-run as many times as you want without making a single network request. Enable it with one class attribute:class MySpider(Spider): name = "my_spider" start_urls = ["https://example.com"] development_mode = True async def parse(self, response): yield {"title": response.css("title::text").get("")}
The cache lives in
.scrapling_cache/{spider.name}/by default and can be redirected anywhere withdevelopment_cache_dir. Two new stat counters,cache_hitsandcache_misses, let you see how the cache performed. Cache replay bypassesdownload_delay, rate limiting, and the blocked-request retry path so iteration is as fast as the disk allows. Don't ship a spider withdevelopment_mode = True-- it's a development tool, not a production cache. See the docs for the full story. -
Safer redirects by default:
follow_redirectsnow defaults to"safe"across all HTTP fetchers, the MCP server, and the shell. Redirects are still followed, but ones targeting internal/private IPs (loopback, private networks, link-local) are rejected. This protects you from SSRF when scraping user-supplied URLs. Passfollow_redirects="all"to get the old behavior, orFalseto disable redirects entirely.
- Force-stop no longer loses your checkpoint: Pressing Ctrl+C twice (force-stop) on a spider with
crawldirenabled used to race against the checkpoint write -- the cancel scope would tear down the task before the pickle finished, leavingpaused=Falseand triggering the cleanup path that deletes the previous checkpoint. The result was that force-stopping a long crawl could lose all the progress you were trying to save. The engine now writes the checkpoint before callingcancel_scope.cancel(), so a force-stop always preserves the latest pending state. By @voidborne-d in #230.
v0.4.4 - 2026-04-05
A new update with important spider improvements and bug fixes 🎉
- Added robots.txt compliance to the Spider framework with a new
robots_txt_obeyoption. When enabled, the spider will automatically fetch and respect robots.txt rules before crawling, includingDisallow,Crawl-delay, andRequest-ratedirectives. Robots.txt files are fetched concurrently and cached per domain for the entire crawl. By @AbdullahY36 in #226 - Added robots.txt cache pre-warming so all start_urls domains have their robots.txt fetched and parsed before the crawl loop begins, avoiding delays on the first request to each domain.
- Added a new
robots_disallowed_countstat toCrawlStatsto track how many requests were blocked by robots.txt rules during a crawl.
Check it out on the website from here
- Fixed a critical MRO issue with
ProxyRotatorwhere the_build_context_with_proxystub was shadowing the real implementation from child classes, causing proxy rotation to always raiseNotImplementedError(Fixes #215). Thanks @yetval - Fixed a page pool leak when using per-request proxy rotation with browser sessions. Pages created inside temporary contexts were not removed from the pool on cleanup, leading to stale references accumulating over time. By @yetval in #223
- Fixed a missing type assertion in the static fetcher where
curl_cfficould returnNonefromsession.request(), causing downstream errors.
- Updated dependencies, so expect the latest fingerprints and other stuff.
- Added
protegoas a new dependency under thefetchersoptional group for robots.txt parsing.
v0.4.3 - 2026-03-30
A new update with many important changes 🎉
- Added a new MCP tool to open a persistent normal/stealthy browser to keep using it with the rest of the tools, and another new tool to close it. (Examples)
- Added a new MCP tool to list all existing browser sessions. Aimed to be used with the new tools.
- Added a new option to browser sessions to automatically collect all background requests that happen during a request (Solves #159) [Examples].
- Added a new sanitizer to protect the MCP server from common Prompt Injection attacks by removing hidden/invisible content.
- Added a new commandline option called
--ai-targetedto the Web Scraping commands to make content targeted to AI and safe against common Prompt Injection attacks like the MCP server. - Added a new option to browser sessions called
executable_pathto allow setting a custom browser path (Solves #202) - Refactored the MCP server code to be easily maintained and unified all tools to be async.
- Refactored the CLI commands code to be easily maintained and shorter by 210 lines.
- A fix to preserve HTTP method across retries in spider session by @karesansui-u in #201
- Added a max retry limit to getting page content to prevent infinite loop by @haosenwang1018 & @D4Vinci in #197
- Replace bare
raisewithreturn Falsein_restore_from_checkpointby @haosenwang1018 in #196 - Replaced
get_allwithgetallinTexthandlerto match the Selector class.
- Added
_normalize_credentialsedge case coverage tests by @Bortlesboat in #192 - Added save/retrieve round-trip and core storage coverage tests by @haosenwang1018 in #193
- Added coverage for
TextHandlerregex paths andTextHandlers.re()by @haosenwang1018 in #194 - Added edge case tests for
filter,iterancestors, andfind_similarby @awanawana in #200
- Fixed broken markdown links in skill references by @yetval in #204
- Improved the skill structure to be more acceptable by Clawhub validation.
- Forced the skill to use the
--ai-targetedcommandline option when scraping through commandline commands.
- Added Korean README translation by @greatsk55 in #187
- CJK Latin spacing fixes for the Chinese and Japanese READMEs.
- Fixed broken links from the old website design.
v0.4.2 - 2026-03-08
A new maintenance update with important changes
- The function
get_all_text()now captures tail text nodes. This will make the MCP server and commands see text that was missed before (#168). Thanks @mhillebrand - Referer now returns a bare Google url instead of a Google search URL. The previous logic was incorrect and may have produced a fingerprinting signal (#179). Thanks @Bortlesboat
- Fixed an issue with extra flags concatenation in all browsers. Thanks @rostchri
- Fixed a type hints issue with Python versions below 3.12 that caused it to crash. (Solves #163)
- Added an Agent Skill for Claude Code / OpenClaw and other AI agentic tools.
- Added the Agent Skill to Clawhub.
- Updates all browsers and Playwright versions to the latest.
- Added a French translation to the main README file.
v0.4.1 - 2026-02-27
A new update with many important changes
- Improved regex precision for Cloudflare challenge detection (Thanks to @Rinz27 #133)
- Improved the speed and efficiency of the Cloudflare solver. Now it is nearly twice as fast.
- Improved the Cloudflare solver to handle the case where websites sometimes show the Cloudflare page twice before redirecting to the main website.
- Improved the stealthy browser's stealth mode and speed by removing the injected JS files.
- Improved the MCP schema to be acceptable by OpenCode (Thanks to @robin-ede #137)
- Made the MCP schema even more MCP-friendly to be accepted by VS Code Copilot and other strict tools. (Solves #150 )
- Improved the MCP server tokens consumption by a large margin through stripping useless HTML tags while the
main_content_onlyoption is activated. - Fixed the PyPI page and added the files to register the MCP server to the MCP servers registry.
- Added a new code snippet to show how to install the browsers deps through code instead of using the commandline to allow easier automation.
- Improved all workflows by using the latest actions versions (Thanks to @salmanmkc #143/#144)
v0.4 - 2026-02-15
The biggest release of Scrapling yet — introducing the Spider framework, proxy rotation, and major parser improvements
This release brings a fully async spider/crawling framework, intelligent proxy management, and significant API changes that make Scrapling more powerful and consistent. Please review the breaking changes section carefully before upgrading.
A new async crawling framework built on top of anyio for structured, large-scale scraping:
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()- Scrapy-like Spider API: Define spiders with
start_urls, asyncparsecallbacks,Request/Responseobjects, and priority queue. - Concurrent Crawling: Configurable concurrency limits, per-domain throttling, and download delays.
- Multi-Session Support: Unified interface for HTTP requests, and stealthy headless browsers in a single spider - route requests to different sessions by ID. Supports lazy session initialization.
- Pause & Resume: Checkpoint-based crawl persistence. Press Ctrl+C to gracefully shut down; then restart to resume from where you left off.
- Streaming Mode: Stream scraped items as they arrive via
async for item in spider.stream()with real-time stats - ideal for UI, pipelines, and long-running crawls. - Blocked Request Detection: Automatic detection and retry of blocked requests with customizable logic.
- Built-in Export: Export results through hooks and your own pipeline or the built-in JSON/JSONL with
result.items.to_json()/result.items.to_jsonl()respectively. - Lifecycle hooks:
on_start(),on_close(),on_error(),on_scraped_item(), and more hooks for full control over the crawl lifecycle. - Detailed crawl stats: track requests, responses, bytes, status codes, proxies, per-domain/session breakdowns, log level counts, and more.
- uvloop support: Pass
use_uvloop=Truetospider.start()for faster async execution when available.
A new section has been added to the website with the Full details. Click here
- New
ProxyRotatorclass with thread-safe rotation. Works with all fetchers and sessions:from scrapling import ProxyRotator rotator = ProxyRotator(["http://proxy1:8080", "http://proxy2:8080"]) Fetcher.get(url, proxy_rotator=rotator)
- Custom rotation strategies: Make your own proxy rotation logic
- Per-request proxy override: Pass
proxy=to any individualget()/post()/fetch()call to override the session proxy for that request.
- Domain blocking: New
blocked_domainsparameter onDynamicFetcher/StealthyFetcherto block requests to specific domains (subdomains matched automatically). - Automatic retries: Browser fetchers now retry on failure with
retries(default: 3) andretry_delay(default: 1s) parameters. Includes proxy-aware error detection. - Response metadata:
Response.metadict automatically stores the proxy used, and merges request metadata. - Response.follow(): Create follow-up
Requestobjects with automatic referer flow, designed for the spider system. - No autoplay: Browser sessions are now blocking autoplay content, which caused issues before.
- Speed: Improved stealth and speed by adjusting browser flags.
- Parser optimization: Optimized the parser for repeated operations, improving performance.
- Errored pages: Fixed a bug that caused the browser to not close when pages gave errors.
- Empty body: Handle responses with empty body.
- Playwright loop: Solving an issue with leaving the Playwright loop open when CDP connection fails
- Type safety: Fixed all mypy errors and added type hints across untyped function bodies. Added mypy and pyright to the CI workflow.
css_first/xpath_firstremoved: Usecss('.selector').first,css('.selector')[0], orcss('.selector').get()instead.- All selection now returns
Selectors:css('::text'),xpath('//text()'),css('::attr(href)'), andxpath('//@href')now returnSelectors(wrapping text nodes inSelectorobjects withtag="#text") instead ofTextHandlers. This makes the API consistent across all selection methods and the type hints. Response.bodyis alwaysbytes: Previously could bestrorbytes, now always returnsbytes.get()/getall()behavior: OnSelector:get()returnsTextHandler(serialized HTML or text value),getall()returnsTextHandlers. Aliases:extract_first = get,extract = getall. Oldget_all()onSelectorsis removed.Selectors.first/.last: Safe accessors that returnSelector | Noneinstead of raisingIndexError.- Internal constants renamed:
DEFAULT_FLAGS→DEFAULT_ARGS,DEFAULT_STEALTH_FLAGS→STEALTH_ARGS,HARMFUL_DEFAULT_ARGS→HARMFUL_ARGS,DEFAULT_DISABLED_RESOURCES→EXTRA_RESOURCES.
- Dependency changes: Replaced
tldextractwithtld, removed internal_html_utils.pyin favor ofw3lib.html.replace_entities, addedtyping_extensionsas a hard requirement. - Docs overhaul: Full switch from MkDocs to Zensical, new spider documentation section, updated all existing pages, and added new API references.
v0.3.14 - 2026-01-03
A minor maintenance update to fix issues that happened with some devices in v0.3.13
- Disabled the incognito mode in
StealthyFetcherand its session classes since it made cookies not persistent across pages on Windows devices. It didn't happen on MacOS and Linux (Fixes #123, thanks to @frugality4121 for bringing it up and to @gembleman for pointing out the solution). - Pinned down the last version of browserforge to solve the issue with old header models for users with an already old browserforge version.
v0.3.13 - 2026-01-01
This is a big update with many improvements across many places, but also many breaking changes for good reasons. Please read the below before updating
-
For many reasons, we decided that from now on, we will stop using Camoufox entirely, and we might switch back to it in the future if its development continues. If you prefer to continue using Camoufox as before this release, there are instructions for that in this section.
-
Previously, we were using patchright in the stealth mode inside
DynamicFetcherand its session classes. Now we removed the stealth mode from them and started using patchright insideStealthyFetcherand its session classes, with A LOT of improvements, as you will see, improving the stealth overall on top of patchright.
This makes StealthyFetcher and its session classes 101% faster than before, use less memory and space, and have ~400 lines of code shorter, but, most importantly, are more stable than when we used Camoufox before.
This will also shorten the installation time of the scrapling install command, reduce the size of the Docker image, improve test smoothness in GitHub's CI, and make scrapling less confusing for new users.
- The
stealthargument was removed from theDynamicFetcherclass and its session class, while thehide_canvasargument was moved to theStealthyFetcherand its session classes. - The
disable_webglargument has been moved fromDynamicFetcherto theStealthyFetcherclass and renamed asallow_webgl. All session classes as well. - The
StealthyFetcherclass is now basically the new stealthy version ofDynamicFetcher, so the following arguments are removed:block_images,humanize,addons,os_randomize,disable_ads, andgeoip. I tried to replicate them in Chromium, but each had its own problem. This might change with upcoming releases before v0.4.
Now to the good news, we have improved and fixed a lot of stuff :)
- You already know that the
StealthyFetcherclass and its session classes are now 101% faster than before, but now also theDynamicFetcherclass and its session class are 20% faster. - Cloudflare's solver algorithm has been improved over before now to finish faster and handle more cases. Also, thanks to the new refactor, expect the solver to solve the captcha twice as fast!
- All fetchers now use less memory.
- The MCP server now uses fewer tokens to save more money!
- The Docker image is now 60% smaller.
- The whole documentation website has been updated with the new stuff. At the same time, it was made more explicit, many sections were shortened, more examples were added, missing arguments were included, the API reference section was updated with graphs, and many other improvements were made. The Website now loads 130% faster, uses less data, and is better for SEO.
- Added the arguments that were missing before in the Web Scraping shell shortcuts and made them more accurate.
- Fixed the issue where the
google_searchargument was creating a Google referrer even if the URL is a localhost/IP.
v0.3.12 - 2025-12-18
- Added a new argument to
DynamicSession/AsyncDynamicSessionclasses calledtimezone_id, which allows you to set the timezone of the browser so that it matches the timezone of the Proxy/VPN you are using. That way, the websites can't detect that you are using a proxy through the timezone mismatch technique. - Improved the automated conversion of response to JSON.
- Renamed the internal function
__create__tostartinside fetchers' session classes to make it easier to use them outside thewithcontext. - Updated
curl_cffiand other deps to the latest versions.
v0.3.11 - 2025-12-03
- Added a better logic for handling timeout errors when the
network_idleargument is used on an unstable website (websites with media playing, etc.) - Fixed the autocompletion for the
stealthy_fetchshortcut in the Web Scraping Shell
v0.3.10 - 2025-11-26
A maintenance update with many significant changes and possible breaking changes
- Solved all encoding issues by using a better approach which will handle web pages where encoding is not correctly declared (Thanks to @Kemsty2's efforts for pointing that out in #110 #111 )
- Solved a logical issue with overriding session-level parameters with request-level parameters in all browser-based fetchers that was present since v0.3
- Fixed the signatures of the shortcuts in the interactive web scraping shell, which made a perfect autocompletion experience for the shortcuts in the shell. This issue has been present since v0.3 as well.
- Pumped up the version for the Maxmind database, which will improve the
geoipargument forStealthyFetcherand its session classes. - Updated all used browser versions to the latest available ones.
- BREAKING - all fetchers had gone through a big refactor, which resulted in some interesting things that might break your code:
- Scrapling codebase is now smaller by ~750 lines and many changes which would make maintenance very much easier in the future and use a bit less resources.
- The validation for all fetchers and their session classes became much faster, which will reflect on their overall speed.
- To achieve this, now all fetchers can't accept standard arguments other than the
urlargument; the rest of the arguments must be keyword-arguments so your code must be likeFetcher.get('https://google.com', stealthy_headers=True)notFetcher.get('https://google.com', True)if you were doing that for some reason! - An annoying difference between browser-based fetchers and their session classes since v0.3 was that the argument used to pass custom parser settings per request was called
custom_config, while it was namedselector_configin the session classes. This refactor allowed us to unify the naming toselector_configwithout breaking your code, so the main one is nowselector_configwith backward compatibility for thecustom_configargument. The autocompletion support will be available only for theselector_configargument. - Also, to achieve all of this, we had to make the type hints of the fetchers' functions dynamically generated, so if you don't get a proper autocompletion in your IDE, make sure you are using a modern version of it. We have tested almost all known IDEs/editors.
We have also updated all benchmark tables with the current numbers against the latest versions of all alternative libraries.
v0.3.9 - 2025-11-17
A new update with many important changes
- Now the
impersonateargument inFetcherandFetcherSessioncan accept a list of browsers that the library will choose a random browser from them with each request.
from scrapling.fetchers import FetcherSession
with FetcherSession(impersonate=['chrome', 'firefox', 'safari']) as s:
s.get('https://github.com/D4Vinci/Scrapling')- A new argument to the
cleanmethod inTextHandlerto remove html entities from the current text easily. - Huge improvements to the documentation with more precise explanations of many parts and automatic translations of the main
README.mdfile.
- Fixed a big issue with retrieving responses from browser-based fetchers. Now, there is intelligent content type detection that ensures
response.bodycontains the rendered browser content only if the content is HTML; otherwise, it contains the raw content of the last request made. This allows you to download binary files and text-based files without having to find them wrapped in HTML tags, while being able to retrieve the rendered content you want from the website when fetching it.
- Updated the contributing guide to make it clearer and easier.
- Add a new workflow to enforce code quality tools (Same ones used as pre-commit hooks).
v0.3.8 - 2025-10-27
A new update with many important changes
- For all browser-based fetchers: websites that never finish loading their requests won't crash the code now if you used
network_idlewith them. - The logic for collecting/checking for page content in browser-based fetchers has been changed to make browsers more stable on Windows systems now, as Linux/MacOS (All this difference in behaviour is because of Playwright's different implementation on Windows systems).
- Refactored all the validation logic, which made all requests done from all browser-based fetchers faster by 8-15%
- A New option called
extra_flagshas been added toDynamicFetcherand its session to allow users to add custom Chrome flags to the existing ones while launching the browser. - Reverted the route logic for catching responses (changed in the last version) to use the old routing version when
page_actionis used. This was added to collect the latest version of a page's content in casepage_actionchanges it without making a request. (Thanks for @gembleman to pointing it in #100 and #102 )
- Fixed a typo in
load_domin DynamicSession's async_fetch - Fixed an issue with Cloudflare solver that made the solver wait forever for embedded captchas that don't disappear after solving. Now it will wait for the captcha to disappear for 30 seconds, then assume it's the type that doesn't disappear (Fixes #100 )
- Now the Docker image is automatically pushed to Dockerhub and GitHub's container registry for user convenience.
- Added a new documentation page to show how to use Scrapeless browser with Scrapling.
v0.3.7 - 2025-10-12
A new update with many important changes
- Reworked
solve_cloudflareargument inStealthyFetcherto make it able to solve all kinds of custom implementations of Turnstile. - Refactored the entire codebase to be acceptable by Pyright, so expect a flawless IDE experience now with all software and many bugs solved.
- Refactored the requests logic to be cleaner and faster (Also solves #97 )
- Added a new option
user_data_dirto all browser-based session classes to allow the user to reuse the browser session data (cookies/storage/etc...) from previous sessions. Leaving it will cause Playwright to use a random directory on each run, as was happening before. - Added a new customization option
additional_argstoDynamic fetcherand its session class to enable the user to pass extra arguments to Playwright's context, as we had withStealthyFetcherbefore. - The route logic for collecting the last navigation response for all browsers has been improved, which allows the raw responses to be passed to the parser before being processed by the browsers as before. This will be very helpful with text/JSON responses.
- The rework of the route logic solved an issue with retrieving the content of unstable websites on some Windows devices.
- All the refactors that happened in this version solved a lot of bugs along the way that were hard to spot before, and weird autocompletion issues with some IDEs.
- Many fixes to the documentation website
v0.3.6 - 2025-10-01
- Improved the
solve_cloudflareargument inStealthyFetcherand its session classes to be able to solve all types of both Turnstile and interstitial Cloudflare challenges 🎉 - Now the MCP server has the option to use
Streamable HTTP, so you can easily expose the server. - Added Docker support, so now an image is built and pushed to Docker Hub automatically with each release (contains all browsers)
- Fixed an encoding issue with the parser that happened in some cases (the famous
invalid start byteerror) - Restructured multiple parts of the library to fix some memory leaks, so now enjoy noticably lower memory usage based on your config (Also solves #92 )
- Improved type annotation in many parts of the code so you can have a better IDE experience (Also solves #93 )
v0.3.5 - 2025-09-20
Necessary release that fixes multiple issues
-
All browser-based fetchers (
DynamicFetcher/StealthyFetcher/...) and their session classes are now fetching websites 15-20%:- Page management is now much faster due to the logic improvement by @AbdullahY36 in #87
- Optimized the validation logic overall and improved page creation for sync fetches, which together introduced a lot of speed improvements
-
Big improvements to the stealth mode in
DynamicFetcherand its session classes by replacingrebrowser-playwrightwithPatchRight:- Before this update,
rebrowser-playwrightwas turned off when you enabledstealthandreal_chromebecause they weren't doing well together, but now we don't have this issue withPatchRight - You can now interact with Closed-Shadow Roots since
PatchRightcan handle them automatically.
- Before this update,
- Fixed a bug that happens while using the
remethod from theSelectorsclass. - Fixed a bug with
uncurlandcurl2fetchercommands in the Web Scraping Shell that made curl's--data-rawflag parse incorrectly. - Fixed a bug with the
viewcommand in the Web Scraping Shell that depended on the website's encoding to happen. - Fixed a bug with content converting that affected the
mcpmode andextractcommands.
- @AbdullahY36 made their first contribution in #87
v0.3.4 - 2025-09-16
Necessary release that fixes multiple issues
- Added all the fetchers session classes to the interactive shell to be available right away without import.
- Added a workaround for a bug with the Playwright API on Windows that happened while retrieving content while solving Cloudflare.
- Fixed an encoding issue with the
viewcommand in the interactive shell - Fixed a bug with the
max_pagesargument inAsyncStealthySessionthat was crashing the code. - Fixed an issue that happened with the last updates that made the
html_contentandprettifyproperties in theSelectorclass return bytes, depending on the encoding. Both are returning strings as they were.
v0.3.3 - 2025-09-15
- Removed the logic that is removing the default browser tab on browser-based fetchers since it caused a crashing error (Not happening on Mac, only managed to produce on Windows and Linux)
v0.3.2 - 2025-09-15
Release Notes for v0.3.2
-
Optional fetcher dependencies: All fetchers are now part of optional dependency groups, reducing core package size. So the base
scraplingmodule is now the parser only, and to use the fetchers or the commandline options, you have to do:pip install "scrapling[fetchers]". Check out the detailed installation instructions from here -
Per-page configuration in sessions: Session classes for browser fetchers now support individual configuration per page in sessions. All fetch-level parameters are now validated like session-level ones. More details on the documentation website here
Example:with StealthySession(headless=True, solve_cloudflare=True) as session: page = session.fetch('https://nopecha.com/demo/cloudflare', google_search=False)
-
Improved browser-based fetchers
- A new option to control whether to wait for JavaScript execution to finish in pages or not (it's enabled by default now, as it was before)
with DynamicSession(headless=True, disable_resources=False, network_idle=True) as session: page = session.fetch('https://quotes.toscrape.com/', load_dom=False)
- The Stealth mode is now more reliable in
DynamicFetcherand its session classes. - Both
DynamicFetcherandStealthyFetcherare now using fewer resources (Automatically finding and closing the default tab opened by Persistent contexts in Playwright API) - Fixed a vital logic bug in browser-based fetchers' pages rotation - previous pages are now replaced with fresh ones. (Tabs that get reused in rotation are possibly contaminated from previous settings used on them)
StealthyFetcherand its session classes are now slightly faster (5%)
- A new option to control whether to wait for JavaScript execution to finish in pages or not (it's enabled by default now, as it was before)
-
Enhanced
.bodyproperty: Now returns the passed content as-is without processing, enabling file downloads and handling non-HTML requests. Below is an example of downloading a photo:from scrapling.fetchers import Fetcher page = Fetcher.get('https://raw.githubusercontent.com/D4Vinci/Scrapling/main/images/poster.png') with open(file='poster.png', mode='wb') as f: f.write(page.body)
- Encoding issues resolved: Fixed multiple encoding problems that happened with some websites in parser, mcp mode, and extract commands (Also solves #80 and #81)
- Faster parsing: Due to many changes here and there, the library is now faster, and it's reflected in the updated benchmarks
- Updated benchmarks: Refreshed performance benchmarks to compare the current speed improvements to the latest versions of similar libraries
- Refactored a lot of the code and replaced dead code with better implementations: Fewer code, cleaner code, easier maintenance
- Added YouTube video: Included video content for MCP documentation.
- A new issues template: Easy new template for users who can't use the current templates.
- CI workflow optimization: Tests workflow now skips runs when only documentation or non-code files are changed.
- Updated dependencies: Bumped up various dependencies to the latest versions.
- Code style improvements: Applied new ruff rules across all files.
- Pre-commit hooks: Updated pre-commit configuration.
- Removed
max_pagesparameter from syncStealthySessionto matchDynamicSession(it's meaningless to have in the sync version)
v0.3.1 - 2025-09-02
- Fixed an issue with scrapling installation when you install it without the
shellextra (#76 ) - Added a new argument to all browser-based fetchers and their session classes to add a JS file to be executed on page creation (#56) :
from scrapling.fetchers import StealthyFetcher
StealthyFetcher.fetch('https://example.com', init_script="/absolute/path/to/js/script.js")v0.3 - 2025-09-01
🎉 Major Release — Complete Architecture Overhaul
Scrapling v0.3 represents the most significant update in the project's history, featuring a complete architectural rewrite, considerable performance improvements, and powerful new features, including AI integration and interactive Web Scraping shell capabilities.
This release includes multiple breaking changes; please review the release notes carefully.
- New Session Classes: Complete rewrite introducing persistent session support
FetcherSession- HTTP requests with persistent state management that works with both sync and async codeDynamicSession/AsyncDynamicSession- Browser automation while keeping the browser open till you finishStealthySession/AsyncStealthySession- Stealth browsing while keeping the browser open till you finish
- Async Browser Tabs Management: A new pool of tabs feature through the
max_pagesargument that rotates browser tabs for concurrent browser fetches - Concurrent Sessions: Run multiple isolated sessions simultaneously
Refer to the Fetching section on the website for more details.
- 🤖 Cloudflare Solver: Automatic Cloudflare Turnstile challenge solving in
StealthyFetcherand its session classes - Browser fingerprint impersonation: Mimic real browsers' TLS fingerprints, version-matching browser headers, HTTP/3 support, and more with the all-new Fetcher class
- Improved stealth mode: The stealth mode for
DynamicFetcherand its session classes is now more robust and reliable (AKAPlayWrightFetcher)
- Built-in MCP Server: Model Context Protocol server for AI-assisted web scraping
- 6 Powerful Tools:
get,bulk_get,fetch,bulk_fetch,stealthy_fetch,bulk_stealthy_fetch - Smart Content Extraction: Convert web pages/elements to Markdown, HTML, or extract a clean version of the text content
- CSS Selector Support: Use the Scrapling engine to target specific elements with precision before handing the content to the AI
- Anti-Bot Bypass: Handle Cloudflare Turnstile and other protections
- Proxy Support: Use proxies for anonymity and geo-targeting
- Browser Impersonation: Mimic real browsers with TLS fingerprinting, real browser headers matching that version, and more
- Parallel Processing: Scrape multiple URLs concurrently for efficiency
- and more...
- A New Shell: Custom IPython shell with many smart Built-in Shortcuts like
get,post,put,delete,fetch, andstealthy_fetch - Smart Page Management: New commands
pageandpagesto automatically store the current page and history for all requests done through the shell - Curl Integration: Convert browser DevTools curl commands with
uncurlandcurl2fetcherfunctions toFetcherrequests - and more...
- New Extract Commands: Terminal-based scraping without programming
scrapling extract get/post/put/delete- Simple HTTP requestsscrapling extract fetch- Dynamic content scrapingscrapling extract stealthy-fetch- Anti-bot bypass
- Downloads web pages and saves their content to files.
- Converts HTML to readable formats like Markdown, keeps it as HTML, or just extracts the text content of the page.
- Supports custom CSS selectors to extract specific parts of the page.
- Handles HTTP requests and fetching through browsers.
- Highly customizable with custom headers, cookies, proxies, and the rest of the options. Almost all the options available through the code are also accessible through the terminal.
- and more...
- Fetcher is now 4 times faster - Yes you have read it right!
- DynamicFetcher is now ~60% faster - A much faster version depending on your config (especially stealth mode)
- StealthyFetcher is now 20–30% faster - Using the new structure, and starting to use our implementation instead of
CamoufoxPython interface - 50%+ combined speed gains across core selection methods (
find_by_text,find_similar,find_by_regex,relocate, etc.) 🚀 - ~10% CSS/XPath first methods speed increase -
css_firstandxpath_firstare now faster thancssandxpath - 40% faster
get_all_text()method for content extraction - 20% speed improvement in adaptive element relocation
- Navigation properties optimization — Properties like
next,previous,below_elements, and more are now noticeably faster - 5x faster text cleaning operations
- Memory efficiency improvements with optimized imports and reduced overhead
- ⚡ Lightning-fast imports: Reduced startup time with optimized module loading
- Better benchmarks: All the speed improvements Scrapling got made it much faster than before, compared to other libraries (1775x faster than BeautifulSoup and 5.1x faster than AutoScraper, check benchmarks)
- Persistent Context: All browser-based fetchers now use persistent context by default. (Solves #64 too)
- Using msgspec to validate all browser-based fetchers very fast before running the requests, so now it's easier to debug errors.
- All cookies returned from fetchers are now matching the format accepted by the same fetcher. So you can retrieve cookies and pass them again to all fetchers and their session classes.
- Faster linting and formatting due to migrating to
ruff - Modern Build System: Migrated from setup.py to pyproject.toml 📦
- Better GitHub actions and workflows for smoother development and testing
- 🎨 Enhanced Type Hints: Complete type coverage with modern Python standards for better IDE support and reliability
- Cleaner Codebase: Removed dead code and optimized core functions 🧹
- 🚀 Backward Compatibility: Added shortcuts to maintain compatibility with older code
- Python 3.10+ Required: Dropped support for Python 3.9 and below
These renamings are intended to improve clarity and consistency, particularly for new users.
Adaptor→Selector: Core parsing class renamed (But still can be imported asAdaptorfor backward compatibility)Adaptors→Selectors: Collection class renamed (But still can be imported asAdaptorsfor backward compatibility)auto_match→adaptive: Parameter renamed across all methodsadaptor_arguments→selector_config: Configuration parameter renamedautomatch_domain→adaptive_domain: Domain parameter renamedadditional_arguments→additional_args: Shortened parameter name⚠️ text/body→content: Selector constructor parameter is now accepting bothstrandbytesformatPlayWrightFetcher→DynamicFetcher: Browser automation class renamed (But still can be imported asPlayWrightFetcherfor backward compatibility)- DynamicFetcher doesn't have the NSTBrowser logic/arguments anymore since it's pointless to leave this logic now anyway.
- StealthyFetcher's headless argument can't accept 'virtual' as an argument anymore since we are not using Camoufox's library right now in anything other than getting the browser installation path and the rest of the launch options
- Fixed nested children counting in ignored tags for
get_all_text(#61) - Fixed the issue with installation due to spaces in Python's executable path (#57)
- Resolved threading issues in storage with recursion handling while the adaptive feature is enabled
- Fixed argument precedence issues using the Sentinel pattern in
FetcherSession - Resolved proxy type handling in
StealthyFetcher - Fixed
refererandgoogle_searchargument conflicts - Fixed async stealth script injection problems
v0.2.99 - 2025-04-08
This is an essential update for everyone to fully enjoy Scrapling as it's intended.
- Yup, finally 😄 Check it out from here
- Now you can import all fetchers with
from scrapling.fetchers import Fetcher, AsyncFetcher, StealthyFetcher, PlayWrightFetcher, then use them directly likepage = Fetcher.get(...)without initialization.
This replaces this old importfrom scrapling.defaults import Fetcher, AsyncFetcher, StealthyFetcher, PlayWrightFetcher.
- Now there's new logic to enable automatch from fetchers or other parsing options. Check out the documentation page for details.
Old imports and logic are left usable with a warning for backward compatibility.
- Now, both
StealthyFetcherandPlayWrightFetcherhave a new argument while fetching calledwait. This makes the fetcher wait/sleep for a specific period (milliseconds) before closing the page and returning the response to you. - Now
StealthyFetchermethodsfetchandasync_fetchhave the argumentadditional_argumentsto be passed to Camoufox as additional settings, which takes higher priority than Scrapling's settings (#54 )
- Fixed a bug in
async_fetchin bothStealthyFetcherandPlayWrightFetcherclasses, with catching redirections.
Thanks for all your support and donations!
v0.2.98 - 2025-03-17
This is an essential update for everyone to enjoy Scrapling as it's intended fully
- All selection methods' memory usage is ~40% of previous memory usage and the speed slightly increased.
- Implemented Lazy loading for all submodules of the library so now what you use is what you load, for example:
Before the update this import
from scrapling import Adaptorwas using30-40mbof RAM because it loaded all fetchers and stuff with it too, now it uses~1.2mb. - The last update made the library use ~32% memory it used before with a large requests pool, now we adjusted the caching further to use even less than that.
- Overall speed increase in the parser by a slight 2-5%
Thanks for all your support and donations!
v0.2.97 - 2025-03-15
This is an essential update for everyone to fully enjoy Scrapling as it's intended
- With new limitations across the library over caching size you will notice significantly lower memory usage than before while doing large numbers of requests/operations.
- Refactored big parts of the fetchers to easier maintainability and small speed increase.
- Fixed a bug in
TextHandlerwhere importing it alone and passing a non-string value converts it to an empty string. Now anything passed toTextHandleris automatically converted to a string before being converted toTextHandler, this is forced on any value passed --TextHandleras the name implies is intended to work with strings only after all! (#45 ) - Fixed a bug where the
retriesarguments weren't taken into account in most AsyncFetcher methods.
- Update type hints for most arguments in all fetchers to be clearer and more accurate.
Thanks for all your support and donations!
v0.2.96 - 2025-03-05
This is an essential update for everyone to fully enjoy Scrapling as it's intended
- Added the
-foption toscrapling installto force reinstall browser dependencies. I recommend you doscrapling install -fnow to enjoy the big speed performanceStealthyFetcherjust got with the new Camoufox browser version :) - Fixed a bug in
TextHandlerwhere slicing returnedTextHandlersinstead ofTextHandlerand fixed the type hint there (#41 ) - Fixed an issue where
scrapling installmight in some instances drop the user into a Python shell!
v0.2.95 - 2025-02-25
This is an essential update for everyone to fully enjoy Scrapling as it's intended
- Fixed a bug in
Fetcherthat made headers generated by thestealthy_headersargument overwrite some of the headers provided by the user likeAccept(#39 ) - Improved the headers generation logic a bit so it should give a slight speed boost.
v0.2.94 - 2025-02-22
This is an essential update for everyone to fully enjoy Scrapling as it's intended
- Added the
historyproperty to all fetchers to show redirections (#32 ) - Fixed the logic of the
case_sensitiveargument logic for allre/re_first. This may make your code return different results if you were using it (but you probably deserve it because you noticed it wasn't working as intended and didn't open an issue LOL) - Updated dependencies and enabled
coopback again in the Camoufox engine (StealthyFetcher).
v0.2.93 - 2025-01-31
This is an essential update for everyone to fully enjoy Scrapling as it's intended
- The return type is now consistent across all the parser engine so you will always get a return type as one of these
Adaptor,Adaptors,TextHandler,TextHandlers,None, and a list in case you have mixed results like combined CSS selector. This allows a better coding experience with minimum manual type checking, makes the library more stable, and makes chaining methods always possible. - Most of the parser engine especially the
Adaptorclass got refactored to a cleaner version and most importantly a faster version. So now almost all the methods/properties, especially the searching methods, got a speed increase between 5-40%. Some methods got bigger speed boosts likefind_by_regexgot a ~60% speed boost! The automatch feature got a small ~5% speed boost. - Fixed logic bugs with the
find_all/findmethods that made the passed filters used in OR fashion and other times as an AND. So now all elements returned need to fulfill all filters you pass. - Now all regex-related methods return
TextHandler/TextHandlersfor easier methods chaining. - Added a new
below_elementsproperty that returns anAdaptorsobject of all elements under the current element in the DOM tree. - Now all methods/properties that were returning HTML source as string are now returning it as
TextHandlerso you can do regex easily on it etc... - StealthyFetcher is now a bit faster and more stealthy. Also, now it's possible to click Captchas in iframes like Cloudflare Turnstile.
- The auto-completion and type hints improved a lot in nearly half the library. Especially
Adaptor,TextHandler, andTextHandlers. - Now slicing
TextHandler, accessing by index, or using thesplitmethod returns anotherTextHandlerinstead of the standard Python string. Now almost all standard string operations/methods return otherTexthandlerinstead of standard string to make chaining methods/functions always possible. - Fixed some small bugs and typos. For example, the Fetcher async_put was doing post request instead of put request 😶🌫️
- Improved the README a bit till I finish the documentation website.
This was supposed to be a small update till version 0.3 but thought to make it better.
v0.2.92 - 2024-12-26
- Now response returned by browser-based fetchers uses more reliable data sources in cases where the page loaded uses many Iframes.
- Now installing
Scraplingis made even easier, you install it with pip then runscrapling installin the terminal and you are ready! - Fixed an inaccurate type hint in the parser.
v0.2.91 - 2024-12-19
- Fixed a bug where the logging fetch logging sentence was showing in the first request only.
- The default behavior for Playwright API while browsing a page is returning the first response that fulfills the load state given to the
gotomethod["load", "domcontentloaded", "networkidle"]so if a website has a wait page like Cloudflare's one that redirects you to the real website afterward, Playwright will return the first status code which in this case would be something like 403. This update solves this issue for bothPlaywrightFetcherandStealthyFetcheras both are using Playwright API so the result depends on Playwright's default behavior no more. - Added support for proxies that use SOCKS proxies in the
Fetcherclass. - Fixed the type hint for the
wait_selector_stateargument so now it will show the accurate values you should use while auto-completing.
v0.2.9 - 2024-12-16
- Introducing the long-awaited async support for Scrapling! Now you have the
AsyncFetcherclass version ofFetcher, and bothStealthyFetcherandPlayWrightFetcherhave a new method calledasync_fetchwith the same options.
>> from scrapling import StealthyFetcher
>> page = await StealthyFetcher().async_fetch('https://www.browserscan.net/bot-detection') # the async version of fetch
>> page.status == 200
True-
Now the
StealthyFetcherclass has thegeoipargument in its fetch methods which when enabled makes the class automatically use IP's longitude, latitude, timezone, country, and locale, then spoof the WebRTC IP address. It will also calculate and spoof the browser's language based on the distribution of language speakers in the target region. -
Added the
retriesargument toFetcher/AsyncFetcherclasses so now you can set the number of retries of each request done byhttpx. -
Added the
url_joinmethod toAdaptorand Fetchers which takes a relative URL and joins it with the current URL to generate an absolute full URL! -
Added the
keep_cdatamethod toAdaptorand Fetchers to stop the parser from removing cdata when needed. -
Now
Adaptor/Responsebodymethod returns the raw HTML response when possible (without processing it in the library). -
Adding logging for the
Responseclass so now when you use the Fetchers you will get a log that gives info about the response you got. Example:>> from scrapling.defaults import Fetcher >> Fetcher.get('https://books.toscrape.com/index.html') [2024-12-16 13:33:36] INFO: Fetched (200) <GET https://books.toscrape.com/index.html> (referer: https://www.google.com/search?q=toscrape) >>
-
Now using all standard string methods on a
TextHandlerlike.replace()will result in anotherTextHandler. It was returning the standard string before. -
Big improvements to speed across the library and improvements to stealth in Fetchers classes overall.
-
Added dummy functions like
extract_first`extract` which returns the same result as the parent. These functions are added only to make it easy to copy code from Scrapy/Parsel to Scrapling when needed as these functions are used there! -
Due to refactoring a lot of the code and using caching at the right positions, now doing requests in bulk will have a big speed increase.
-
Now the support for Python 3.8 has been dropped. (Mainly because Playwright stopped supporting it but it was a problematic version anyway)
-
The
debugargument has been removed from all the library, now if you want to set the library to debugging, do this after importing the library:>>> import logging >>> logging.getLogger("scrapling").setLevel(logging.DEBUG)
- Now WebGL is enabled by default as a lot of protections are checking if it's enabled now.
- Some mistakes and typos in the docs/README.
- All logging is now unified under the logger name
scraplingfor easier and cleaner control. We were using the root logger before. - Restructured the tests folder into a cleaner structure and added tests for the new features. All the tests were rewritten to a cleaner version and more tests were added for higher coverage.
- Refactored a big part of the code to be cleaner and easier to maintain.
All these changes were part of the changes I decided before to add with 0.3 but decided to add them here because it will be some time till the next version. Now the next step is to finish the detailed documentation website and then work on version 0.3
v0.2.8 - 2024-11-30
- This is a small update that includes some must-have quality-of-life changes to the code and fixed a typo in the main README file (#20)
v0.2.7 - 2024-11-26
- Now if you used the
wait_selectorargument withStealthyFetcherandPlayWrightFetcherclasses, Scrapling will wait again for the JS to fully load and execute like normal. If you used thenetwork_idleargument, Scrapling will wait for it again too after waiting for all of that. If the states are all fulfilled then no waiting happens, of course. - Now you can enable and disable ads on
StealthyFetcherwith thedisable_adsargument. This is enabled by default and it installs theublock originaddon. - Now you can set the locale used by
PlayWrightFetcherwith thelocaleargument. The default value is stillen-US. - Now the basic requests done through
Fetchercan accept proxies in this formathttp://username:password@localhost:8030. - The stealth mode improved a bit for
PlayWrightFetcher.
- Now enabling proxies on the
PlayWrightFetcherclass is not tied to thestealthmode being on or off (Thanks to @AbdullahY36 for pointing that out) - Now the
ResponseEncodingtests if the encoding returned from the response can be used with the page or not. If the returned encoding triggered an error, Scrapling defaults toutf-8
v0.2.6 - 2024-11-24
- Now the
PlayWrightFetchercan use the real browser directly with thereal_chromeargument passed to thePlayWrightFetcher.fetchfunction but this requires you to have Chrome browser installed. Scrapling will launch an instance of your Chrome browser and you can use most of the options as normal. (Before you only had thecdp_urlargument to do so) - Pumped up the version of headers generated for real browsers.
- Turns out the format of the browser headers generated by
BrowserForgewas outdated which made Scrapling detected by some protections so nowBrowserForgeis only used to generate real useragent. - Now the
hide_canvasargument is turned off by default as it's being detected by Google's ReCaptcha.
v0.2.5 - 2024-11-23
- Handled an error that happens with the 'wait_selector' argument if it resolved to more than 1 element. This affects the
StealthyFetcherand thePlayWrightFetcherclasses. - Fixed the encoding type in cases where the
content_typeheader gets value with parameters likecharset(Thanks to @andyfcx for #12 )
- Added more tests to cover new parts of the code and made tests run in threads.
- I updated the docs strings to be readable correctly on Sphinx's apidoc or similar tools.
- @andyfcx made their first contribution at #13
v0.2.4 - 2024-11-20
- Fixed a bug when retrieving response bytes after using the
network_idleargument in both theStealthyFetcherandPlayWrightFetcherclasses.
That was causing the following error message:
Response.body: Protocol error (Network.getResponseBody): No resource with given identifier found
- The PlayWright API sometimes returns empty status text with responses, so now
Scraplingwill calculate it manually if that happens. This affects both theStealthyFetcherandPlayWrightFetcherclasses.
v0.2.3 - 2024-11-19
- Fixed a bug with pip installation that prevented the stealth mode on PlayWright Fetcher from working entirely.
v0.2.2 - 2024-11-16
- Now if you don't want to pass arguments to the generated
Adaptorobject and want to use the default values, you can use this import instead for cleaner codeOtherwise>> from scrapling.default import Fetcher, StealthyFetcher, PlayWrightFetcher >> page = Fetcher.get('https://example.com', stealthy_headers=True)
>> from scrapling import Fetcher, StealthyFetcher, PlayWrightFetcher >> page = Fetcher(auto_match=False).get('https://example.com', stealthy_headers=True)
- Fixed a bug with the
Responseobject introduced with patch v0.2.1 yesterday that happened with some cases of nested selecting/parsing.
v0.2.1 - 2024-11-15
- Now the
Responseobject returned from all fetchers is the same as theAdaptorobject except it has these added attributes:status,reason,cookies,headers, andrequest_headers. Allcookies,headers, andrequest_headersare always of typedictionary.
So your code can now become like:Instead of before>> from scrapling import Fetcher >> page = Fetcher().get('https://example.com', stealthy_headers=True) >> print(page.status) 200 >> products = page.css('.product')
But I have left the>> from scrapling import Fetcher >> fetcher = Fetcher().get('https://example.com', stealthy_headers=True) >> print(fetcher.status) 200 >> page = fetcher.adaptor >> products = page.css('.product')
.adaptorproperty working for backward compatibility. - Now both the
StealthyFetcherandPlayWrightFetcherclasses can take aproxyargument with the fetch method which accepts a string or a dictionary. - Now the
StealthyFetcherclass has theos_randomizeargument with thefetchmethod. If enabled, Scrapling will randomize the OS fingerprints used. The default is Scrapling matching the fingerprints with the current OS.
- Fixed a bug that happens while passing headers with the
Fetcherclass. - Fixed a bug with parsing JSON responses passed from the fetcher-type classes.
- The text functionality behavior was to try to remove HTML comments before returning the text but that induced errors in some cases and made the code more complicated than needed. Now it has reverted to the default lxml behavior, you will notice a slight speed increase to all operations that counts on elements' text like selectors. Now if you want Scrapling to remove HTML comments from elements before returning the text to avoid the weird text-splitting behavior that's in lxml/parsel/scrapy, just keep the
keep_commentsargument set to True as it is by default.
v0.2 - 2024-11-11
- Introducing the
Fetchersfeature with 3 new main types to make Scrapling fetch pages for you with a LOT of options!- The
Fetcherclass for basic HTTP requests - The
StealthyFetcherclass is a completely stealthy fetcher that uses a stealthy modified version of Firefox. - The
PlayWrightFetcherclass that allows doing browser-based requests with Vanilla PlayWright, PlayWright with stealth mode made by me, Real browsers through CDP, and NSTBrowser's docker browserless!
- The
- Added the completely new
find_all/findmethods to find elements easily on the page with dark magic! - Added the methods
filterandsearchto theAdaptorsclass for easier bulk operations onAdaptorobject groups. - Added methods
css_firstandxpath_firstmethods for easier usage. - Added the new class type
TextHandlerswhich is used for bulk operations onTextHandlerobjects like theAdaptorsclass. - Added
generate_full_css_selectorandgenerate_full_xpath_selectormethods.
- Now the
Adaptorsclass version ofre_firstreturns the first result that matches in allAdaptorobjects inside instead of the faulty logic of returning the results ofre_firstof allAdaptorobjects. - Now if the user selects a text-type content to be returned from selected elements (like css
::textfunction) with any method like.cssor.xpath. TheAdaptorobject will return theTextHandlersclass instead of returning a list of strings like before. So now you can dopage.css('something::text').re_first(r'regex_pattern').json()instead ofpage.css('something::text')[0].re_first(r'regex_pattern').json() - Now
Adaptor/Adaptorsre/re_first arguments are consistent with theTextHandlerones. So now you haveclean_matchandcase_sensitivearguments. - Now the
auto_matchargument is enabled by default in the initialization ofAdaptorbut still you have to enable it while selecting elements if you want to enable it. (Not a bug but a design decision) - A lot of type-annotations corrections here and there for better auto-completion experience while you are coding with Scrapling.
- Renamed both
css_selectorandxpath_selectormethods togenerate_css_selectorandgenerate_xpath_selectorfor clarity and to not interrupt the auto-completion while coding. - Restructured most of the old code into a
coresubpackage and other design decisions for cleaner and easier maintenance in the future. - Restructured the tests folder into a cleaner structure and added tests for the new features. Also now tox environments are cached on GitHub for faster automated tests with each commit.
v0.1.2 - 2024-10-16
Changelog:
- Fixed a bug where the
keep_commentsargument is not working as intended. - Adjusted the text function to automatically remove HTML comments from elements before extracting its text to prevent Lxml different behavior, for example:
previously would result in this because of Lxml default behavior but now it would return the full text 'CONDITION: Excellent' This behavior is known with parsel\scrapy as well so wanted to handle it here.
>>> page = Adaptor('<span>CONDITION: <!-- -->Excellent</span>', keep_comments=True) >>> page.css('span::text') ['CONDITION: ', 'Excellent']
- Fixed a bug where the SQLite db file created by the library is not deleted when doing
pip uninstall scraplingor similar.
v0.1.1 - 2024-10-14
Minor fixes
v0.1 - 2024-10-13
Full Changelog: https://github.com/D4Vinci/Scrapling/commits/v0.1