From c243c8aca5fe5155dd6476064fcf133aaf0a1939 Mon Sep 17 00:00:00 2001 From: ETM-Code <158038827+ETM-Code@users.noreply.github.com> Date: Sat, 30 May 2026 11:37:22 +0100 Subject: [PATCH 01/11] feat: add `--version` flag to the CLI - Add a `--version` flag to the main CLI group using click's `version_option` - Prints `Scrapling, version ` and exits, sourcing the version from `scrapling.__version__` - Add a CLI test asserting the flag's output Closes #299 --- scrapling/cli.py | 4 +++- tests/cli/test_cli.py | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/scrapling/cli.py b/scrapling/cli.py index 47056d9..4b9eb86 100644 --- a/scrapling/cli.py +++ b/scrapling/cli.py @@ -2,6 +2,7 @@ from pathlib import Path from subprocess import check_output from sys import executable as python_executable +from scrapling import __version__ from scrapling.core.utils import log from scrapling.engines.toolbelt.custom import Response from scrapling.core.utils._shell import _CookieParser, _ParseHeaders @@ -10,7 +11,7 @@ from scrapling.core._types import List, Optional, Dict, Tuple, Any, Callable from orjson import loads as json_loads, JSONDecodeError try: - from click import command, option, Choice, group, argument + from click import command, option, Choice, group, argument, version_option except (ImportError, ModuleNotFoundError) as e: raise ModuleNotFoundError( "You need to install scrapling with any of the extras to enable Shell commands. See: https://scrapling.readthedocs.io/en/latest/#installation" @@ -650,6 +651,7 @@ def stealthy_fetch( @group() +@version_option(version=__version__, prog_name="Scrapling") def main(): pass diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index 39345d0..642699d 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -4,8 +4,9 @@ from unittest.mock import patch, MagicMock import pytest_httpbin from scrapling.parser import Selector +from scrapling import __version__ from scrapling.cli import ( - shell, mcp, get, post, put, delete, fetch, stealthy_fetch + main, shell, mcp, get, post, put, delete, fetch, stealthy_fetch ) @@ -32,6 +33,12 @@ class TestCLI: def runner(self): return CliRunner() + def test_version_flag(self, runner): + """Test that the --version flag prints the Scrapling version and exits""" + result = runner.invoke(main, ['--version']) + assert result.exit_code == 0 + assert result.output.strip() == f'Scrapling, version {__version__}' + def test_shell_command(self, runner): """Test shell command""" with patch('scrapling.core.shell.CustomShell') as mock_shell: From 9e43062a63613d9785bd34226b4259181bc0a64f Mon Sep 17 00:00:00 2001 From: Evan Alferez Date: Mon, 1 Jun 2026 09:30:21 +0900 Subject: [PATCH 02/11] docs: use pyd4vinci/scrapling in remaining Docker examples (#282) PR #283 fixed the bare `scrapling` image name in docs/ai/mcp-server.md; the agent-skill MCP reference and CLI extract docs still used the unqualified name, which Docker resolves against the official library namespace and fails with pull access denied. --- agent-skill/Scrapling-Skill/references/mcp-server.md | 2 +- docs/cli/extract-commands.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/agent-skill/Scrapling-Skill/references/mcp-server.md b/agent-skill/Scrapling-Skill/references/mcp-server.md index 48a2d10..6aaa014 100644 --- a/agent-skill/Scrapling-Skill/references/mcp-server.md +++ b/agent-skill/Scrapling-Skill/references/mcp-server.md @@ -208,7 +208,7 @@ Docker alternative: ```bash docker pull pyd4vinci/scrapling -docker run -i --rm scrapling mcp +docker run -i --rm pyd4vinci/scrapling mcp ``` The MCP server name when registering with a client is `ScraplingServer`. The command is the path to the `scrapling` binary and the argument is `mcp`. \ No newline at end of file diff --git a/docs/cli/extract-commands.md b/docs/cli/extract-commands.md index 671cdcc..b3663c9 100644 --- a/docs/cli/extract-commands.md +++ b/docs/cli/extract-commands.md @@ -50,7 +50,7 @@ The extract command is a set of simple terminal tools that: scrapling extract get "https://example.com" content.txt # Or use the Docker image with something like this: - docker run -v $(pwd)/output:/output scrapling extract get "https://blog.example.com" /output/article.md + docker run -v $(pwd)/output:/output pyd4vinci/scrapling extract get "https://blog.example.com" /output/article.md ``` - **Extract Specific Content** From dbc6817f73655821c3fe9e06271b8426bef70892 Mon Sep 17 00:00:00 2001 From: Andrew Barnes Date: Mon, 1 Jun 2026 09:04:48 -0400 Subject: [PATCH 03/11] Fix CONTRIBUTING docs link --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3967c1d..c1ed65f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ There are many ways to contribute to Scrapling. Here are some of them: - Report bugs and request features using the [GitHub issues](https://github.com/D4Vinci/Scrapling/issues). Please follow the issue template to help us resolve your issue quickly. - Blog about Scrapling. Tell the world how you’re using Scrapling. This will help newcomers with more examples and increase the Scrapling project's visibility. - Join the [Discord community](https://discord.gg/EMgGbDceNQ) and share your ideas on how to improve Scrapling. We’re always open to suggestions. -- If you are not a developer, perhaps you would like to help with translating the [documentation](https://github.com/D4Vinci/Scrapling/tree/docs)? +- If you are not a developer, perhaps you would like to help with translating the [documentation](https://github.com/D4Vinci/Scrapling/tree/dev/docs)? ## Making a Pull Request To ensure that your PR gets accepted, please make sure that your PR is based on the latest changes from the dev branch and that it satisfies the following requirements: From 3a2dd29255547a07d5be5369a9f24da2664d75cd Mon Sep 17 00:00:00 2001 From: Karim shoair Date: Wed, 3 Jun 2026 20:43:40 +0300 Subject: [PATCH 04/11] docs: update a sponsor --- README.md | 2 +- docs/README_AR.md | 2 +- docs/README_CN.md | 2 +- docs/README_DE.md | 2 +- docs/README_ES.md | 2 +- docs/README_FR.md | 2 +- docs/README_JP.md | 2 +- docs/README_KR.md | 2 +- docs/README_PT_BR.md | 2 +- docs/README_RU.md | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ade1f5c..38af5bd 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,7 @@ MySpider().start() - 9Proxy provides residential proxies from just $0.015/IP or $0.68/GB. 20M+ IPs across 90+ countries. Sticky or rotating sessions, managed from desktop or mobile app. + 9Proxy provides residential proxies from just $0.018/IP or $0.68/GB. 20M+ IPs across 90+ countries. Sticky or rotating sessions, managed from desktop or mobile app. diff --git a/docs/README_AR.md b/docs/README_AR.md index f798baa..46057d5 100644 --- a/docs/README_AR.md +++ b/docs/README_AR.md @@ -185,7 +185,7 @@ MySpider().start() - يوفر 9Proxy بروكسيات سكنية بدءًا من 0.015 دولار فقط لكل IP أو 0.68 دولار لكل جيجابايت. أكثر من 20 مليون عنوان IP في أكثر من 90 دولة. جلسات ثابتة أو متناوبة، تتم إدارتها من تطبيق سطح المكتب أو الجوال. + يوفر 9Proxy بروكسيات سكنية بدءًا من 0.018 دولار فقط لكل IP أو 0.68 دولار لكل جيجابايت. أكثر من 20 مليون عنوان IP في أكثر من 90 دولة. جلسات ثابتة أو متناوبة، تتم إدارتها من تطبيق سطح المكتب أو الجوال. diff --git a/docs/README_CN.md b/docs/README_CN.md index 62b4747..5a05aa2 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -185,7 +185,7 @@ MySpider().start() - 9Proxy 提供住宅代理,价格低至每个 IP 仅 $0.015 或每 GB $0.68。覆盖 90+ 国家/地区的 2000 万+ IP。支持固定或轮换会话,可通过桌面或移动应用进行管理。 + 9Proxy 提供住宅代理,价格低至每个 IP 仅 $0.018 或每 GB $0.68。覆盖 90+ 国家/地区的 2000 万+ IP。支持固定或轮换会话,可通过桌面或移动应用进行管理。 diff --git a/docs/README_DE.md b/docs/README_DE.md index e3fb3e6..7f84eb3 100644 --- a/docs/README_DE.md +++ b/docs/README_DE.md @@ -185,7 +185,7 @@ MySpider().start() - 9Proxy bietet Residential-Proxys ab nur 0,015 $/IP oder 0,68 $/GB. Über 20 Mio. IPs in mehr als 90 Ländern. Sticky oder rotierende Sessions, verwaltet über die Desktop- oder Mobile-App. + 9Proxy bietet Residential-Proxys ab nur 0,018 $/IP oder 0,68 $/GB. Über 20 Mio. IPs in mehr als 90 Ländern. Sticky oder rotierende Sessions, verwaltet über die Desktop- oder Mobile-App. diff --git a/docs/README_ES.md b/docs/README_ES.md index 05d49a0..5933803 100644 --- a/docs/README_ES.md +++ b/docs/README_ES.md @@ -185,7 +185,7 @@ MySpider().start() - 9Proxy ofrece proxies residenciales desde solo $0,015/IP o $0,68/GB. Más de 20 millones de IPs en más de 90 países. Sesiones fijas o rotativas, gestionadas desde la aplicación de escritorio o móvil. + 9Proxy ofrece proxies residenciales desde solo $0,018/IP o $0,68/GB. Más de 20 millones de IPs en más de 90 países. Sesiones fijas o rotativas, gestionadas desde la aplicación de escritorio o móvil. diff --git a/docs/README_FR.md b/docs/README_FR.md index d1dc0fd..5520480 100644 --- a/docs/README_FR.md +++ b/docs/README_FR.md @@ -185,7 +185,7 @@ MySpider().start() - 9Proxy propose des proxys résidentiels à partir de seulement 0,015 $/IP ou 0,68 $/Go. Plus de 20 millions d'IPs dans plus de 90 pays. Sessions fixes ou rotatives, gérées depuis l'application de bureau ou mobile. + 9Proxy propose des proxys résidentiels à partir de seulement 0,018 $/IP ou 0,68 $/Go. Plus de 20 millions d'IPs dans plus de 90 pays. Sessions fixes ou rotatives, gérées depuis l'application de bureau ou mobile. diff --git a/docs/README_JP.md b/docs/README_JP.md index 9d4db25..1da34c2 100644 --- a/docs/README_JP.md +++ b/docs/README_JP.md @@ -185,7 +185,7 @@ MySpider().start() - 9Proxy はIPあたり $0.015 またはGBあたり $0.68 からの住宅用プロキシを提供します。90カ国以上で2,000万以上のIPを保有。固定セッションまたはローテーションセッションをデスクトップまたはモバイルアプリで管理できます。 + 9Proxy はIPあたり $0.018 またはGBあたり $0.68 からの住宅用プロキシを提供します。90カ国以上で2,000万以上のIPを保有。固定セッションまたはローテーションセッションをデスクトップまたはモバイルアプリで管理できます。 diff --git a/docs/README_KR.md b/docs/README_KR.md index 4a4ef26..58c8342 100644 --- a/docs/README_KR.md +++ b/docs/README_KR.md @@ -185,7 +185,7 @@ MySpider().start() - 9Proxy는 IP당 $0.015 또는 GB당 $0.68의 저렴한 가격부터 시작하는 주거용 프록시를 제공합니다. 90개국 이상에서 2천만 개 이상의 IP를 보유하고 있으며, 고정 또는 회전 세션을 데스크톱이나 모바일 앱에서 관리할 수 있습니다. + 9Proxy는 IP당 $0.018 또는 GB당 $0.68의 저렴한 가격부터 시작하는 주거용 프록시를 제공합니다. 90개국 이상에서 2천만 개 이상의 IP를 보유하고 있으며, 고정 또는 회전 세션을 데스크톱이나 모바일 앱에서 관리할 수 있습니다. diff --git a/docs/README_PT_BR.md b/docs/README_PT_BR.md index 23ce34b..b940441 100644 --- a/docs/README_PT_BR.md +++ b/docs/README_PT_BR.md @@ -187,7 +187,7 @@ MySpider().start() - 9Proxy oferece proxies residenciais a partir de apenas $0,015/IP ou $0,68/GB. Mais de 20M de IPs em mais de 90 países. Sessões fixas ou rotativas, gerenciadas pelo aplicativo desktop ou móvel. + 9Proxy oferece proxies residenciais a partir de apenas $0,018/IP ou $0,68/GB. Mais de 20M de IPs em mais de 90 países. Sessões fixas ou rotativas, gerenciadas pelo aplicativo desktop ou móvel. diff --git a/docs/README_RU.md b/docs/README_RU.md index 7660551..9bacd22 100644 --- a/docs/README_RU.md +++ b/docs/README_RU.md @@ -188,7 +188,7 @@ MySpider().start() - 9Proxy предоставляет резидентные прокси всего от $0,015 за IP или $0,68 за ГБ. Более 20 млн IP в 90+ странах. Закреплённые или ротационные сессии, управление через настольное или мобильное приложение. + 9Proxy предоставляет резидентные прокси всего от $0,018 за IP или $0,68 за ГБ. Более 20 млн IP в 90+ странах. Закреплённые или ротационные сессии, управление через настольное или мобильное приложение. From cd4cdc69e622c1e9f07a709cc7180a2ee6b6fd2a Mon Sep 17 00:00:00 2001 From: Mubashir Rahim Date: Thu, 4 Jun 2026 14:53:10 +0500 Subject: [PATCH 05/11] fix: prevent IndexError in adaptive relocation with auto_save When `css()`/`xpath()` are called with both `adaptive=True` and `auto_save=True`, the relocation branch guarded the re-save with `if elements is not None`. However `relocate()` returns an empty list (never `None`) when no candidate clears the `percentage` threshold, so the guard always passed and `self.save(elements[0], ...)` raised `IndexError: list index out of range`. This crashes exactly when adaptive resilience is needed most: the page structure changed enough that nothing matches above the threshold. Fix: use a truthiness check (`if elements and auto_save`) so the re-save is skipped when relocation yields nothing. The successful relocation path (which re-saves the relocated element) is unchanged. Added a regression test that fails before the fix (IndexError) and passes after. Co-Authored-By: Claude Opus 4.8 --- scrapling/parser.py | 2 +- tests/parser/test_adaptive.py | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/scrapling/parser.py b/scrapling/parser.py index 2b88af3..200cb43 100644 --- a/scrapling/parser.py +++ b/scrapling/parser.py @@ -671,7 +671,7 @@ class Selector(SelectorsGeneration): element_data = self.retrieve(identifier or selector) if element_data: elements = self.relocate(element_data, percentage) - if elements is not None and auto_save: + if elements and auto_save: self.save(elements[0], identifier or selector) return self.__handle_elements(elements) diff --git a/tests/parser/test_adaptive.py b/tests/parser/test_adaptive.py index 8d40f77..85881d1 100644 --- a/tests/parser/test_adaptive.py +++ b/tests/parser/test_adaptive.py @@ -56,6 +56,32 @@ class TestParserAdaptive: assert relocated[0].has_class("new-class") assert relocated[0].css(".new-description")[0].text == "Description 1" + def test_relocation_auto_save_no_match_above_threshold(self): + """Adaptive relocation with `auto_save=True` must not crash when no element + clears the `percentage` threshold (relocate() returns an empty list).""" + original_html = """ +
+
+

