What’s New

Scrapling

AI

Scrapling release notes.

Latest v0.4.12 · by ScraplingWebsiteD4Vinci/Scrapling

Changelog

v0.4.12

Release v0.4.12

Added
  • Spiders can now tune their own speed with AutoThrottle, which measures how fast each website answers and adjusts the delay of every domain on its own
  • Export your results to CSV and XML formats, with support for items with different keys and nested values written as JSON
  • The MCP server can now require authentication with a token and restrict which hostnames it answers to
  • Browsers now accept CDP URLs over HTTP, not just WebSocket ones
  • Docker images are now tagged with their release version instead of only latest
Changed
  • get_all_text() is now O(nodes) instead of walking up the ancestors of every single text node, making it around 5-6x faster on deeply nested pages
Fixed
  • Fixed cached responses losing all their cookies when the response came from a browser engine
  • Fixed StealthyFetcher forcing the en-US locale on every browser instead of following the system's locale
  • Fixed a misleading error message in the storage system and removed a dead call left after inserts

A release focused on making your spiders smarter about the websites they crawl

[!NOTE] Follow us on X for daily tips and tricks

🚀 New Stuff and quality of life changes
  • Spiders can now tune their own speed with AutoThrottle. Instead of guessing a download_delay that'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 the Retry-After header asks for) until that stops, then speeds back up. Your download_delay and any robots.txt Crawl-delay are still respected as the minimum. (Check the docs)

    class MySpider(Spider):
        name = "adaptive"
        start_urls = ["https://example.com"]
        autothrottle_enabled = 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.

🐛 Bug Fixes
  • 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 StealthyFetcher forcing the en-US locale 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.

Performance
  • 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.

🙏 Special thanks to the community for all the continuous testing and feedback


Big shoutout to our Platinum Sponsors

v0.4.11

Release v0.4.11

Added
  • Added ShopifySpider, the first platform spider template, to extract products from Shopify-powered stores through their JSON API
  • Added --executable-path option to the CLI browser commands scrapling extract fetch and scrapling extract stealthy-fetch to accept a custom Chromium-compatible browser executable
Changed
  • Made find_by_text and find_by_regex up to ~2x faster when first_match is enabled by wrapping elements lazily
Fixed
  • Fixed the MCP server's fetch tools crashing on pages containing control characters with the error All strings must be XML compatible

A solid update bringing the first platform spider template, a faster parser, and important fixes 🎉

[!NOTE] Follow us on X for daily tips and tricks

🚀 New Stuff and quality of life changes
  • 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-path to the CLI browser commands. Both scrapling extract fetch and scrapling extract stealthy-fetch now accept a custom Chromium-compatible browser executable, and fall back to the SCRAPLING_EXECUTABLE_PATH environment 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"
    
🤖 Quality of life changes
  • Made find_by_text and find_by_regex up to ~2x faster when first_match is 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
🐛 Bug Fixes
  • 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)

🙏 Special thanks to the community for all the continuous testing and feedback


Big shoutout to our Platinum Sponsors:

v0.4.10

Release v0.4.10

Added
  • Added a Scrapy integration that allows using Scrapling's parsing API inside existing Scrapy projects with the scrapling_response decorator
  • MCP server can now use a custom Chromium-compatible browser for all browser-based tools via scrapling mcp --executable-path or SCRAPLING_EXECUTABLE_PATH environment variable or per-request executable_path argument
Changed
  • Updated all browsers and fingerprints
Fixed
  • Fixed garbled text (mojibake) from browser fetchers on non-UTF-8 websites
  • Fixed LinkExtractor not filtering compound file extensions like .tar.gz
  • Fixed paused crawls losing their in-flight requests from checkpoints so resuming no longer skips them
  • Fixed spiders calculating wrong crawl delays from robots.txt Request-rate directives

A new update with a brand-new Scrapy integration and a batch of community fixes 🎉

[!NOTE] Follow us on X for daily tips and tricks

🚀 New Stuff and quality of life changes
  • Added a Scrapy integration so you can use Scrapling's parsing API inside your existing Scrapy projects without rewriting them. Put the scrapling_response decorator on any spider callback, and the response it receives becomes a Scrapling Response while 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 the SCRAPLING_EXECUTABLE_PATH environment variable, or per request with the executable_path argument, by @samrusani in #360 (Solves #347)

  • Updated all browsers and fingerprints. Run scrapling install --force after updating to refresh them.

