Commit Graph

147 Commits

Author SHA1 Message Date
Karim shoair 8a8b2d14ba test: add tests accordingly 2026-05-11 02:33:43 +03:00
yetval 334b0f5538 fix: str(value) 2026-04-30 09:52:34 -04:00
yetval a5a5652996 test: add request fingerprint regressions 2026-04-26 16:31:58 -04:00
Karim shoair b626b4d585 test: adding new tests for the configure function 2026-04-22 15:35:15 +02:00
Karim shoair 78e388f75c feat: add new mcp tool to screenshot pages
Implements #244
2026-04-17 22:07:44 +02:00
Karim shoair 65421f0a16 Merge branch 'dev' into fix/block-ads-unexpected-kwarg 2026-04-17 20:45:00 +02:00
voidborne-d 76ba28efaa fix(static): exclude block_ads from HTTP request args
block_ads is a browser-engine parameter (used by PlayWright/Camoufox
fetchers for ad-domain blocking) and is not recognised by curl_cffi's
Session.request(). When the CLI's --ai-targeted flag sets block_ads=True,
_merge_request_args forwards it unfiltered, causing:

  TypeError: Session.request() got an unexpected keyword argument 'block_ads'

Add block_ads to the skip_keys set so it is stripped before the dict
reaches Session.request(), consistent with existing entries for
extra_headers and google_search.

Fixes #247
2026-04-16 13:10:59 +00:00
Jules Omlor 00897dad2f feat(mcp): add optional session_id parameter to open_session
Allow users to specify a custom session_id when opening a browser session,
rather than always generating a random UUID. Useful for naming sessions
for easier management across multiple tool calls.

- Add session_id: Optional[str] = None parameter
- Validate session_id doesn't already exist before starting browser
- Fall back to uuid4().hex[:12] if not provided
- Add tests for custom session_id and duplicate detection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-14 01:17:52 -04:00
Karim shoair 3c23436448 Merge branch 'dev' into fix/full-path-selector-duplicate-id 2026-04-13 12:03:15 +02:00
Karim shoair d952db8ef8 feat(browsers): add a new feature to block ads
This is working by aborting all requests to known ads domains.
2026-04-12 17:59:00 +02:00
sjhddh 273c8c2fa0 fix: emit valid XPath node test for ID elements in full-path mode
Address review feedback: In full-path XPath generation, elements with
IDs were producing bare predicates like `[@id='x']` which creates
invalid XPath steps like `//body/[@id='main']`. Now emits `*[@id='x']`
for full-path mode (e.g. `//body/*[@id='main']/*[@id='target']`).

Short-path XPath mode unchanged — still uses `//*[@id='x']` prefix.

Also added XPath evaluation assertion to the regression test to verify
the generated selector actually selects the correct element.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 09:46:55 +02:00
sjhddh 1ac26733d8 fix: prevent duplicate ID segments in full-path selector generation
When generating full-path CSS/XPath selectors, elements with id
attributes had their selector appended twice — once in the id branch
(line 30) and again unconditionally (line 50).

This produced selectors like 'body > #main > #main > #target > #target'
instead of the correct 'body > #main > #target'.

Move the append into the else branch so it only fires for elements
without an id (elements with id already append in the if branch).

Includes 2 regression tests.
2026-04-12 01:25:45 +02:00
Karim shoair d1baf1fc46 feat(spiders): add a development mode 2026-04-07 04:08:54 +02:00
d 🔹 9950dde724 test: align force-stop regression stubs with dev branch 2026-04-05 19:13:19 +00:00
voidborne-d eaa0e8cae6 fix: save checkpoint before cancel_scope.cancel() on force-stop to prevent data loss
On force-stop (second Ctrl+C), cancel_scope.cancel() was called BEFORE
_save_checkpoint(). Since cancel_scope.cancel() causes all subsequent
awaits within the scope to raise Cancelled, the checkpoint write was
silently aborted:

1. _save_checkpoint() uses anyio.open_file + rename — both are await
   checkpoints that get cancelled immediately
2. self.paused never gets set to True (code after the aborted save)
3. The finally block sees 'not self.paused' and calls cleanup() which
   DELETES the previous checkpoint file

Result: a user who ran a long crawl, pressed Ctrl+C twice to force-stop,
loses their entire checkpoint irrecoverably. The old checkpoint (from
periodic saves or a previous graceful pause) is deleted, and the new
one was never written.

Fix: move the cancel_scope.cancel() call AFTER the checkpoint save.
The save completes normally, self.paused is set to True, and only then
does the scope get cancelled to abort in-flight tasks. The finally
block correctly sees paused=True and skips cleanup.