Widget

+

A widget

+
+
+ """ + # Unrelated structure so nothing can match a high threshold + changed_html = "totally unrelated content" + + old_page = Selector(original_html, url="example.com", adaptive=True) + new_page = Selector(changed_html, url="example.com", adaptive=True) + + old_page.css("#target", identifier="target", auto_save=True) + + # Before the fix this raised `IndexError: list index out of range` because the + # guard checked `elements is not None` but relocate() returns [] (never None). + result = new_page.css( + "#target", identifier="target", adaptive=True, auto_save=True, percentage=95 + ) + assert list(result) == [] + @pytest.mark.asyncio async def test_element_relocation_async(self): """Test relocating element after structure change in async mode""" From 141b389cdbfac338b1e5a285e94545f86d77b9c2 Mon Sep 17 00:00:00 2001 From: Karim shoair Date: Thu, 4 Jun 2026 17:47:40 +0300 Subject: [PATCH 06/11] docs: updating the support page title --- docs/donate.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/donate.md b/docs/donate.md index 0228bd6..dbaab5a 100644 --- a/docs/donate.md +++ b/docs/donate.md @@ -1,3 +1,5 @@ +# Support and Advertisement + I've been creating all of these projects in my spare time and have invested considerable resources & effort in providing them to the community for free. By becoming a sponsor, you'd be directly funding my coffee reserves, helping me fulfill my responsibilities, and enabling me to continuously update existing projects and potentially create new ones. You can sponsor me directly through the [GitHub Sponsors program](https://github.com/sponsors/D4Vinci) or [Buy Me a Coffee](https://buymeacoffee.com/d4vinci). From f0db3d7d1405544fdf5ba6eaed1791d50518d73a Mon Sep 17 00:00:00 2001 From: Ahmed Elshahat Date: Sun, 7 Jun 2026 04:24:12 +0300 Subject: [PATCH 07/11] fix(spiders): use os.replace for atomic checkpoint/cache writes on Windows CheckpointManager.save() and ResponseCacheManager.put() write to a temp file and then move it into place with Path.rename(). On Windows, os.rename cannot overwrite an existing destination and raises FileExistsError (WinError 183), so every write after the first one fails: checkpoint saving raises and breaks resume, while the development response cache swallows the error and keeps returning the stale entry. Path.replace() (os.replace) overwrites the destination atomically on every platform and behaves identically to rename() on POSIX, so this is a no-op on Linux and macOS and only fixes the broken overwrite on Windows. Add a regression test for the cache overwrite path; the checkpoint overwrite is already covered by test_multiple_saves_overwrite. --- scrapling/spiders/cache.py | 2 +- scrapling/spiders/checkpoint.py | 2 +- tests/spiders/test_cache.py | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/scrapling/spiders/cache.py b/scrapling/spiders/cache.py index 40d39d3..0305aef 100644 --- a/scrapling/spiders/cache.py +++ b/scrapling/spiders/cache.py @@ -64,7 +64,7 @@ class ResponseCacheManager: async with await anyio.open_file(temp_path, "wb") as f: await f.write(serialized) - await temp_path.rename(self._cache_path(fingerprint)) + await temp_path.replace(self._cache_path(fingerprint)) except Exception as e: if await temp_path.exists(): await temp_path.unlink() diff --git a/scrapling/spiders/checkpoint.py b/scrapling/spiders/checkpoint.py index 25de362..95515bf 100644 --- a/scrapling/spiders/checkpoint.py +++ b/scrapling/spiders/checkpoint.py @@ -50,7 +50,7 @@ class CheckpointManager: async with await anyio.open_file(temp_path, "wb") as f: await f.write(serialized) - await temp_path.rename(self._checkpoint_path) + await temp_path.replace(self._checkpoint_path) log.info(f"Checkpoint saved: {len(data.requests)} requests, {len(data.seen)} seen URLs") except Exception as e: diff --git a/tests/spiders/test_cache.py b/tests/spiders/test_cache.py index fc9bc59..eb1b04b 100644 --- a/tests/spiders/test_cache.py +++ b/tests/spiders/test_cache.py @@ -49,6 +49,27 @@ class TestResponseCacheManager: assert dict(restored.headers) == dict(original.headers) assert dict(restored.request_headers) == dict(original.request_headers) + @pytest.mark.anyio + async def test_put_overwrites_existing_entry(self): + """Re-caching the same fingerprint must replace the stored response. + + Regression test for a Windows-only failure: ``Path.rename`` cannot + overwrite an existing destination on Windows (raising ``WinError 183``), + so the second ``put`` was caught by the error handler, the temp file was + removed, and ``get`` kept returning the stale body. ``Path.replace`` + overwrites atomically on every platform. + """ + with tempfile.TemporaryDirectory() as tmpdir: + cache = ResponseCacheManager(tmpdir) + fp = b"\x05" * 20 + + await cache.put(fp, _make_response(body=b"first"), "GET") + await cache.put(fp, _make_response(body=b"second"), "GET") + + restored = await cache.get(fp) + assert restored is not None + assert restored.body == b"second" + @pytest.mark.anyio async def test_get_cache_miss(self): with tempfile.TemporaryDirectory() as tmpdir: From b9bc17b46109da28be7c7b89ef398904ec15b56b Mon Sep 17 00:00:00 2001 From: Karim shoair Date: Sun, 7 Jun 2026 15:11:06 +0300 Subject: [PATCH 08/11] docs: removing a sponsor --- README.md | 10 ---------- docs/README_AR.md | 10 ---------- docs/README_CN.md | 10 ---------- docs/README_DE.md | 10 ---------- docs/README_ES.md | 10 ---------- docs/README_FR.md | 10 ---------- docs/README_JP.md | 10 ---------- docs/README_KR.md | 10 ---------- docs/README_PT_BR.md | 10 ---------- docs/README_RU.md | 10 ---------- docs/index.md | 3 --- images/nsocks.png | Bin 23086 -> 0 bytes 12 files changed, 103 deletions(-) delete mode 100644 images/nsocks.png diff --git a/README.md b/README.md index 38af5bd..4507844 100644 --- a/README.md +++ b/README.md @@ -141,16 +141,6 @@ MySpider().start() TikHub.io provides 900+ stable APIs across 16+ platforms including TikTok, X, YouTube & Instagram, with 40M+ datasets.
Also offers DISCOUNTED AI models - Claude, GPT, GEMINI & more up to 71% off. - - - - - - - - Nsocks provides fast Residential and ISP proxies for developers and scrapers. Global IP coverage, high anonymity, smart rotation, and reliable performance for automation and data extraction. Use Xcrawl to simplify large-scale web crawling. - - diff --git a/docs/README_AR.md b/docs/README_AR.md index 46057d5..2070bc9 100644 --- a/docs/README_AR.md +++ b/docs/README_AR.md @@ -137,16 +137,6 @@ MySpider().start() TikHub.io يوفر أكثر من 900 واجهة API مستقرة عبر أكثر من 16 منصة تشمل TikTok و X و YouTube و Instagram، مع أكثر من 40 مليون مجموعة بيانات.
يقدم أيضاً نماذج ذكاء اصطناعي بأسعار مخفضة - Claude و GPT و GEMINI والمزيد بخصم يصل إلى 71%. - - - - - - - - Nsocks يوفر بروكسيات سكنية و ISP سريعة للمطورين والسكرابرز. تغطية IP عالمية، إخفاء هوية عالي، تدوير ذكي، وأداء موثوق للأتمتة واستخراج البيانات. استخدم Xcrawl لتبسيط زحف الويب على نطاق واسع. - - diff --git a/docs/README_CN.md b/docs/README_CN.md index 5a05aa2..0750bcc 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -137,16 +137,6 @@ MySpider().start() TikHub.io 提供覆盖 16+ 平台(包括 TikTok、X、YouTube 和 Instagram)的 900+ 稳定 API,拥有 4000 万+ 数据集。
还提供优惠 AI 模型 - Claude、GPT、GEMINI 等,最高优惠 71%。 - - - - - - - - Nsocks 提供面向开发者和爬虫的快速住宅和 ISP 代理。全球 IP 覆盖、高匿名性、智能轮换,以及可靠的自动化和数据提取性能。使用 Xcrawl 简化大规模网页爬取。 - - diff --git a/docs/README_DE.md b/docs/README_DE.md index 7f84eb3..ac6e533 100644 --- a/docs/README_DE.md +++ b/docs/README_DE.md @@ -137,16 +137,6 @@ MySpider().start() TikHub.io bietet über 900 stabile APIs auf mehr als 16 Plattformen, darunter TikTok, X, YouTube und Instagram, mit über 40 Mio. Datensätzen.
Bietet außerdem vergünstigte KI-Modelle - Claude, GPT, GEMINI und mehr mit bis zu 71% Rabatt. - - - - - - - - Nsocks bietet schnelle Residential- und ISP-Proxies für Entwickler und Scraper. Globale IP-Abdeckung, hohe Anonymität, intelligente Rotation und zuverlässige Leistung für Automatisierung und Datenextraktion. Verwenden Sie Xcrawl, um großflächiges Web-Crawling zu vereinfachen. - - diff --git a/docs/README_ES.md b/docs/README_ES.md index 5933803..1af1f9a 100644 --- a/docs/README_ES.md +++ b/docs/README_ES.md @@ -137,16 +137,6 @@ MySpider().start() TikHub.io ofrece más de 900 APIs estables en más de 16 plataformas, incluyendo TikTok, X, YouTube e Instagram, con más de 40M de conjuntos de datos.
También ofrece modelos de IA con descuento - Claude, GPT, GEMINI y más con hasta un 71% de descuento. - - - - - - - - Nsocks ofrece proxies residenciales e ISP rápidos para desarrolladores y scrapers. Cobertura IP global, alto anonimato, rotación inteligente y rendimiento fiable para automatización y extracción de datos. Usa Xcrawl para simplificar el crawling web a gran escala. - - diff --git a/docs/README_FR.md b/docs/README_FR.md index 5520480..c6d76ed 100644 --- a/docs/README_FR.md +++ b/docs/README_FR.md @@ -137,16 +137,6 @@ MySpider().start() TikHub.io propose plus de 900 APIs stables sur plus de 16 plateformes, dont TikTok, X, YouTube et Instagram, avec plus de 40M de jeux de données.
Propose également des modèles IA à prix réduit - Claude, GPT, GEMINI et plus, jusqu'à 71% de réduction. - - - - - - - - Nsocks fournit des proxies résidentiels et ISP rapides pour les développeurs et les scrapeurs. Couverture IP mondiale, anonymat élevé, rotation intelligente et performances fiables pour l'automatisation et l'extraction de données. Utilisez Xcrawl pour simplifier le crawling web à grande échelle. - - diff --git a/docs/README_JP.md b/docs/README_JP.md index 1da34c2..dcf44fc 100644 --- a/docs/README_JP.md +++ b/docs/README_JP.md @@ -137,16 +137,6 @@ MySpider().start() TikHub.io は TikTok、X、YouTube、Instagram を含む 16 以上のプラットフォームで 900 以上の安定した API を提供し、4,000 万以上のデータセットを保有。
さらに 割引 AI モデルも提供 - Claude、GPT、GEMINI など最大 71% オフ。 - - - - - - - - Nsocks は開発者やスクレイパー向けの高速なレジデンシャルおよび ISP プロキシを提供。グローバル IP カバレッジ、高い匿名性、スマートなローテーション、自動化とデータ抽出のための信頼性の高いパフォーマンス。Xcrawl で大規模ウェブクローリングを簡素化。 - - diff --git a/docs/README_KR.md b/docs/README_KR.md index 58c8342..18d2e05 100644 --- a/docs/README_KR.md +++ b/docs/README_KR.md @@ -137,16 +137,6 @@ MySpider().start() TikHub.io는 TikTok, X, YouTube, Instagram 등 16개 이상 플랫폼에서 900개 이상의 안정적인 API를 제공하며, 4,000만 이상의 데이터셋을 보유하고 있습니다.
할인된 AI 모델도 제공 - Claude, GPT, GEMINI 등 최대 71% 할인. - - - - - - - - Nsocks는 개발자와 스크레이퍼를 위한 빠른 레지덴셜 및 ISP 프록시를 제공합니다. 글로벌 IP 커버리지, 높은 익명성, 스마트 로테이션, 자동화와 데이터 추출을 위한 안정적인 성능. Xcrawl로 대규모 웹 크롤링을 간소화하세요. - - diff --git a/docs/README_PT_BR.md b/docs/README_PT_BR.md index b940441..4c7f617 100644 --- a/docs/README_PT_BR.md +++ b/docs/README_PT_BR.md @@ -139,16 +139,6 @@ MySpider().start() TikHub.io oferece mais de 900 APIs estáveis em mais de 16 plataformas, incluindo TikTok, X, YouTube e Instagram, com mais de 40M de datasets.
Também oferece modelos de IA com desconto - Claude, GPT, GEMINI e mais com até 71% de desconto. - - - - - - - - Nsocks fornece proxies residenciais e ISP rápidos para desenvolvedores e scrapers. Cobertura global de IPs, alto anonimato, rotação inteligente e desempenho confiável para automação e extração de dados. Use o Xcrawl para simplificar o crawling web em larga escala. - - diff --git a/docs/README_RU.md b/docs/README_RU.md index 9bacd22..4dde2b9 100644 --- a/docs/README_RU.md +++ b/docs/README_RU.md @@ -140,16 +140,6 @@ MySpider().start() TikHub.io предоставляет более 900 стабильных API на 16+ платформах, включая TikTok, X, YouTube и Instagram, с более чем 40 млн наборов данных.
Также предлагает AI-модели со скидкой - Claude, GPT, GEMINI и другие со скидкой до 71%. - - - - - - - - Nsocks предоставляет быстрые резидентные и ISP прокси для разработчиков и скраперов. Глобальное покрытие IP, высокая анонимность, умная ротация и надёжная производительность для автоматизации и извлечения данных. Используйте Xcrawl для упрощения масштабного веб-краулинга. - - diff --git a/docs/index.md b/docs/index.md index b9ea5ca..fdae518 100644 --- a/docs/index.md +++ b/docs/index.md @@ -71,9 +71,6 @@ MySpider().start() - - - diff --git a/images/nsocks.png b/images/nsocks.png deleted file mode 100644 index 120a06c26c5297cd2e6377bde50fd55716a03c2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23086 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%0DJEBmCLWV`hbHE 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%0AbXoiRcVl%Tc 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 N002ovPDHLkV1ff;WPtzx From 9c0c857245726352a8502b1e4822a444ade96543 Mon Sep 17 00:00:00 2001 From: Karim shoair Date: Sun, 7 Jun 2026 15:51:10 +0300 Subject: [PATCH 09/11] fix(parser): use max of both attribute counts in similarity scoring denominator Candidates with fewer attributes than the original got inflated scores because the denominator counted candidate attributes only, while the extra-attributes penalty direction worked as intended. Using `max()` on both counts fixes the inflation while keeping the penalty. Closes #322 --- scrapling/parser.py | 4 ++- tests/parser/test_find_similar_advanced.py | 37 ++++++++++++++++++---- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/scrapling/parser.py b/scrapling/parser.py index 2b88af3..a075f2b 100644 --- a/scrapling/parser.py +++ b/scrapling/parser.py @@ -991,7 +991,9 @@ class Selector(SelectorsGeneration): SequenceMatcher(None, v, candidate_attributes.get(k, "")).ratio() for k, v in original_attributes.items() ) - checks += len(candidate_attributes) + # Using `max` so candidates with extra attributes are penalized and candidates + # with fewer attributes don't get inflated scores from a smaller denominator + checks += max(len(original_attributes), len(candidate_attributes)) else: if not candidate_attributes: # Both don't have attributes, this must mean something diff --git a/tests/parser/test_find_similar_advanced.py b/tests/parser/test_find_similar_advanced.py index 099b0dd..95e1e69 100644 --- a/tests/parser/test_find_similar_advanced.py +++ b/tests/parser/test_find_similar_advanced.py @@ -2,6 +2,7 @@ Tests for Selector.find_similar() with non-default parameters. Target file: tests/parser/test_general.py (append to TestSimilarElements class) """ + import pytest from scrapling import Selector @@ -61,14 +62,10 @@ class TestFindSimilarAdvanced: first = product_page.css("div.product")[0] # Ignore both data-price and data-category → only class matters → all 3 divs match ignore_all_data = first.find_similar( - similarity_threshold=0.2, - ignore_attributes=["data-price", "data-category"] + similarity_threshold=0.2, ignore_attributes=["data-price", "data-category"] ) # Ignore nothing → data-category difference (fruit vs veggie) may reduce matches - ignore_nothing = first.find_similar( - similarity_threshold=0.9, - ignore_attributes=[] - ) + ignore_nothing = first.find_similar(similarity_threshold=0.9, ignore_attributes=[]) assert len(ignore_all_data) >= len(ignore_nothing) def test_find_similar_on_text_node_returns_empty(self, product_page): @@ -76,3 +73,31 @@ class TestFindSimilarAdvanced: text_node = product_page.css(".name::text")[0] result = text_node.find_similar() assert len(result) == 0 + + def test_find_similar_attribute_count_mismatch_scoring(self): + """The similarity denominator uses max() of both attribute counts, so candidates + with fewer attributes don't get inflated scores and candidates with extra + attributes stay penalized.""" + html = """ + +
+
Alpha
+
Beta
+
Gamma
+
Delta
+
+ + """ + page = Selector(html, adaptive=False) + first = page.css("div.card")[0] # Alpha + + similar = first.find_similar(similarity_threshold=0.9, ignore_attributes=[]) + texts = {el.text for el in similar} + + # An exact attribute match must pass + assert "Delta" in texts + # Beta matches 1 of Alpha's 4 attributes; the old denominator counted candidate + # attributes only, inflating it to a perfect score (1.0 / 1) + assert "Beta" not in texts + # Gamma's extra attribute dilutes the score (4.0 / 5) - the intentional penalty + assert "Gamma" not in texts From 4ced6e4ac26893304464f6368454a80830c3263d Mon Sep 17 00:00:00 2001 From: Karim shoair Date: Sun, 7 Jun 2026 16:16:23 +0300 Subject: [PATCH 10/11] build: pump up version, browsers and deps --- .github/workflows/tests.yml | 2 +- agent-skill/Scrapling-Skill.zip | Bin 90018 -> 90020 bytes agent-skill/Scrapling-Skill/SKILL.md | 4 ++-- .../Scrapling-Skill/examples/README.md | 2 +- pyproject.toml | 8 ++++---- scrapling/__init__.py | 2 +- scrapling/engines/toolbelt/fingerprints.py | 4 ++-- server.json | 4 ++-- setup.cfg | 2 +- tests/requirements.txt | 2 +- tox.ini | 4 ++-- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f82605d..930d0a0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -73,7 +73,7 @@ jobs: - name: Install all browsers dependencies run: | python3 -m pip install --upgrade pip - python3 -m pip install playwright==1.59.0 patchright==1.59.1 + python3 -m pip install playwright==1.60.0 patchright==1.60.1 - name: Get Playwright version id: playwright-version diff --git a/agent-skill/Scrapling-Skill.zip b/agent-skill/Scrapling-Skill.zip index 22fbb3231c28ed74331a39fbddbe65c71e15ae82..1a8d15b54cc8f92b9465e6ece763689a08e89e50 100644 GIT binary patch delta 9276 zcmZ{KbySpF_y0UI)X*_VBi#bh4N}sb(gKo#G$JrGNP9;Z5RgW?Q$SLXZV-_M=}wXO zf!_PR_g(Azd)7KLv+L}A_C9;hAD<_y1vRGy6-PrEf&v5GJgOkbY9M=Q&cE_OKwMhx`~W)K^YZyZS_yZEaA)$G@+`<7=V zp7$+UAX4{Nj4O(H`uO#{mM?Z8_qi!?MU#z-D#~4Gk92JMG7UN0i><#Y!kWfk7N`o` zBf2c@41EWWGUiMmrof8%$?r)-^3fQYoZurQn49G6W>>PFd!h1rdED_YIW{atH9@&& z%4vnyQeU-tU?L@NRkTn`)m3{dMpwejF&9^Gos z0O$rLWtHMsW(;E!^QL^-VBcua3z!};oh~71bDJJr7*xYG{`qPipM# zoIK1btk?p@AWn~-v{N-!qnjy85cP|pF#eV*X4R8;qnOKBmhxg0*M@^eJt-W9hnFpaZh&|%>Y`wt$!%@{7wFTa z!QmJqe?w*;Q4VQ>ZWh&RK!+(N?H)g7<|-jsUC$fk12&qBS${ zawf~CYaiE0x9#e)*2w7>EGOk5Hm%+~GRYnSk-7vL_Be@$)~63rO6DWh0^l~4jpc{- zwS+%m!%iW+E0hUYDxH*X$H__y3z~*|d)%r$ZOj`$!>=+Tb)n?h=$q0fqG^`+V#9E) z)UB-VpsO_Vd~DOCXgz9Do|#B2Xr~Z*N0c^RQULEl;t?6t98I=D#DZ_UTcN*xEIClI z6^*el(J4OfZ2x?*Po}oA3a+*W>sG{;!K7jML8pu(^?Fu8oJVUrP$6QmRl=UnC~ewN z0O>hVfHE;8Kp8~Yg=yq2QpQ*;0{Q;RPZW#lne9ouv#X+nwzTX7BMwR!?A5pt(FTuO zAoh^cJ(VA?8GSxZlP?6?RqSk=sl}?9^6``1iLE`Y>74m-qm1R^kO0di<)z=R^&c2e z`4em$V=r3C`3V`sY;f~PDZ^LXG+Zi5n+WMbSWgAaN|(Um32ADUp2kTL0X56|Ty1G6 z84))2NvF6g{T+0Pt?>4j*23(GLC$kCC`3=_$^ot2(}En zhy3B<*jLv(JsW*>&P?|^4`!yy;^xGN_u}UK_n zJ`Ig4+>r??@Fog5P>N?;iqX{*^WZGb?aY7Qtz0=DRWZ>9Jqhrig{>0`mp0%2fnhN{ z0>wQp$E)bVBiw1gx$Z5MqJg7kO8Ey*tD>2rQuL5~C^MUIxqfobhJ{V7j>wSD4_dVG zU?gbxElr2jCPwqs0Wrc2iyk;aAhD>ac<(Ksbq2VgaqK7ds5#aksgYj&;$12Kb!JFjEFTffNtKaaSq)pV*=$D$Ve1TnECVh`_XlhcA%z>-r<(`w1OtUVuN>iqFOu+E+fuhWI^06m7D>SA)CXhPyN|=n(MdZI zc0Y~ufuqBLjjFQu&Dr5z5PdHStp672cN%mf8!w69n9p_49ec*99yc zWKm`F&4om$&smZ*qC;4iV;gF)v>D(ia=Ni@+HYH6E*PXyJlb{9b*Onx(P!{)OY;4T zI}h^+gQ?mA2w&irwW|2T%w18?d_fJL6dj+mURr0faCgaCO>2Q|?EAD1H{3y0)9pD0 z4WvOXSwaJm1Wo<@yRX1MaG1VgkaBOr?6WDstNR$xZ&%? z@{7&8TJ4d zlZ(Us^ugK{(+IZKLL7qEmd}df56=P()aCjx#Dw=Pra+`VF5A%o)pS)^bx8ZZhe`k^ zrUH2%!v$%~9OQuVOCvWDf&sl(Iehg6Afn`Q22sU$&egF5@2;z@ufu2jmEH;I5OLRt zt>6qCsH67terRSftWFdulIFqu#%r>(5gqhl?*d79 z|IoT$gR&xiAp4}eTw-FXfmWt>W-t*p_&r%BapQ{v;P zre?Tb0D{lTp<8^RIYQnRKGv`QOdr3vR&>l`{$sUa(F;rN_>fHaGx*Ou#jVD{aZ#qB zT-)}cBT>Ajty5OKRWbkb^#hW(yAqd^#XL{?HUD}6ro7DBmUr(np0bT-x<|l_YZk$o zzx32QBs==^)(#Oy%zb&hcFHzQm)@W93LPA@NZBgN&lSBEXetyjw$9C`KIcl$`fyew zbOkPbMsXC|^f-3C;6gO_!b3}Chim|OQQ-gycFB1Xh6T=pvog_(Qh%?=4*0C5_lczg zrM+XSGQoQz(pJfY2mWo7{354{M27uU&$qVaJo>PYjrs z65=JK`B+vtMsPkSFlv`-<2PLL043*d_`Pzk8_ViRq~5Ec9&n-Wr17w~>^3Q3v1E-8 zG-Ra&eB6(;#HD35IB(*9$Rg#gD6zAr25mO;5-F=4jK^mF(ICM%2aZfVG1KI1f{t_d zY!9af*C`JcPHmRdHBzJIOEA9wlpM0tYVWq)Dh)dha)2j}3uE4^RZb9dONgh;povaaXtL z4ZqYZ57AN2A-6$>S4wXsJKvAEdXN0)`DT>~(xtWHXAG7SPhq1NFo~x$t;g+87IbPR z+Cl=pyTVg@Rm<=F#Z6HN^L}h76cYX{ppyPH_?u{{M}N(k-`Q%FQ)mB>%;ASwXi48? z=sOZ?m8V>+1$(A^=0uHJH!<&8M8)qJKK%>nJ3hEzZ+){50>X|*N zsbYNnCAwCpZsA*sG4UuPo9FBv`(G}!n1yMWfwVm{#SUemgViRX2GH?TiK$gjb&;^5 zGN%L|@)}q5lZ+B=v+uDt5yZkTORWy7NThmL1~2(%JDgUSDCl@}q*+<9_a2p{J1DQ( zNgOZ2*Zp%xeJd6}9?K30Ds$j(-E0$25*o&cb-uTyMxKYL3-z=1J)x?bu8P)^n@y_< zefzfQ$(LPjOSC2|D(Hf2$w2;Jn`oh)vY*npnvu%fr9FJ*oSHGpsMDIJ8p3aSY8}ky z*EtQK$)C}N4)fX1Gd%yB)?C5@cxyZ^wR^eL_JrVLczVdYio)vt$G!&U zVH5Q-`(qPe)Bf_)QoT@x2P?)OKq$G-GEpZ68X_&!AXbs;t|L$im09Pf96s)=1_qC6 zjS`uLV$@J7@#(A!Dgu&wFQo_u12^)&4SyW(YsS z2&Z3+%6&k^)ApB%KJjzLdkQ_<(QMVmqfcWc*bfEMxPw0FzDhP%WUy@pGNPjfr*c60 zY!b`%HWE%A;bVGGXG^+6k+g45?X>jQa1Tvbp0GZ!4>jkH^EGB<)M3;o{8rP8aQn-=}`Tvz-xF^T7#z zQGw5~uQKm5&GMlGbTOt+#AmClyAnF6BtrREF;PZR3FQ-fueEB#i{kC~d_G@+H{;`v zgkl=fK1LkH7mLSqiJOaQ&1Gc!pc6C`5$sAucb}hS#EpR-yz(2@a^Cm?D-Uglb|=+#Y|U0tJgBIvj4Q3)#Dq_gQq$~@CRrEe-MftT_V`i?!%=F?_1ND1nr1X| zkGuJ^&(nQZe9#Q&bS}wd%>igbz9PX8OYu)V=QKXs79Xuo|nyA?lmB_+-#^q~#y zeq=b_c~gCI%BWhK<@+yBc*7Yv2qZyKsA8$;Ib?vmnFJS2=uihR%*Z8;obp z6pu4TnD@L~U6Sq^BurzITL)i1v~!T6?KBkMh1rSS8@cHbrk?fxa82!P8+me1yxUX9 zcf`6)THmfEWS`Ydf1;ak|Do)P=U~RzF~lW=vSC@NJgK?MqtR;$O3~(3Qapj_+7w+; zh^7d#Fj{A<|E3*Iz10c+Zck&aY3Y2%1WuXWS|a%6G*j^Wyv*d(tPy^7eNB)`KRFv0 znn%1HT=Hh#*+F7`h`^R+V7gSS?|?M9Hk{H(P+4`dWv!N@P)6MMelVT_%hsl`O8AI8 z9KXk!=!(&F&vt$yP@7E(qnBXWUP%^ftcK1q7rmZsqm^${-=^bAbE zzq-sx{2+vRM10J;68O>CDU3$Ep zH!AogZbw7sbDi!*asA4;r&cdX(rfegmGC{Fm>q9o!PMIO~+g+Qk17oa)iPYgIN`VXa*5e^i@?!l77B z7s9F4!tc=|hqvS>^B~mYl)P_yWU$j>gvx@rhRWmDtC}%bORZR1%3~B%@r%cYWZ0P5 zK|XFFA!j1l-cA96xF{xT=$`vE(bx|%t?lfkL~qKBHhFXxYWU%fDqbh?ezm1X)BZ~= zV^rKGeZEOBcRb9a)a3#w{K$CKdy<`@OME>x8-dhuY*$0KB^G4TBp4^2o6jxZM?0=M zAj13@-}4wdgUgjuodA;|T#2R0I@^`y9S0#Z%_3nQZs}?CyD$eVjZ})N@Q>`wUa`Ms z`Ka3u`f&mcA3ufrM-RNSf0c#*EE-)Gt!@7c^m9Zk>XvuJ#+ql zUckN>20J`M*U7Qul+RGFB^3>%pa`a}@Pt`Dqe2G$`o6d=@!5&C(H+bYlzpw&^k&WH zWw&GCQJyUqnz}@z$?>-y0$EGFm`io5A@l7rDWXOO^84PUa4{=Myt!vqgs(hGcLFFw zeS(@N1Uj!@OWqaI;S(+Ml$-MRvUY*mteOOUWI|k$jvGezO=JyUwXB}@~ zTq1>3hx3A)zHrf8%GVUGAbIzf3Pqc4K*Mqaxuk~jW)0GcG|7b6XKwr~GM3iOtqJT5 zJt|r(1_@JY$G$H!Pz{F|o*O%;yjh2x(P+w_qBgajy``2~SuEZG&Uw>byjbVd>22&b zznVQU^C{6b92VfZaU+k^dzVe{(3qlij50MUs_2`5BwWP#WN$F;P=C2zbP==JIKi7r z=j*7F%GDF9xhyQN_V_K8WwT|v`b@vnO*!5i_1P%XGrW4{V@B>D_t#rnk(}lduAC*T zt=&D3h#iXT(XU({uL%|(d|~@&uG*B>8q2*Sxa8pAUPLQXIM5yOW_HF?AVKxyQw*_A zWZW6Eq78f?&%FguT(uU?Y5W*e)e}0t;J&nhk!s&b^wCJyQ6Wd7Y0Okv^7xf|xA?m* z&ygT?0tw$s(GT}+pG#~!I?J3Os1{r|?Njbu_}cLpM{ERB*>30y#R@Fw3ehWrYm_Y3X1}`Y~t~L=8%jlTS1t@{9DR)r6bOr*p8x^Y#;l#ab$1blQN@EW!D&JGPoE{6Xd&Fh4J^+}x){GO(vqq+(!e z?91^+^{uV>XNn4CmdVSBAI@i@yy)-wy;%RkUPCuM90O$P`Gm)pJP1kZerH;WxgX$w zo^ZVgSGTzeW+9l}+Z~om3fxwyYHzm8OQ#9=F60_ z8oW?tq+Dffj=y?=T*Q^KAqYgsQiY~tCzi3MzN=@=Q=h?n0=(cHI#Q0W9Y|gIv9Siv zHCmEp?8uj$4Y01DFK%yDwADR8-?=VSxuGg^whWbkP!`i#msA+K)U)2ZPh zYu}#?`cTK`-K)1~?XPp)TNfs+ogA!}1z$enF|uRLZK!VgUuEmTzC^Sl3Il<}@)mQCL1_ zdMk&k{Uo<*0kE3f0??36;+)6axRTyOqwgP3=X@StEpUyzqSgY$ee zdO)$rILXBbHKjx^f;A^gei6+TtddxZdzic`?Y`ain$hd06K5SsP8H4$X<0cT-!s`) zIui%HFz0fSF8cQABX_HVkNlCr1=8C$fQB+U#$>Mzn*t>Wlxze7{SP!{{2Mp>`-2`t z12Qv14AAgn{exEh!Maol4W6}rn{y9Sq`Ean$B+%Veu;3THAE@uLA9^yp$&$h-~IX< z7(;$g0SFP3jKzOrZO;+IO(<2TOq9^yfEzKlD)t?ecJepaR^`O)MPTdMtmko9E6n2_ z(I#A{^8L-kSBz9$$eDdM0d8+U*%^@B>L(Y%go~5sPEh$ao3!iOIbL)>zH4tGT zTmJ935g+97s}kg#{|y{IMEKCckd2H0>^Jy_{HO^qBlgv>s%js@!T)3IPm5pGpd^j* zZ{{6th=`D5L&j?Zrthxy+B4ARK?%W= zHW_p%i-%0S)CGI0*6ZQVJWl8i=0-QnhnIZeP(Xo(%o5M>M=>q-z2wnP(j4}Gsbq&X zaDm4#=!}!!HX+^@&1dzsQDi@wQ_5YHrq6fxny2m@EN->$ndBPVND8JMtPTt1^$NkY z`Z1v7V?9ckRvbC3X%<0%{=5+@W|Gur-IFmTE?g%62|y+y(|NDl_4W9U>$4A#d0t{u zct@x5T4;Uz6vkU&$Nl_>J;nDU+#?|CXL0@XKH1$VhmXSD7;MqC z9-}>SG>$-3GV~M7;ZJ4F%h@sNs9xA9cu=>qn0qGvS2*wT`>6coP7&`@2e&Kw%a%EL zN-xp-)I(2?ErOin6*?_lyva!fFAy!L`IDgsSTP-jy*w5JR?wxy5D;MIBPlSdz$S0g|V{n_E;{8-TF41$g zS?QoPQ|Ccpy_Nd>XXOmuH!IEJ+-14w21)lx)*qH{j1;TQPObEI6ep}?XpR)^(goqq zXDa28K$X{ry5o|?r`?VlY@};f*a|<eRm&Oy_74i&h1Fbj17`YT)t$CF~k)4X~$Zqa*3+2vShgnX^Q z|ILMDPfjB;K)Qh4!f%jNJK|6^NEH5=WFMNi`6G8XP_HlN9Aa4)N2@oD!89S9PpZ{v zdt`cxtdO=|>p=s$!^6A7&0bJi5jKgYEzS zGpEW)TNw4{kF}Ah#WkBvb?@@8%zJOA$~bx*LER$^aM!f!)4g@cSJDq>2(?@IQjP zv3$2jof4ozdN(EhZE-6wz8mXE3S~h4|CHWMY2-^~K=aOj-GDM60LHlu!1zD25s9w` zXmb4@p%`Cqo4?b$^`3pZctNChf&6}Q0;Q1gYJd{Toe^?Q4Pb+i$bb+B&Xjj~MsleG z(qL~H?eoR z&;clr^BRCMI9(q3M){VweYpY)F)vS!bkqbMg4G}2sZb&tH35BahvI)!EWw#deX?y60%1F!?$NOc{+4cxB#C$mo(5Hjg}$52F`-4XaccLWnsQy(vKTA^VAc?6{D0NOOZ*RsXC% zjOHv#p*ltLr>y!$w`Ki)^p8F!5@~dMn&#s_s&~DFZwx3y0x$sNA_KsUjJ>0b zp}_Bu?!>9k+iL&5A(AElE~Ja$x1#bNzm+DpezO_>t9{#;st_W{zc(|LyuB0grniSY jko>K4*O_{d#m9fkITQfO+j|B@#+m`RXt%9-dzb$o+kd`l delta 9036 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.4.8"` +`pip install "scrapling[all]>=0.4.9"` Then do this to download all the browsers' dependencies: diff --git a/agent-skill/Scrapling-Skill/examples/README.md b/agent-skill/Scrapling-Skill/examples/README.md index d0f9a2b..85de486 100644 --- a/agent-skill/Scrapling-Skill/examples/README.md +++ b/agent-skill/Scrapling-Skill/examples/README.md @@ -9,7 +9,7 @@ All examples collect **all 100 quotes across 10 pages**. Make sure Scrapling is installed: ```bash -pip install "scrapling[all]>=0.4.8" +pip install "scrapling[all]>=0.4.9" scrapling install --force ``` diff --git a/pyproject.toml b/pyproject.toml index 8cb4768..9c390c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "scrapling" # Static version instead of a dynamic version so we can get better layer caching while building docker, check the docker file to understand -version = "0.4.8" +version = "0.4.9" description = "Scrapling is an undetectable, powerful, flexible, high-performance Python library that makes Web Scraping easy and effortless as it should be!" readme = {file = "README.md", content-type = "text/markdown"} license = {file = "LICENSE"} @@ -61,7 +61,7 @@ classifiers = [ "Typing :: Typed", ] dependencies = [ - "lxml>=6.1.0", + "lxml>=6.1.1", "cssselect>=1.4.0", "orjson>=3.11.8", "tld>=0.13.2", @@ -73,8 +73,8 @@ dependencies = [ fetchers = [ "click>=8.3.0", "curl_cffi>=0.15.0", - "playwright==1.59.0", - "patchright==1.59.1", + "playwright==1.60.0", + "patchright==1.60.1", "browserforge>=1.2.4", "apify-fingerprint-datapoints>=0.13.0", "msgspec>=0.21.1", diff --git a/scrapling/__init__.py b/scrapling/__init__.py index 3420e25..a5ff5eb 100644 --- a/scrapling/__init__.py +++ b/scrapling/__init__.py @@ -1,5 +1,5 @@ __author__ = "Karim Shoair (karim.shoair@pm.me)" -__version__ = "0.4.8" +__version__ = "0.4.9" __copyright__ = "Copyright (c) 2024 Karim Shoair" from typing import Any, TYPE_CHECKING diff --git a/scrapling/engines/toolbelt/fingerprints.py b/scrapling/engines/toolbelt/fingerprints.py index f4fdfc1..0c02002 100644 --- a/scrapling/engines/toolbelt/fingerprints.py +++ b/scrapling/engines/toolbelt/fingerprints.py @@ -13,8 +13,8 @@ from scrapling.core._types import Dict, Literal, Tuple __OS_NAME__ = platform_system() OSName = Literal["linux", "macos", "windows"] # Current versions hardcoded for now (Playwright doesn't allow to know the version of a browser without launching it) -chromium_version = 147 -chrome_version = 147 +chromium_version = 148 +chrome_version = 148 @lru_cache(1, typed=True) diff --git a/server.json b/server.json index e4e813e..deccdda 100644 --- a/server.json +++ b/server.json @@ -14,12 +14,12 @@ "mimeType": "image/png" } ], - "version": "0.4.8", + "version": "0.4.9", "packages": [ { "registryType": "pypi", "identifier": "scrapling", - "version": "0.4.8", + "version": "0.4.9", "runtimeHint": "uvx", "packageArguments": [ { diff --git a/setup.cfg b/setup.cfg index 1c967c8..4e3d8b6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = scrapling -version = 0.4.8 +version = 0.4.9 author = Karim Shoair author_email = karim.shoair@pm.me description = Scrapling is an undetectable, powerful, flexible, high-performance Python library that makes Web Scraping easy and effortless as it should be! diff --git a/tests/requirements.txt b/tests/requirements.txt index 83aa3dc..7c9c3ff 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,6 +1,6 @@ pytest>=2.8.0,<9 pytest-cov -playwright==1.59.0 +playwright==1.60.0 werkzeug<3.0.0 pytest-httpbin==2.1.0 pytest-asyncio diff --git a/tox.ini b/tox.ini index e740705..799ceae 100644 --- a/tox.ini +++ b/tox.ini @@ -10,8 +10,8 @@ envlist = pre-commit,py{310,311,312,313} usedevelop = True changedir = tests deps = - playwright==1.59.0 - patchright==1.59.1 + playwright==1.60.0 + patchright==1.60.1 -r{toxinidir}/tests/requirements.txt extras = ai,shell commands = From b3408c6f3c70eb8672c4178a55918ec567e2dad1 Mon Sep 17 00:00:00 2001 From: Karim shoair Date: Sun, 7 Jun 2026 16:44:18 +0300 Subject: [PATCH 11/11] docs: warn that the default installation doesn't include fetchers The quickstart examples import from `scrapling.fetchers`, which raises `ModuleNotFoundError` on a bare install since those dependencies live in the fetchers extra. Make the consequence explicit in the installation section across the docs and all README translations. Closes #343 --- README.md | 3 ++- docs/README_AR.md | 3 ++- docs/README_CN.md | 3 ++- docs/README_DE.md | 3 ++- docs/README_ES.md | 3 ++- docs/README_FR.md | 3 ++- docs/README_JP.md | 3 ++- docs/README_KR.md | 3 ++- docs/README_PT_BR.md | 3 ++- docs/README_RU.md | 3 ++- docs/index.md | 4 +++- 11 files changed, 23 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 4507844..e888c46 100644 --- a/README.md +++ b/README.md @@ -471,7 +471,8 @@ Scrapling requires Python 3.10 or higher: pip install scrapling ``` -This installation only includes the parser engine and its dependencies, without any fetchers or commandline dependencies. +> [!IMPORTANT] +> This installation only includes the parser engine and its dependencies, without any fetchers or commandline dependencies. So importing anything from `scrapling.fetchers` or `scrapling.spiders`, like in the examples above, will raise `ModuleNotFoundError` with this installation alone. If you are going to use any of the fetchers or spiders, install the fetchers' dependencies first as shown below. ### Optional Dependencies diff --git a/docs/README_AR.md b/docs/README_AR.md index 2070bc9..fdc5990 100644 --- a/docs/README_AR.md +++ b/docs/README_AR.md @@ -467,7 +467,8 @@ Scrapling ليس قوياً فحسب - بل هو أيضاً سريع بشكل م pip install scrapling ``` -يتضمن هذا التثبيت فقط محرك المحلل وتبعياته، بدون أي جوالب أو تبعيات سطر الأوامر. +> [!IMPORTANT] +> يتضمن هذا التثبيت فقط محرك المحلل وتبعياته، بدون أي جوالب أو تبعيات سطر الأوامر. لذلك، فإن استيراد أي شيء من `scrapling.fetchers` أو `scrapling.spiders`، كما في الأمثلة أعلاه، سيؤدي إلى خطأ `ModuleNotFoundError` مع هذا التثبيت وحده. إذا كنت ستستخدم أيًا من الجوالب أو العناكب، فقم أولًا بتثبيت تبعيات الجوالب كما هو موضح أدناه. ### التبعيات الاختيارية diff --git a/docs/README_CN.md b/docs/README_CN.md index 0750bcc..02c7415 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -467,7 +467,8 @@ Scrapling 需要 Python 3.10 或更高版本: pip install scrapling ``` -此安装仅包括解析器引擎及其依赖项,没有任何 Fetcher 或命令行依赖项。 +> [!IMPORTANT] +> 此安装仅包括解析器引擎及其依赖项,没有任何 Fetcher 或命令行依赖项。 因此,仅使用此安装时,像上面的示例那样从 `scrapling.fetchers` 或 `scrapling.spiders` 导入任何内容都会引发 `ModuleNotFoundError`。如果要使用任何 Fetcher 或 Spider,请先按照下面的说明安装 Fetcher 的依赖项。 ### 可选依赖项 diff --git a/docs/README_DE.md b/docs/README_DE.md index ac6e533..e4e320b 100644 --- a/docs/README_DE.md +++ b/docs/README_DE.md @@ -467,7 +467,8 @@ Scrapling erfordert Python 3.10 oder höher: pip install scrapling ``` -Diese Installation enthält nur die Parser-Engine und ihre Abhängigkeiten, ohne Fetcher oder Kommandozeilenabhängigkeiten. +> [!IMPORTANT] +> Diese Installation enthält nur die Parser-Engine und ihre Abhängigkeiten, ohne Fetcher oder Kommandozeilenabhängigkeiten. Daher führt der Import von allem aus `scrapling.fetchers` oder `scrapling.spiders`, wie in den Beispielen oben, mit dieser Installation allein zu einem `ModuleNotFoundError`. Wenn Sie einen der Fetcher oder Spider verwenden möchten, installieren Sie zuerst die Fetcher-Abhängigkeiten wie unten gezeigt. ### Optionale Abhängigkeiten diff --git a/docs/README_ES.md b/docs/README_ES.md index 1af1f9a..2105dd8 100644 --- a/docs/README_ES.md +++ b/docs/README_ES.md @@ -467,7 +467,8 @@ Scrapling requiere Python 3.10 o superior: pip install scrapling ``` -Esta instalación solo incluye el motor de análisis y sus dependencias, sin ningún fetcher ni dependencias de línea de comandos. +> [!IMPORTANT] +> Esta instalación solo incluye el motor de análisis y sus dependencias, sin ningún fetcher ni dependencias de línea de comandos. Por lo tanto, importar cualquier cosa desde `scrapling.fetchers` o `scrapling.spiders`, como en los ejemplos anteriores, lanzará un `ModuleNotFoundError` solo con esta instalación. Si va a usar alguno de los fetchers o spiders, instale primero las dependencias de los fetchers como se muestra a continuación. ### Dependencias Opcionales diff --git a/docs/README_FR.md b/docs/README_FR.md index c6d76ed..55a122c 100644 --- a/docs/README_FR.md +++ b/docs/README_FR.md @@ -467,7 +467,8 @@ Scrapling nécessite Python 3.10 ou supérieur : pip install scrapling ``` -Cette installation n'inclut que le moteur de parsing et ses dépendances, sans aucun fetcher ni dépendance en ligne de commande. +> [!IMPORTANT] +> Cette installation n'inclut que le moteur de parsing et ses dépendances, sans aucun fetcher ni dépendance en ligne de commande. Importer quoi que ce soit depuis `scrapling.fetchers` ou `scrapling.spiders`, comme dans les exemples ci-dessus, lèvera donc une `ModuleNotFoundError` avec cette seule installation. Si vous comptez utiliser l'un des fetchers ou spiders, installez d'abord les dépendances des fetchers comme indiqué ci-dessous. ### Dépendances optionnelles diff --git a/docs/README_JP.md b/docs/README_JP.md index dcf44fc..e57a0b5 100644 --- a/docs/README_JP.md +++ b/docs/README_JP.md @@ -467,7 +467,8 @@ Scrapling には Python 3.10 以上が必要です: pip install scrapling ``` -このインストールにはパーサーエンジンとその依存関係のみが含まれており、Fetcher やコマンドライン依存関係は含まれていません。 +> [!IMPORTANT] +> このインストールにはパーサーエンジンとその依存関係のみが含まれており、Fetcher やコマンドライン依存関係は含まれていません。 そのため、このインストールのみでは、上記の例のように `scrapling.fetchers` や `scrapling.spiders` から何かをインポートすると `ModuleNotFoundError` が発生します。Fetcher や Spider を使用する場合は、以下のように、まず Fetcher の依存関係をインストールしてください。 ### オプションの依存関係 diff --git a/docs/README_KR.md b/docs/README_KR.md index 18d2e05..f5c913b 100644 --- a/docs/README_KR.md +++ b/docs/README_KR.md @@ -467,7 +467,8 @@ Scrapling은 Python 3.10 이상이 필요합니다: pip install scrapling ``` -이 설치에는 파서 엔진과 의존성만 포함되며, Fetcher나 커맨드라인 의존성은 포함되지 않습니다. +> [!IMPORTANT] +> 이 설치에는 파서 엔진과 의존성만 포함되며, Fetcher나 커맨드라인 의존성은 포함되지 않습니다. 따라서 이 설치만으로는 위 예제처럼 `scrapling.fetchers`나 `scrapling.spiders`에서 무언가를 임포트하면 `ModuleNotFoundError`가 발생합니다. Fetcher나 Spider를 사용하려면 아래와 같이 먼저 Fetcher 의존성을 설치하세요. ### 선택적 의존성 diff --git a/docs/README_PT_BR.md b/docs/README_PT_BR.md index 4c7f617..3884614 100644 --- a/docs/README_PT_BR.md +++ b/docs/README_PT_BR.md @@ -469,7 +469,8 @@ O Scrapling requer Python 3.10 ou superior: pip install scrapling ``` -Esta instalação inclui apenas o motor de parsing e suas dependências, sem fetchers nem dependências de linha de comando. +> [!IMPORTANT] +> Esta instalação inclui apenas o motor de parsing e suas dependências, sem fetchers nem dependências de linha de comando. Portanto, importar qualquer coisa de `scrapling.fetchers` ou `scrapling.spiders`, como nos exemplos acima, lançará um `ModuleNotFoundError` apenas com esta instalação. Se você for usar algum dos fetchers ou spiders, instale primeiro as dependências dos fetchers como mostrado abaixo. ### Dependências Opcionais diff --git a/docs/README_RU.md b/docs/README_RU.md index 4dde2b9..ba5bb31 100644 --- a/docs/README_RU.md +++ b/docs/README_RU.md @@ -470,7 +470,8 @@ Scrapling требует Python 3.10 или выше: pip install scrapling ``` -Эта установка включает только движок парсера и его зависимости, без каких-либо Fetcher'ов или зависимостей командной строки. +> [!IMPORTANT] +> Эта установка включает только движок парсера и его зависимости, без каких-либо Fetcher'ов или зависимостей командной строки. Поэтому импорт чего-либо из `scrapling.fetchers` или `scrapling.spiders`, как в примерах выше, вызовет `ModuleNotFoundError` при такой установке. Если вы собираетесь использовать какие-либо Fetcher'ы или Spider'ы, сначала установите зависимости Fetcher'ов, как показано ниже. ### Опциональные зависимости diff --git a/docs/index.md b/docs/index.md index fdae518..b5a8ba4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -180,7 +180,9 @@ Scrapling requires Python 3.10 or higher: pip install scrapling ``` -This installation only includes the parser engine and its dependencies, without any fetchers or commandline dependencies. +!!! warning + + This installation only includes the parser engine and its dependencies, without any fetchers or commandline dependencies. So importing anything from `scrapling.fetchers` or `scrapling.spiders`, like in the examples above, will raise `ModuleNotFoundError` with this installation alone. If you are going to use any of the fetchers or spiders, install the fetchers' dependencies first as shown below. ### Optional Dependencies