🐛 Bug Fixes
  • Fixed garbled text (mojibake) from browser fetchers on non-UTF-8 websites by @yehudalevy-collab in #365 (Fixes #364).
  • Fixed LinkExtractor not filtering compound file extensions like .tar.gz by @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-rate directives through the Protego upgrade, with tests aligned by @Disaster-Terminator in #355.
Docs
  • Clarified how init_script interacts 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.

🙏 Special thanks to the community for all the continuous testing and feedback


Big shoutout to our Platinum Sponsors
v0.4.9

Release v0.4.9

Added
  • Added a --version flag to the CLI
Changed
  • Updated all browsers and fingerprints
  • Mixing a session-level proxy with a per-request proxies argument now raises an error instead of one being silently dropped
Fixed
  • Fixed the session-level proxy argument being silently ignored in HTTP sessions
  • Fixed browser navigations failing when combining init_script with user_data_dir
  • Fixed encoding detection when websites quote the charset value in the Content-Type header
  • Fixed an IndexError in adaptive element relocation when auto_save is enabled
  • Fixed spiders' checkpoint and cache saving crashing on Windows
  • Fixed incorrect similarity scoring in find_similar for elements with mismatched attribute counts

A maintenance update packed with community-reported fixes 🛠️

[!NOTE] Follow us on X for daily tips and tricks

🚀 New Stuff and quality of life changes
  • Updated all browsers and fingerprints. Run scrapling install --force after updating to refresh them.
  • Added a --version flag to the CLI by @ETM-Code in #303 (Solves #299)
