deep text
+diff --git a/README.md b/README.md
index dfb7d7f..ea56b15 100644
--- a/README.md
+++ b/README.md
@@ -185,7 +185,6 @@ MySpider().start()

-
diff --git a/agent-skill/Scrapling-Skill.zip b/agent-skill/Scrapling-Skill.zip
index 0bbd64c..fb76762 100644
Binary files a/agent-skill/Scrapling-Skill.zip and b/agent-skill/Scrapling-Skill.zip differ
diff --git a/agent-skill/Scrapling-Skill/references/fetching/dynamic.md b/agent-skill/Scrapling-Skill/references/fetching/dynamic.md
index a87fdf6..1a4c96d 100644
--- a/agent-skill/Scrapling-Skill/references/fetching/dynamic.md
+++ b/agent-skill/Scrapling-Skill/references/fetching/dynamic.md
@@ -44,7 +44,7 @@ Instead of launching a browser locally (Chromium/Google Chrome), you can connect
**Notes:**
* There was a `stealth` option here, but it was moved to the `StealthyFetcher` class, as explained on the next page, with additional features since version 0.3.13.
-* This makes it less confusing for new users, easier to maintain, and provides other benefits, as explained on the [StealthyFetcher page](fetching/stealthy.md).
+* This makes it less confusing for new users, easier to maintain, and provides other benefits, as explained on the [StealthyFetcher page](stealthy.md).
## Full list of arguments
All arguments for `DynamicFetcher` and its session classes:
diff --git a/agent-skill/Scrapling-Skill/references/spiders/architecture.md b/agent-skill/Scrapling-Skill/references/spiders/architecture.md
index 9e7586f..de95d24 100644
--- a/agent-skill/Scrapling-Skill/references/spiders/architecture.md
+++ b/agent-skill/Scrapling-Skill/references/spiders/architecture.md
@@ -11,8 +11,8 @@ Here's what happens step by step when you run a spider:
1. The **Spider** produces the first batch of `Request` objects. By default, it creates one request for each URL in `start_urls`, but you can override `start_requests()` for custom logic.
2. The **Scheduler** receives requests and places them in a priority queue, and creates fingerprints for them. Higher-priority requests are dequeued first.
3. The **Crawler Engine** asks the **Scheduler** to dequeue the next request, respecting concurrency limits (global and per-domain) and download delays. Once the **Crawler Engine** receives the request, it passes it to the **Session Manager**, which routes it to the correct session based on the request's `sid` (session ID).
-4. The **session** fetches the page and returns a [Response](fetching/choosing.md#response-object) object to the **Crawler Engine**. The engine records statistics and checks for blocked responses. If the response is blocked, the engine retries the request up to `max_blocked_retries` times. Of course, the blocking detection and the retry logic for blocked requests can be customized.
-5. The **Crawler Engine** passes the [Response](fetching/choosing.md#response-object) to the request's callback. The callback either yields a dictionary, which gets treated as a scraped item, or a follow-up request, which gets sent to the scheduler for queuing.
+4. The **session** fetches the page and returns a [Response](../fetching/choosing.md#response-object) object to the **Crawler Engine**. The engine records statistics and checks for blocked responses. If the response is blocked, the engine retries the request up to `max_blocked_retries` times. Of course, the blocking detection and the retry logic for blocked requests can be customized.
+5. The **Crawler Engine** passes the [Response](../fetching/choosing.md#response-object) to the request's callback. The callback either yields a dictionary, which gets treated as a scraped item, or a follow-up request, which gets sent to the scheduler for queuing.
6. The cycle repeats from step 2 until the scheduler is empty and no tasks are active, or the spider is paused.
7. If `crawldir` is set while starting the spider, the **Crawler Engine** periodically saves a checkpoint (pending requests + seen URLs set) to disk. On graceful shutdown (Ctrl+C), a final checkpoint is saved. The next time the spider runs with the same `crawldir`, it resumes from where it left off — skipping `start_requests()` and restoring the scheduler state.
@@ -50,9 +50,9 @@ A priority queue with built-in URL deduplication. Requests are fingerprinted bas
Manages one or more named session instances. Each session is one of:
-- [FetcherSession](fetching/static.md)
-- [AsyncDynamicSession](fetching/dynamic.md)
-- [AsyncStealthySession](fetching/stealthy.md)
+- [FetcherSession](../fetching/static.md)
+- [AsyncDynamicSession](../fetching/dynamic.md)
+- [AsyncStealthySession](../fetching/stealthy.md)
When a request comes in, the Session Manager routes it to the correct session based on the request's `sid` field. Sessions can be started with the spider start (default) or lazily (started on the first use).
diff --git a/agent-skill/Scrapling-Skill/references/spiders/getting-started.md b/agent-skill/Scrapling-Skill/references/spiders/getting-started.md
index 1446779..2a9e6c8 100644
--- a/agent-skill/Scrapling-Skill/references/spiders/getting-started.md
+++ b/agent-skill/Scrapling-Skill/references/spiders/getting-started.md
@@ -25,7 +25,7 @@ Every spider needs three things:
2. **`start_urls`** — A list of URLs to start crawling from.
3. **`parse()`** — An async generator method that processes each response and yields results.
-The `parse()` method processes each response. You use the same selection methods you'd use with Scrapling's [Selector](parsing/main_classes.md#selector)/[Response](fetching/choosing.md#response-object), and `yield` dictionaries to output scraped items.
+The `parse()` method processes each response. You use the same selection methods you'd use with Scrapling's [Selector](../parsing/main_classes.md#selector)/[Response](../fetching/choosing.md#response-object), and `yield` dictionaries to output scraped items.
## Running the Spider
diff --git a/agent-skill/Scrapling-Skill/references/spiders/sessions.md b/agent-skill/Scrapling-Skill/references/spiders/sessions.md
index 82a21ac..761cea6 100644
--- a/agent-skill/Scrapling-Skill/references/spiders/sessions.md
+++ b/agent-skill/Scrapling-Skill/references/spiders/sessions.md
@@ -6,14 +6,14 @@ A spider can use multiple fetcher sessions simultaneously — for example, a fas
A session is a pre-configured fetcher instance that stays alive for the duration of the crawl. Instead of creating a new connection or browser for every request, the spider reuses sessions, which is faster and more resource-efficient.
-By default, every spider creates a single [FetcherSession](fetching/static.md). You can add more sessions or swap the default by overriding the `configure_sessions()` method, but you have to use the async version of each session only, as the table shows below:
+By default, every spider creates a single [FetcherSession](../fetching/static.md). You can add more sessions or swap the default by overriding the `configure_sessions()` method, but you have to use the async version of each session only, as the table shows below:
| Session Type | Use Case |
|-------------------------------------------------|------------------------------------------|
-| [FetcherSession](fetching/static.md) | Fast HTTP requests, no JavaScript |
-| [AsyncDynamicSession](fetching/dynamic.md) | Browser automation, JavaScript rendering |
-| [AsyncStealthySession](fetching/stealthy.md) | Anti-bot bypass, Cloudflare, etc. |
+| [FetcherSession](../fetching/static.md) | Fast HTTP requests, no JavaScript |
+| [AsyncDynamicSession](../fetching/dynamic.md) | Browser automation, JavaScript rendering |
+| [AsyncStealthySession](../fetching/stealthy.md) | Anti-bot bypass, Cloudflare, etc. |
## Configuring Sessions
diff --git a/docs/README_AR.md b/docs/README_AR.md
index 7573a10..68c6a51 100644
--- a/docs/README_AR.md
+++ b/docs/README_AR.md
@@ -180,7 +180,6 @@ MySpider().start()

-
diff --git a/docs/README_CN.md b/docs/README_CN.md
index be91908..469b703 100644
--- a/docs/README_CN.md
+++ b/docs/README_CN.md
@@ -180,7 +180,6 @@ MySpider().start()

-
diff --git a/docs/README_DE.md b/docs/README_DE.md
index 88ebd0a..d5c30b9 100644
--- a/docs/README_DE.md
+++ b/docs/README_DE.md
@@ -180,7 +180,6 @@ MySpider().start()

-
diff --git a/docs/README_ES.md b/docs/README_ES.md
index 959bc05..d32cb69 100644
--- a/docs/README_ES.md
+++ b/docs/README_ES.md
@@ -180,7 +180,6 @@ MySpider().start()

-
diff --git a/docs/README_FR.md b/docs/README_FR.md
index 9d4212a..09180d6 100644
--- a/docs/README_FR.md
+++ b/docs/README_FR.md
@@ -180,7 +180,6 @@ MySpider().start()

-
diff --git a/docs/README_JP.md b/docs/README_JP.md
index dde6f5b..4b6a0c0 100644
--- a/docs/README_JP.md
+++ b/docs/README_JP.md
@@ -180,7 +180,6 @@ MySpider().start()

-
diff --git a/docs/README_KR.md b/docs/README_KR.md
index dad94ae..5b29439 100644
--- a/docs/README_KR.md
+++ b/docs/README_KR.md
@@ -180,7 +180,6 @@ MySpider().start()

-
diff --git a/docs/README_RU.md b/docs/README_RU.md
index 99b347a..6a97f17 100644
--- a/docs/README_RU.md
+++ b/docs/README_RU.md
@@ -183,7 +183,6 @@ MySpider().start()

-
diff --git a/images/rapidproxy.jpg b/images/rapidproxy.jpg
deleted file mode 100644
index dee0b35..0000000
Binary files a/images/rapidproxy.jpg and /dev/null differ
diff --git a/scrapling/core/custom_types.py b/scrapling/core/custom_types.py
index d06ac29..53255be 100644
--- a/scrapling/core/custom_types.py
+++ b/scrapling/core/custom_types.py
@@ -112,10 +112,10 @@ class TextHandler(str):
def get(self, default=None): # pragma: no cover
return self
- def get_all(self): # pragma: no cover
+ def getall(self): # pragma: no cover
return self
- extract = get_all
+ extract = getall
extract_first = get
def json(self) -> Dict:
@@ -279,7 +279,7 @@ class TextHandlers(List[TextHandler]):
return self
extract_first = get
- get_all = extract
+ getall = extract
class AttributesHandler(Mapping[str, _TextHandlerType]):
diff --git a/scrapling/engines/toolbelt/convertor.py b/scrapling/engines/toolbelt/convertor.py
index 1a8d799..8606003 100644
--- a/scrapling/engines/toolbelt/convertor.py
+++ b/scrapling/engines/toolbelt/convertor.py
@@ -187,34 +187,34 @@ class ResponseFactory:
return history
@classmethod
- def _get_page_content(cls, page: SyncPage) -> str:
+ def _get_page_content(cls, page: SyncPage, max_retries: int = 20) -> str:
"""
A workaround for the Playwright issue with `page.content()` on Windows. Ref.: https://github.com/microsoft/playwright/issues/16108
:param page: The page to extract content from.
+ :param max_retries: Maximum number of retry attempts before raising `RuntimeError`.
:return:
"""
- while True:
+ for _ in range(max_retries):
try:
return page.content() or ""
except PlaywrightError:
page.wait_for_timeout(500)
- continue
- return "" # pyright: ignore
+ raise RuntimeError(f"Failed to retrieve the page content after retrying for {max_retries * 500}ms.")
@classmethod
- async def _get_async_page_content(cls, page: AsyncPage) -> str:
+ async def _get_async_page_content(cls, page: AsyncPage, max_retries: int = 20) -> str:
"""
A workaround for the Playwright issue with `page.content()` on Windows. Ref.: https://github.com/microsoft/playwright/issues/16108
:param page: The page to extract content from.
+ :param max_retries: Maximum number of retry attempts before raising `RuntimeError`.
:return:
"""
- while True:
+ for _ in range(max_retries):
try:
return (await page.content()) or ""
except PlaywrightError:
await page.wait_for_timeout(500)
- continue
- return "" # pyright: ignore
+ raise RuntimeError(f"Failed to retrieve the page content after retrying for {max_retries * 500}ms.")
@classmethod
async def from_async_playwright_response(
diff --git a/scrapling/spiders/engine.py b/scrapling/spiders/engine.py
index 416911d..d77f838 100644
--- a/scrapling/spiders/engine.py
+++ b/scrapling/spiders/engine.py
@@ -205,7 +205,7 @@ class CrawlerEngine:
Returns True if successfully restored, False otherwise.
"""
if not self._checkpoint_system_enabled:
- raise
+ return False
data = await self._checkpoint_manager.load()
if data is None:
diff --git a/scrapling/spiders/session.py b/scrapling/spiders/session.py
index cc07042..536be6d 100644
--- a/scrapling/spiders/session.py
+++ b/scrapling/spiders/session.py
@@ -112,10 +112,12 @@ class SessionManager:
client = session._client
if isinstance(client, _ASyncSessionLogic):
+ kwargs = request._session_kwargs.copy()
+ method = cast(SUPPORTED_HTTP_METHODS, kwargs.pop("method", "GET"))
response = await client._make_request(
- method=cast(SUPPORTED_HTTP_METHODS, request._session_kwargs.pop("method", "GET")),
+ method=method,
url=request.url,
- **request._session_kwargs,
+ **kwargs,
)
else:
# Sync session or other types - shouldn't happen in async context
diff --git a/tests/parser/test_ancestor_navigation.py b/tests/parser/test_ancestor_navigation.py
new file mode 100644
index 0000000..1814ec5
--- /dev/null
+++ b/tests/parser/test_ancestor_navigation.py
@@ -0,0 +1,66 @@
+"""
+Tests for Selector.iterancestors() and Selector.find_ancestor() methods.
+Target file: tests/parser/test_general.py (append to TestElementNavigation class)
+"""
+import pytest
+from scrapling import Selector
+
+
+@pytest.fixture
+def nested_page():
+ html = """
+
deep text
+