Adds 6 regression tests covering:
- Force-stop checkpoint preservation (core regression)
- Graceful pause still works
- Force-stop checkpoint is loadable
- Normal completion cleanup still works
- Force-stop without checkpoint system
- Existing checkpoint not deleted on force-stop
2026-04-05 19:11:58 +00:00
Karim shoair afaf68e7d5 fix(spider robots): removing dead code 2026-04-05 02:32:33 +02:00
Abdullah a86e9709ea feat(spiders): pre-warm robots.txt cache before crawl loop starts
Previously robots.txt was fetched lazily on the first request per
domain, causing early concurrent requests to each stall waiting for
the same network fetch. The cache is now warmed before the crawl loop
starts, making all subsequent robots.txt lookups a local read.

- RobotsTxtManager gains a prefetch(urls, sid) method that fetches all domains concurrently via a task group
- CrawlerEngine._prefetch_robots_txt() is called after on_start():
  uses allowed_domains if configured, otherwise falls back to unique
  domains extracted from start_urls
- Mid-crawl domain discovery (not covered by prefetch) still fetches
  lazily; two concurrent callbacks on the same new domain can each
  trigger a fetch — accepted tradeoff, documented in _get_domain_delay

Files: scrapling/spiders/robotstxt.py, scrapling/spiders/engine.py, tests/spiders/test_engine.py
2026-04-04 03:00:15 +02:00
Abdullah e2b293f41c refactor(spiders): simplify robots.txt cache to domain-only key
robots.txt is a domain-level document and does not vary by session.

Keying the cache by (domain, sid) was both wasteful and incorrect —
it caused redundant fetches when the same domain was accessed by different sessions.

- Cache is now keyed by domain string only; all sessions share one entry
- Removed asyncio.Event inflight-deduplication mechanism (superseded by the prefetch approach added in the next commit)
- clear_cache() loses the `sid` parameter (breaking change); clearing a domain now evicts the single shared entry for all sessions
- Updated tests to reflect shared-cache semantics

Files: scrapling/spiders/robotstxt.py, tests/spiders/test_robotstxt.py
2026-04-04 03:00:15 +02:00
Abdullah 132f33c846 test(spiders): add comprehensive test suite for robots.txt compliance 2026-04-03 15:08:34 +02:00
yetval 6c6aabeb73 fix: proxy rotation page pool leak 2026-04-02 11:57:17 -04:00
Karim shoair a403156b2e docs: style adjustment 2026-03-30 03:53:56 +02:00
Karim shoair c458ab65a2 feat(mcp): Add three new tools to control browser sessions
Now you can open a browser, keep using it for other requests as you want, and close it when you want.
2026-03-28 00:16:14 +02:00
Karim shoair a356dd2f2b refactor(mcp)!: Cleaning and unifying functions to async
- `get()` now delegates to `bulk_get([url])[0]` (was a separate sync implementation)
- `fetch()` now delegates to `bulk_fetch([url])[0]` (eliminated duplicate fetcher call)
- `stealthy_fetch()` now delegates to `bulk_stealthy_fetch([url])[0]` (same)
- Replaced 6x repeated `_content_translator(Convertor._extract_content(...), page)` with a single `_translate_response()` helper
- Removed unused imports (`Fetcher`, `DynamicFetcher`, `StealthyFetcher`, `Generator`)
2026-03-27 19:30:58 +02:00
Karim shoair 9fee49c928 test: fixes to storage tests 2026-03-25 00:13:42 +02:00
Karim shoair df4b1ac7e3 Merge branch 'dev' into test/storage-core-coverage 2026-03-22 18:05:42 +02:00
Karim shoair bf9e2da329 Merge branch 'dev' into test/edge-cases-filter-ancestors-find-similar 2026-03-17 22:15:59 +02:00
Karim shoair 4c07b294ae Merge branch 'dev' into fix/preserve-http-method-on-retry 2026-03-17 22:04:57 +02:00
Karim shoair 136c389787 fix(Texthandler): Replace get_all with getall to match the Selector class 2026-03-17 21:53:17 +02:00
Karim shoair 5123590a15 Merge branch 'dev' into test/custom-types-coverage 2026-03-17 21:42:17 +02:00
Karim shoair cc6c0dbfb9 fix: adjust test to the _restore_from_checkpoint fix 2026-03-17 17:46:47 +02:00
Karim shoair 6699213bdc Merge branch 'dev' into test/storage-core-coverage 2026-03-17 17:31:31 +02:00
karesansui 5bf921b308 fix: preserve HTTP method across retries in spider session
SessionManager.fetch() pops `method` from `_session_kwargs`,
which mutates the original request dict. When the engine retries
a blocked request via request.copy(), the copy no longer has
`method`, so it defaults to GET.

