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:
+72
-114
@@ -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."""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user