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`)
This commit is contained in:
Karim shoair
2026-03-27 19:30:58 +02:00
parent 8e147db7f8
commit a356dd2f2b
2 changed files with 75 additions and 116 deletions
+72 -114
View File
@@ -7,11 +7,8 @@ from scrapling.core.shell import Convertor
from scrapling.engines.toolbelt.custom import Response as _ScraplingResponse from scrapling.engines.toolbelt.custom import Response as _ScraplingResponse
from scrapling.engines.static import ImpersonateType from scrapling.engines.static import ImpersonateType
from scrapling.fetchers import ( from scrapling.fetchers import (
Fetcher,
FetcherSession, FetcherSession,
DynamicFetcher,
AsyncDynamicSession, AsyncDynamicSession,
StealthyFetcher,
AsyncStealthySession, AsyncStealthySession,
) )
from scrapling.core._types import ( from scrapling.core._types import (
@@ -21,7 +18,6 @@ from scrapling.core._types import (
Dict, Dict,
List, List,
Any, Any,
Generator,
Sequence, Sequence,
SetCookieParam, SetCookieParam,
extraction_types, extraction_types,
@@ -37,9 +33,22 @@ class ResponseModel(BaseModel):
url: str = Field(description="The URL given by the user that resulted in this response.") url: str = Field(description="The URL given by the user that resulted in this response.")
def _content_translator(content: Generator[str, None, None], page: _ScraplingResponse) -> ResponseModel: def _translate_response(
"""Convert a content generator to a list of ResponseModel objects.""" page: _ScraplingResponse,
return ResponseModel(status=page.status, content=[result for result in content], url=page.url) extraction_type: extraction_types,
css_selector: Optional[str],
main_content_only: bool,
) -> ResponseModel:
"""Extract content from a response and translate it to a ResponseModel."""
content = list(
Convertor._extract_content(
page,
css_selector=css_selector,
extraction_type=extraction_type,
main_content_only=main_content_only,
)
)
return ResponseModel(status=page.status, content=content, url=page.url)
def _normalize_credentials(credentials: Optional[Dict[str, str]]) -> Optional[Tuple[str, str]]: def _normalize_credentials(credentials: Optional[Dict[str, str]]) -> Optional[Tuple[str, str]]:
@@ -58,7 +67,7 @@ def _normalize_credentials(credentials: Optional[Dict[str, str]]) -> Optional[Tu
class ScraplingMCPServer: class ScraplingMCPServer:
@staticmethod @staticmethod
def get( async def get(
url: str, url: str,
impersonate: ImpersonateType = "chrome", impersonate: ImpersonateType = "chrome",
extraction_type: extraction_types = "markdown", extraction_type: extraction_types = "markdown",
@@ -107,36 +116,28 @@ class ScraplingMCPServer:
:param http3: Whether to use HTTP3. Defaults to False. It might be problematic if used it with `impersonate`. :param http3: Whether to use HTTP3. Defaults to False. It might be problematic if used it with `impersonate`.
:param stealthy_headers: If enabled (default), it creates and adds real browser headers. It also sets a Google referer header. :param stealthy_headers: If enabled (default), it creates and adds real browser headers. It also sets a Google referer header.
""" """
normalized_proxy_auth = _normalize_credentials(proxy_auth) results = await ScraplingMCPServer.bulk_get(
normalized_auth = _normalize_credentials(auth) urls=[url],
page = Fetcher.get(
url,
auth=normalized_auth,
proxy=proxy,
http3=http3,
verify=verify,
params=params,
proxy_auth=normalized_proxy_auth,
retry_delay=retry_delay,
stealthy_headers=stealthy_headers,
impersonate=impersonate, impersonate=impersonate,
extraction_type=extraction_type,
css_selector=css_selector,
main_content_only=main_content_only,
params=params,
headers=headers, headers=headers,
cookies=cookies, cookies=cookies,
timeout=timeout, timeout=timeout,
retries=retries,
max_redirects=max_redirects,
follow_redirects=follow_redirects, follow_redirects=follow_redirects,
max_redirects=max_redirects,
retries=retries,
retry_delay=retry_delay,
proxy=proxy,
proxy_auth=proxy_auth,
auth=auth,
verify=verify,
http3=http3,
stealthy_headers=stealthy_headers,
) )
return _content_translator( return results[0]
Convertor._extract_content(
page,
css_selector=css_selector,
extraction_type=extraction_type,
main_content_only=main_content_only,
),
page,
)
@staticmethod @staticmethod
async def bulk_get( async def bulk_get(
@@ -214,18 +215,7 @@ class ScraplingMCPServer:
for url in urls for url in urls
] ]
responses = await gather(*tasks) responses = await gather(*tasks)
return [ return [_translate_response(page, extraction_type, css_selector, main_content_only) for page in responses]
_content_translator(
Convertor._extract_content(
page,
css_selector=css_selector,
extraction_type=extraction_type,
main_content_only=main_content_only,
),
page,
)
for page in responses
]
@staticmethod @staticmethod
async def fetch( async def fetch(
@@ -280,34 +270,29 @@ class ScraplingMCPServer:
:param extra_headers: A dictionary of extra headers to add to the request. _The referer set by `google_search` takes priority over the referer set here if used together._ :param extra_headers: A dictionary of extra headers to add to the request. _The referer set by `google_search` takes priority over the referer set here if used together._
:param proxy: The proxy to be used with requests, it can be a string or a dictionary with the keys 'server', 'username', and 'password' only. :param proxy: The proxy to be used with requests, it can be a string or a dictionary with the keys 'server', 'username', and 'password' only.
""" """
page = await DynamicFetcher.async_fetch( results = await ScraplingMCPServer.bulk_fetch(
url, urls=[url],
extraction_type=extraction_type,
css_selector=css_selector,
main_content_only=main_content_only,
headless=headless,
google_search=google_search,
real_chrome=real_chrome,
wait=wait, wait=wait,
proxy=proxy, proxy=proxy,
locale=locale,
timeout=timeout,
cookies=cookies,
cdp_url=cdp_url,
headless=headless,
useragent=useragent,
timezone_id=timezone_id, timezone_id=timezone_id,
real_chrome=real_chrome, locale=locale,
network_idle=network_idle,
wait_selector=wait_selector,
extra_headers=extra_headers, extra_headers=extra_headers,
google_search=google_search, useragent=useragent,
cdp_url=cdp_url,
timeout=timeout,
disable_resources=disable_resources, disable_resources=disable_resources,
wait_selector=wait_selector,
cookies=cookies,
network_idle=network_idle,
wait_selector_state=wait_selector_state, wait_selector_state=wait_selector_state,
) )
return _content_translator( return results[0]
Convertor._extract_content(
page,
css_selector=css_selector,
extraction_type=extraction_type,
main_content_only=main_content_only,
),
page,
)
@staticmethod @staticmethod
async def bulk_fetch( async def bulk_fetch(
@@ -383,18 +368,7 @@ class ScraplingMCPServer:
) as session: ) as session:
tasks = [session.fetch(url) for url in urls] tasks = [session.fetch(url) for url in urls]
responses = await gather(*tasks) responses = await gather(*tasks)
return [ return [_translate_response(page, extraction_type, css_selector, main_content_only) for page in responses]
_content_translator(
Convertor._extract_content(
page,
css_selector=css_selector,
extraction_type=extraction_type,
main_content_only=main_content_only,
),
page,
)
for page in responses
]
@staticmethod @staticmethod
async def stealthy_fetch( async def stealthy_fetch(
@@ -459,39 +433,34 @@ class ScraplingMCPServer:
:param proxy: The proxy to be used with requests, it can be a string or a dictionary with the keys 'server', 'username', and 'password' only. :param proxy: The proxy to be used with requests, it can be a string or a dictionary with the keys 'server', 'username', and 'password' only.
:param additional_args: Additional arguments to be passed to Playwright's context as additional settings, and it takes higher priority than Scrapling's settings. :param additional_args: Additional arguments to be passed to Playwright's context as additional settings, and it takes higher priority than Scrapling's settings.
""" """
page = await StealthyFetcher.async_fetch( results = await ScraplingMCPServer.bulk_stealthy_fetch(
url, urls=[url],
extraction_type=extraction_type,
css_selector=css_selector,
main_content_only=main_content_only,
headless=headless,
google_search=google_search,
real_chrome=real_chrome,
wait=wait, wait=wait,
proxy=proxy, proxy=proxy,
timezone_id=timezone_id,
locale=locale, locale=locale,
extra_headers=extra_headers,
useragent=useragent,
hide_canvas=hide_canvas,
cdp_url=cdp_url, cdp_url=cdp_url,
timeout=timeout, timeout=timeout,
cookies=cookies,
headless=headless,
useragent=useragent,
timezone_id=timezone_id,
real_chrome=real_chrome,
hide_canvas=hide_canvas,
allow_webgl=allow_webgl,
network_idle=network_idle,
block_webrtc=block_webrtc,
wait_selector=wait_selector,
google_search=google_search,
extra_headers=extra_headers,
additional_args=additional_args,
solve_cloudflare=solve_cloudflare,
disable_resources=disable_resources, disable_resources=disable_resources,
wait_selector=wait_selector,
cookies=cookies,
network_idle=network_idle,
wait_selector_state=wait_selector_state, wait_selector_state=wait_selector_state,
block_webrtc=block_webrtc,
allow_webgl=allow_webgl,
solve_cloudflare=solve_cloudflare,
additional_args=additional_args,
) )
return _content_translator( return results[0]
Convertor._extract_content(
page,
css_selector=css_selector,
extraction_type=extraction_type,
main_content_only=main_content_only,
),
page,
)
@staticmethod @staticmethod
async def bulk_stealthy_fetch( async def bulk_stealthy_fetch(
@@ -581,18 +550,7 @@ class ScraplingMCPServer:
) as session: ) as session:
tasks = [session.fetch(url) for url in urls] tasks = [session.fetch(url) for url in urls]
responses = await gather(*tasks) responses = await gather(*tasks)
return [ return [_translate_response(page, extraction_type, css_selector, main_content_only) for page in responses]
_content_translator(
Convertor._extract_content(
page,
css_selector=css_selector,
extraction_type=extraction_type,
main_content_only=main_content_only,
),
page,
)
for page in responses
]
def serve(self, http: bool, host: str, port: int): def serve(self, http: bool, host: str, port: int):
"""Serve the MCP server.""" """Serve the MCP server."""
+3 -2
View File
@@ -16,9 +16,10 @@ class TestMCPServer:
def server(self): def server(self):
return ScraplingMCPServer() return ScraplingMCPServer()
def test_get_tool(self, server, test_url): @pytest.mark.asyncio
async def test_get_tool(self, server, test_url):
"""Test the get tool method""" """Test the get tool method"""
result = server.get(url=test_url, extraction_type="markdown") result = await server.get(url=test_url, extraction_type="markdown")
assert isinstance(result, ResponseModel) assert isinstance(result, ResponseModel)
assert result.status == 200 assert result.status == 200
assert result.url == test_url assert result.url == test_url