Steps to reproduce:
1. Yield Request(url, method="POST", data=...)
2. Target returns a response that triggers is_blocked()
3. Engine retries via request.copy() → second fetch uses GET

Fix: copy the kwargs dict before popping, so the original
request stays intact.
2026-03-17 00:53:52 +09:00
awanawona cec5acd68a test: add edge case tests for filter, iterancestors, and find_similar
- test_selectors_filter.py: covers chained filter(), empty result,
  all-pass predicate, and calling filter() on empty Selectors
- test_ancestor_navigation.py: covers iterancestors() order, depth,
  text-node safety, root-element edge case, and find_ancestor() with
  no match
- test_find_similar_advanced.py: covers similarity_threshold levels,
  match_text=True behavior, ignore_attributes combinations, and
  text-node safety
2026-03-16 18:01:11 +08:00
Karim shoair 1e7704864e Merge branch 'dev' into test/custom-types-coverage 2026-03-15 16:38:48 +02:00
Karim shoair 656dbc7d12 Merge branch 'dev' into test/normalize-credentials-coverage 2026-03-15 16:24:59 +02:00
haosenwang1018 7178279586 test: add coverage for TextHandler regex, clean, and TextHandlers.re()
Several critical code paths in custom_types.py lacked test coverage:

- TextHandler.re(check_match=True): returns bool, not TextHandlers
- TextHandler.re(replace_entities=False): entity preservation path
- TextHandler.re() with capture groups: flatten behavior
- TextHandler.re_first() default value when no match
- TextHandler.clean(remove_entities=True): entity replacement path
- TextHandler.json() valid and invalid input
- TextHandlers.re(): list-level regex with result flattening
- TextHandlers.extract()/get_all(): identity return

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 17:27:55 +08:00
haosenwang1018 297423410d test: add save/retrieve round-trip and hash/URL coverage for storage core
The adaptive scraping feature relies on SQLiteStorageSystem to persist
and relocate elements, but the existing tests only verified object
creation — not the actual save/retrieve workflow. This adds:

- _get_base_url: None, empty, valid URL, and case-normalization paths
- _get_hash: determinism, uniqueness, strip/lowercase, length suffix
- save/retrieve round-trip: basic, overwrite (upsert), nonexistent key
- URL-based isolation between different websites
- element_to_dict: with/without text, attributes, whitespace filtering
- _get_element_path: nested and root element paths
- Thread safety: 20 concurrent saves with result verification

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 17:27:10 +08:00
Andrew Barnes 54fcb7c364 test(ai): add _normalize_credentials edge case coverage 2026-03-15 00:14:54 -04:00
Karim shoair d48ac78c79 test: update tests accordingly 2026-03-09 00:28:32 +02:00
Karim shoair b547e04207 ops: update playwright pinned version 2026-03-09 00:05:46 +02:00
Karim shoair b0c8448f81 Merge branch 'dev' into fix/google-referrer-spoof 2026-03-08 19:01:20 +02:00
Andrew Barnes 68b9e128bc fix: return bare Google URL in referer instead of search query
Real browsers send `https://www.google.com/` as the Referer header
when clicking search results, not the full search URL with query
parameters. The previous format was a fingerprinting signal that
the referer was spoofed.

Closes #172
2026-03-08 11:32:04 -04:00
Matt Hillebrand 7cbe566acd Move test for get_all_text to test_parser_advanced.py 2026-03-08 08:20:30 -07:00
Matt Hillebrand 00ad6b4c5f Add two ignored elements to unit test 2026-03-07 17:11:39 -08:00
Matt Hillebrand 5457a697fa fix: Selector.get_all_text() doesn't get all text #167
- Updated text extraction using recursion
- Added new unit test
2026-03-07 17:11:39 -08:00
Karim shoair 50a69c3583 ops: update workflows triggers
and pinning playwright version here too
2026-02-28 16:45:26 +02:00
Karim shoair e34651d887 fix(stealth): improve stealth mode by removing unnecessary scripts
It doesn't add anything to the table as before (it might turn out I was wrong and add them again lol)
2026-02-27 03:50:14 +02:00
Karim shoair e35ba3455c test: add tests for spiders engine
Automated tests written by AI
2026-02-15 04:35:28 +02:00
Karim shoair 0ab54852c4 fix: use more accurate naming for proxy rotation strategy
It can be considered the simplest form of round robin since we don't have weights or anything (as was originally planned), but let's change it to avoid confusion or useless debates. Here goes nothing
2026-02-14 23:19:54 +02:00
Karim shoair 2997d562d5 test: remove irrelevent test 2026-02-14 03:34:41 +02:00