🐛 Bug Fixes
  • Fixed the session-level proxy argument being silently ignored in HTTP sessions, which could leak your real IP (Solves #295). Note that mixing a session-level proxy with a per-request proxies argument (or vice versa) now raises an error instead of one being silently dropped.
  • Fixed browser navigations failing when combining init_script with user_data_dir (Solves #294).
  • Fixed encoding detection when websites quote the charset value in the Content-Type header by @Bortlesboat in #323.
  • Fixed an IndexError in adaptive element relocation when auto_save is enabled by @Mubashirrrr in #340.
  • Fixed spiders' checkpoint and cache saving crashing on Windows by @MrStarkEG in #344.
  • Fixed incorrect similarity scoring in find_similar for elements with mismatched attribute counts (Solves #322).
Docs
  • 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.

🙏 Special thanks to the community for all the continuous testing and feedback


Big shoutout to our Platinum Sponsors
v0.4.8

Release v0.4.8

Added
  • Add LinkExtractor primitive in scrapling.spiders to pull URLs out of a Response with controls for allowing and denying patterns and domains
  • Add CrawlSpider and CrawlRule generic spider templates to reduce boilerplate for following links matching patterns
  • Add SitemapSpider template that seeds crawls directly from sitemaps or robots.txt URLs with support for gzip-compressed sitemaps
Changed
  • Adaptive relocation now defaults to a 40% similarity threshold instead of 0 across all methods
  • Updated all browsers and fingerprints
Fixed
  • Fix Fetcher.configure(...) not applying to per-request calls
  • Fix AsyncFetcher.configure(...) not applying to per-request calls
  • Fix incorrect request fingerprinting that caused duplicate requests in spiders
  • Fix Adaptive scraping engine staying silent on weak matches by showing a warning with the top score when relocation fails

A big spider update that takes the crawling framework to the next level 🕷️

[!NOTE] Follow us on X for daily tips and tricks

🚀 New Stuff and quality of life changes
  • Added a LinkExtractor primitive in scrapling.spiders.LinkExtractor to pull URLs out of a Response. 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 CrawlSpider and CrawlRule generic spider templates so you no longer have to hand-write the same "follow links matching this pattern" boilerplate. Override rules() to return a list of CrawlRule objects, each pairing a LinkExtractor. (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 SitemapSpider template that seeds a crawl directly from a sitemap, or robots.txt URLs. 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 0 across 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 lower percentage deliberately if needed.

  • Updated all browsers and fingerprints. Run a new scrapling install --force after updating to refresh the browsers and fingerprints.

🐛 Bug Fixes
  • Fixed Fetcher.configure(...) not applying to per-request calls. Same fix applied to AsyncFetcher.
  • 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.
Docs
  • 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.

🙏 Special thanks to the community for all the continuous testing and feedback


Big shoutout to our Platinum Sponsors
v0.4.7

Release v0.4.7

Added
  • Add a screenshot MCP tool that captures a page and returns it as an ImageContent block, supporting PNG and JPEG, full-page captures, JPEG quality, and readiness controls
  • Add a custom session_id parameter to open_session to allow naming sessions meaningfully instead of random hex identifiers
Fixed
  • Fix FetcherSession state corruption and lazy session close crash
  • Fix TypeError when using the CLI's --ai-targeted flag with HTTP commands due to unexpected block_ads keyword argument

A focused update bringing eyes to your AI agents 📸

[!NOTE] Follow us on X for daily tips and tricks

🚀 New Stuff and quality of life changes
  • Added a screenshot MCP tool that captures a page and returns it as a real MCP ImageContent block so the model can actually see it. The tool requires an open browser session, so you call open_session first (either dynamic or stealthy) and pass the session_id here. 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_id parameter to open_session so you can name sessions meaningfully ("search", "checkout") instead of the random 12-character hex default. By @hauntedhost in #243
🐛 Bug Fixes
  • Fixed FetcherSession state 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-targeted flag with HTTP commands. By @voidborne-d in #249 (Fixes #247)
Translations
  • Added a Brazilian Portuguese README translation By @rgomids in #250

🙏 Special thanks to the community for all the continuous testing and feedback


Big shoutout to our Platinum Sponsors
v0.4.6

Release v0.4.6

Added
  • Added built-in ad blocking for browser fetchers with `block_ads=True` parameter to block requests to approximately 3,500 known ad and tracker domains at the route interception level
  • Added DNS-over-HTTPS support with `dns_over_https=True` parameter to prevent DNS leaks when using proxies by routing DNS queries through Cloudflare
  • Added `page_setup` callback for browser fetchers that runs before page navigation to register event listeners, routes, or scripts
  • Added `--block-ads` and `--dns-over-https` CLI options to both `fetch` and `stealthy-fetch` commands
Fixed
  • Fixed `Seconds` type alias rejecting float values in browser fetchers
  • Fixed duplicate ID segments in full-path selector generation where elements with `id` attributes had their selectors appended twice
  • Fixed full-path XPath generation emitting bare `[@id='x']` predicates instead of valid `*[@id='x']` syntax
  • Fixed missing shell signature parameters including `blocked_domains`, `block_ads`, `retries`, `retry_delay`, `capture_xhr`, `executable_path`, and `dns_over_https`

A focused update on browser stealth, privacy, and developer experience 🔒

[!NOTE] Follow us on X for daily tips and tricks

🚀 New Stuff and quality of life changes
  • Added built-in ad blocking for browser fetchers. Pass block_ads=True to block requests to ~3,500 known ad and tracker domains at the route interception level -- no DNS, no TCP, instant abort. Can be combined with blocked_domains for custom lists. The MCP server and CLI --ai-targeted mode 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=True to 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_setup callback for browser fetchers. A function that runs before page.goto(), letting you register event listeners, routes, or scripts that must be set up before the page navigates. Pairs with page_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-ads and --dns-over-https CLI options to both fetch and stealthy-fetch commands.
🐛 Bug Fixes
  • Fixed Seconds type alias rejecting float values. Passing wait=1.5 or timeout=500.0 to browser fetchers would fail with a type error because the type alias incorrectly treated float as metadata instead of a type. by @kuishou68 in #240
  • Fixed duplicate ID segments in full-path selector generation. Elements with id attributes had their selector appended twice when generating full CSS/XPath paths, producing selectors like body > #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, and dns_over_https from its function signatures.

🙏 Special thanks to the community for all the continuous testing and feedback


Big shoutout to our Platinum Sponsors
v0.4.5

Release v0.4.5

Added
  • Add Spider Development Mode that caches HTTP responses to disk on first run and replays them on subsequent runs, with cache_hits and cache_misses stat counters, enabled via development_mode class attribute and configurable via development_cache_dir
Changed
  • Change follow_redirects to default to "safe" across all HTTP fetchers, MCP server, and shell to reject redirects targeting internal/private IPs and protect against SSRF
Fixed
  • Fix force-stop losing checkpoint by writing checkpoint before calling cancel_scope.cancel() instead of after, preventing race conditions that deleted previous checkpoints

A focused update with one big quality-of-life feature for spider developers and a couple of important fixes 🎉

[!NOTE] Follow us on X for daily tips and tricks

🚀 New Stuff and quality of life changes
  • 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 with development_cache_dir. Two new stat counters, cache_hits and cache_misses, let you see how the cache performed. Cache replay bypasses download_delay, rate limiting, and the blocked-request retry path so iteration is as fast as the disk allows. Don't ship a spider with development_mode = True -- it's a development tool, not a production cache. See the docs for the full story.

  • Safer redirects by default: follow_redirects now 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. Pass follow_redirects="all" to get the old behavior, or False to disable redirects entirely.

🐛 Bug Fixes
  • Force-stop no longer loses your checkpoint: Pressing Ctrl+C twice (force-stop) on a spider with crawldir enabled used to race against the checkpoint write -- the cancel scope would tear down the task before the pickle finished, leaving paused=False and 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 calling cancel_scope.cancel(), so a force-stop always preserves the latest pending state. By @voidborne-d in #230.

🙏 Special thanks to the community for all the continuous testing and feedback


v0.4.4

Release v0.4.4

Added
  • Add robots.txt compliance to the Spider framework with a new robots_txt_obey option that automatically fetches and respects robots.txt rules including Disallow, Crawl-delay, and Request-rate directives
  • Add robots.txt cache pre-warming to fetch and parse robots.txt for all start_urls domains before the crawl loop begins
  • Add a new robots_disallowed_count stat to CrawlStats to track how many requests were blocked by robots.txt rules
  • Add protego as a new dependency under the fetchers optional group for robots.txt parsing
Changed
  • Update dependencies for latest fingerprints and related improvements
Fixed
  • Fix a critical MRO issue with ProxyRotator where the _build_context_with_proxy stub was shadowing the real implementation from child classes, causing proxy rotation to always raise NotImplementedError
  • Fix a page pool leak when using per-request proxy rotation with browser sessions where pages created inside temporary contexts were not removed from the pool on cleanup
  • Fix a missing type assertion in the static fetcher where curl_cffi could return None from session.request(), causing downstream errors

A new update with important spider improvements and bug fixes 🎉

🚀 New Stuff and quality of life changes
  • Added robots.txt compliance to the Spider framework with a new robots_txt_obey option. When enabled, the spider will automatically fetch and respect robots.txt rules before crawling, including Disallow, Crawl-delay, and Request-rate directives. 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_count stat to CrawlStats to track how many requests were blocked by robots.txt rules during a crawl.

Check it out on the website from here

🐛 Bug Fixes
  • Fixed a critical MRO issue with ProxyRotator where the _build_context_with_proxy stub was shadowing the real implementation from child classes, causing proxy rotation to always raise NotImplementedError (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_cffi could return None from session.request(), causing downstream errors.
Other
  • Updated dependencies, so expect the latest fingerprints and other stuff.
  • Added protego as a new dependency under the fetchers optional group for robots.txt parsing.

🙏 Special thanks to the community for all the continuous testing and feedback


Big shoutout to our Platinum Sponsors
v0.4.3

Release v0.4.3

Added
  • Add a new MCP tool to open a persistent normal or stealthy browser and another new tool to close it
  • Add a new MCP tool to list all existing browser sessions
  • Add a new option to browser sessions to automatically collect all background requests that happen during a request
  • Add a new sanitizer to protect the MCP server from common Prompt Injection attacks by removing hidden or invisible content
  • Add a new commandline option called --ai-targeted to Web Scraping commands to make content targeted to AI and safe against common Prompt Injection attacks
  • Add a new option to browser sessions called executable_path to allow setting a custom browser path
Changed
  • Refactor the MCP server code to be easily maintained and unify all tools to be async
  • Refactor the CLI commands code to be easily maintained and reduce it by 210 lines
  • Improve the skill structure to be more acceptable by Clawhub validation
  • Force the skill to use the --ai-targeted commandline option when scraping through commandline commands
Fixed
  • Preserve HTTP method across retries in spider session
  • Add a max retry limit to getting page content to prevent infinite loop
  • Replace bare raise with return False in _restore_from_checkpoint
  • Replace get_all with getall in TextHandler to match the Selector class
  • Fix broken markdown links in skill references

A new update with many important changes 🎉

🚀 New Stuff and quality of life 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-targeted to 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_path to 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.
🐛 Bug Fixes
  • 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 raise with return False in _restore_from_checkpoint by @haosenwang1018 in #196
  • Replaced get_all with getall in Texthandler to match the Selector class.
Coverage/tests improvement
  • Added _normalize_credentials edge case coverage tests by @Bortlesboat in #192
  • Added save/retrieve round-trip and core storage coverage tests by @haosenwang1018 in #193
  • Added coverage for TextHandler regex paths and TextHandlers.re() by @haosenwang1018 in #194
  • Added edge case tests for filter, iterancestors, and find_similar by @awanawana in #200
Agent Skill improvement
  • 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-targeted commandline option when scraping through commandline commands.
Docs improvement
  • 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.

🙏 Special thanks to the community for all the continuous testing and feedback


Big shoutout to our Platinum Sponsors