feat(fetcher): Make impersonate able to randomize fingerprint
This commit is contained in:
+23
-5
@@ -2,6 +2,8 @@ from pathlib import Path
|
|||||||
from subprocess import check_output
|
from subprocess import check_output
|
||||||
from sys import executable as python_executable
|
from sys import executable as python_executable
|
||||||
|
|
||||||
|
from curl_cffi.requests import impersonate
|
||||||
|
|
||||||
from scrapling.core.utils import log
|
from scrapling.core.utils import log
|
||||||
from scrapling.engines.toolbelt.custom import Response
|
from scrapling.engines.toolbelt.custom import Response
|
||||||
from scrapling.core.utils._shell import _CookieParser, _ParseHeaders
|
from scrapling.core.utils._shell import _CookieParser, _ParseHeaders
|
||||||
@@ -84,7 +86,7 @@ def __BuildRequest(headers: List[str], cookies: str, params: str, json: Optional
|
|||||||
# Parse parameters
|
# Parse parameters
|
||||||
parsed_headers, parsed_cookies, parsed_params, parsed_json = __ParseExtractArguments(headers, cookies, params, json)
|
parsed_headers, parsed_cookies, parsed_params, parsed_json = __ParseExtractArguments(headers, cookies, params, json)
|
||||||
# Build request arguments
|
# Build request arguments
|
||||||
request_kwargs = {
|
request_kwargs: Dict[str, Any] = {
|
||||||
"headers": parsed_headers if parsed_headers else None,
|
"headers": parsed_headers if parsed_headers else None,
|
||||||
"cookies": parsed_cookies if parsed_cookies else None,
|
"cookies": parsed_cookies if parsed_cookies else None,
|
||||||
}
|
}
|
||||||
@@ -95,6 +97,10 @@ def __BuildRequest(headers: List[str], cookies: str, params: str, json: Optional
|
|||||||
if "proxy" in kwargs:
|
if "proxy" in kwargs:
|
||||||
request_kwargs["proxy"] = kwargs.pop("proxy")
|
request_kwargs["proxy"] = kwargs.pop("proxy")
|
||||||
|
|
||||||
|
# Parse impersonate parameter if it contains commas (for random selection)
|
||||||
|
if "impersonate" in kwargs and "," in (kwargs.get("impersonate") or ""):
|
||||||
|
kwargs["impersonate"] = [browser.strip() for browser in kwargs["impersonate"].split(",")]
|
||||||
|
|
||||||
return {**request_kwargs, **kwargs}
|
return {**request_kwargs, **kwargs}
|
||||||
|
|
||||||
|
|
||||||
@@ -225,7 +231,10 @@ def extract():
|
|||||||
default=True,
|
default=True,
|
||||||
help="Whether to verify SSL certificates (default: True)",
|
help="Whether to verify SSL certificates (default: True)",
|
||||||
)
|
)
|
||||||
@option("--impersonate", help="Browser to impersonate (e.g., chrome, firefox).")
|
@option(
|
||||||
|
"--impersonate",
|
||||||
|
help="Browser to impersonate. Can be a single browser (e.g., chrome) or comma-separated list for random selection (e.g., chrome,firefox,safari).",
|
||||||
|
)
|
||||||
@option(
|
@option(
|
||||||
"--stealthy-headers/--no-stealthy-headers",
|
"--stealthy-headers/--no-stealthy-headers",
|
||||||
default=True,
|
default=True,
|
||||||
@@ -318,7 +327,10 @@ def get(
|
|||||||
default=True,
|
default=True,
|
||||||
help="Whether to verify SSL certificates (default: True)",
|
help="Whether to verify SSL certificates (default: True)",
|
||||||
)
|
)
|
||||||
@option("--impersonate", help="Browser to impersonate (e.g., chrome, firefox).")
|
@option(
|
||||||
|
"--impersonate",
|
||||||
|
help="Browser to impersonate. Can be a single browser (e.g., chrome) or comma-separated list for random selection (e.g., chrome,firefox,safari).",
|
||||||
|
)
|
||||||
@option(
|
@option(
|
||||||
"--stealthy-headers/--no-stealthy-headers",
|
"--stealthy-headers/--no-stealthy-headers",
|
||||||
default=True,
|
default=True,
|
||||||
@@ -412,7 +424,10 @@ def post(
|
|||||||
default=True,
|
default=True,
|
||||||
help="Whether to verify SSL certificates (default: True)",
|
help="Whether to verify SSL certificates (default: True)",
|
||||||
)
|
)
|
||||||
@option("--impersonate", help="Browser to impersonate (e.g., chrome, firefox).")
|
@option(
|
||||||
|
"--impersonate",
|
||||||
|
help="Browser to impersonate. Can be a single browser (e.g., chrome) or comma-separated list for random selection (e.g., chrome,firefox,safari).",
|
||||||
|
)
|
||||||
@option(
|
@option(
|
||||||
"--stealthy-headers/--no-stealthy-headers",
|
"--stealthy-headers/--no-stealthy-headers",
|
||||||
default=True,
|
default=True,
|
||||||
@@ -504,7 +519,10 @@ def put(
|
|||||||
default=True,
|
default=True,
|
||||||
help="Whether to verify SSL certificates (default: True)",
|
help="Whether to verify SSL certificates (default: True)",
|
||||||
)
|
)
|
||||||
@option("--impersonate", help="Browser to impersonate (e.g., chrome, firefox).")
|
@option(
|
||||||
|
"--impersonate",
|
||||||
|
help="Browser to impersonate. Can be a single browser (e.g., chrome) or comma-separated list for random selection (e.g., chrome,firefox,safari).",
|
||||||
|
)
|
||||||
@option(
|
@option(
|
||||||
"--stealthy-headers/--no-stealthy-headers",
|
"--stealthy-headers/--no-stealthy-headers",
|
||||||
default=True,
|
default=True,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from pydantic import BaseModel, Field
|
|||||||
|
|
||||||
from scrapling.core.shell import Convertor
|
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.fetchers import (
|
from scrapling.fetchers import (
|
||||||
Fetcher,
|
Fetcher,
|
||||||
FetcherSession,
|
FetcherSession,
|
||||||
@@ -24,9 +25,6 @@ from scrapling.core._types import (
|
|||||||
SelectorWaitStates,
|
SelectorWaitStates,
|
||||||
Generator,
|
Generator,
|
||||||
)
|
)
|
||||||
from curl_cffi.requests import (
|
|
||||||
BrowserTypeLiteral,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ResponseModel(BaseModel):
|
class ResponseModel(BaseModel):
|
||||||
@@ -46,7 +44,7 @@ class ScraplingMCPServer:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get(
|
def get(
|
||||||
url: str,
|
url: str,
|
||||||
impersonate: Optional[BrowserTypeLiteral] = "chrome",
|
impersonate: ImpersonateType = "chrome",
|
||||||
extraction_type: extraction_types = "markdown",
|
extraction_type: extraction_types = "markdown",
|
||||||
css_selector: Optional[str] = None,
|
css_selector: Optional[str] = None,
|
||||||
main_content_only: bool = True,
|
main_content_only: bool = True,
|
||||||
@@ -124,7 +122,7 @@ class ScraplingMCPServer:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
async def bulk_get(
|
async def bulk_get(
|
||||||
urls: Tuple[str, ...],
|
urls: Tuple[str, ...],
|
||||||
impersonate: Optional[BrowserTypeLiteral] = "chrome",
|
impersonate: ImpersonateType = "chrome",
|
||||||
extraction_type: extraction_types = "markdown",
|
extraction_type: extraction_types = "markdown",
|
||||||
css_selector: Optional[str] = None,
|
css_selector: Optional[str] = None,
|
||||||
main_content_only: bool = True,
|
main_content_only: bool = True,
|
||||||
|
|||||||
+35
-15
@@ -1,4 +1,5 @@
|
|||||||
from abc import ABC
|
from abc import ABC
|
||||||
|
from random import choice
|
||||||
from time import sleep as time_sleep
|
from time import sleep as time_sleep
|
||||||
from asyncio import sleep as asyncio_sleep
|
from asyncio import sleep as asyncio_sleep
|
||||||
|
|
||||||
@@ -31,12 +32,29 @@ from .toolbelt.fingerprints import generate_convincing_referer, generate_headers
|
|||||||
_UNSET: Any = object()
|
_UNSET: Any = object()
|
||||||
_NO_SESSION: Any = object()
|
_NO_SESSION: Any = object()
|
||||||
|
|
||||||
|
# Type alias for `impersonate` parameter - accepts a single browser or list of browsers
|
||||||
|
ImpersonateType = BrowserTypeLiteral | List[BrowserTypeLiteral] | None
|
||||||
|
|
||||||
|
|
||||||
|
def _select_random_browser(impersonate: ImpersonateType) -> Optional[BrowserTypeLiteral]:
|
||||||
|
"""
|
||||||
|
Handle browser selection logic for the ` impersonate ` parameter.
|
||||||
|
|
||||||
|
If impersonate is a list, randomly select one browser from it.
|
||||||
|
If it's a string or None, return as is.
|
||||||
|
"""
|
||||||
|
if isinstance(impersonate, list):
|
||||||
|
if not impersonate:
|
||||||
|
return None
|
||||||
|
return choice(impersonate)
|
||||||
|
return impersonate
|
||||||
|
|
||||||
|
|
||||||
class _ConfigurationLogic(ABC):
|
class _ConfigurationLogic(ABC):
|
||||||
# Core Logic Handler (Internal Engine)
|
# Core Logic Handler (Internal Engine)
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
impersonate: Optional[BrowserTypeLiteral] = "chrome",
|
impersonate: ImpersonateType = "chrome",
|
||||||
http3: Optional[bool] = False,
|
http3: Optional[bool] = False,
|
||||||
stealthy_headers: Optional[bool] = True,
|
stealthy_headers: Optional[bool] = True,
|
||||||
proxies: Optional[Dict[str, str]] = None,
|
proxies: Optional[Dict[str, str]] = None,
|
||||||
@@ -76,7 +94,9 @@ class _ConfigurationLogic(ABC):
|
|||||||
def _merge_request_args(self, **method_kwargs) -> Dict[str, Any]:
|
def _merge_request_args(self, **method_kwargs) -> Dict[str, Any]:
|
||||||
"""Merge request-specific arguments with default session arguments."""
|
"""Merge request-specific arguments with default session arguments."""
|
||||||
url = method_kwargs.pop("url")
|
url = method_kwargs.pop("url")
|
||||||
impersonate = self._get_with_precedence(method_kwargs.pop("impersonate"), self._default_impersonate)
|
impersonate = _select_random_browser(
|
||||||
|
self._get_with_precedence(method_kwargs.pop("impersonate"), self._default_impersonate)
|
||||||
|
)
|
||||||
http3_enabled = self._get_with_precedence(method_kwargs.pop("http3"), self._default_http3)
|
http3_enabled = self._get_with_precedence(method_kwargs.pop("http3"), self._default_http3)
|
||||||
final_args = {
|
final_args = {
|
||||||
"url": url,
|
"url": url,
|
||||||
@@ -146,7 +166,7 @@ class _ConfigurationLogic(ABC):
|
|||||||
class _SyncSessionLogic(_ConfigurationLogic):
|
class _SyncSessionLogic(_ConfigurationLogic):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
impersonate: Optional[BrowserTypeLiteral] = "chrome",
|
impersonate: ImpersonateType = "chrome",
|
||||||
http3: Optional[bool] = False,
|
http3: Optional[bool] = False,
|
||||||
stealthy_headers: Optional[bool] = True,
|
stealthy_headers: Optional[bool] = True,
|
||||||
proxies: Optional[Dict[str, str]] = None,
|
proxies: Optional[Dict[str, str]] = None,
|
||||||
@@ -261,7 +281,7 @@ class _SyncSessionLogic(_ConfigurationLogic):
|
|||||||
auth: Optional[Tuple[str, str]] = None,
|
auth: Optional[Tuple[str, str]] = None,
|
||||||
verify: Optional[bool] = _UNSET,
|
verify: Optional[bool] = _UNSET,
|
||||||
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
||||||
impersonate: Optional[BrowserTypeLiteral] = _UNSET,
|
impersonate: ImpersonateType = _UNSET,
|
||||||
http3: Optional[bool] = _UNSET,
|
http3: Optional[bool] = _UNSET,
|
||||||
stealthy_headers: Optional[bool] = _UNSET,
|
stealthy_headers: Optional[bool] = _UNSET,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -334,7 +354,7 @@ class _SyncSessionLogic(_ConfigurationLogic):
|
|||||||
auth: Optional[Tuple[str, str]] = None,
|
auth: Optional[Tuple[str, str]] = None,
|
||||||
verify: Optional[bool] = _UNSET,
|
verify: Optional[bool] = _UNSET,
|
||||||
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
||||||
impersonate: Optional[BrowserTypeLiteral] = _UNSET,
|
impersonate: ImpersonateType = _UNSET,
|
||||||
http3: Optional[bool] = _UNSET,
|
http3: Optional[bool] = _UNSET,
|
||||||
stealthy_headers: Optional[bool] = _UNSET,
|
stealthy_headers: Optional[bool] = _UNSET,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -411,7 +431,7 @@ class _SyncSessionLogic(_ConfigurationLogic):
|
|||||||
auth: Optional[Tuple[str, str]] = None,
|
auth: Optional[Tuple[str, str]] = None,
|
||||||
verify: Optional[bool] = _UNSET,
|
verify: Optional[bool] = _UNSET,
|
||||||
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
||||||
impersonate: Optional[BrowserTypeLiteral] = _UNSET,
|
impersonate: ImpersonateType = _UNSET,
|
||||||
http3: Optional[bool] = _UNSET,
|
http3: Optional[bool] = _UNSET,
|
||||||
stealthy_headers: Optional[bool] = _UNSET,
|
stealthy_headers: Optional[bool] = _UNSET,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -488,7 +508,7 @@ class _SyncSessionLogic(_ConfigurationLogic):
|
|||||||
auth: Optional[Tuple[str, str]] = None,
|
auth: Optional[Tuple[str, str]] = None,
|
||||||
verify: Optional[bool] = _UNSET,
|
verify: Optional[bool] = _UNSET,
|
||||||
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
||||||
impersonate: Optional[BrowserTypeLiteral] = _UNSET,
|
impersonate: ImpersonateType = _UNSET,
|
||||||
http3: Optional[bool] = _UNSET,
|
http3: Optional[bool] = _UNSET,
|
||||||
stealthy_headers: Optional[bool] = _UNSET,
|
stealthy_headers: Optional[bool] = _UNSET,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -552,7 +572,7 @@ class _SyncSessionLogic(_ConfigurationLogic):
|
|||||||
class _ASyncSessionLogic(_ConfigurationLogic):
|
class _ASyncSessionLogic(_ConfigurationLogic):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
impersonate: Optional[BrowserTypeLiteral] = "chrome",
|
impersonate: ImpersonateType = "chrome",
|
||||||
http3: Optional[bool] = False,
|
http3: Optional[bool] = False,
|
||||||
stealthy_headers: Optional[bool] = True,
|
stealthy_headers: Optional[bool] = True,
|
||||||
proxies: Optional[Dict[str, str]] = None,
|
proxies: Optional[Dict[str, str]] = None,
|
||||||
@@ -669,7 +689,7 @@ class _ASyncSessionLogic(_ConfigurationLogic):
|
|||||||
auth: Optional[Tuple[str, str]] = None,
|
auth: Optional[Tuple[str, str]] = None,
|
||||||
verify: Optional[bool] = _UNSET,
|
verify: Optional[bool] = _UNSET,
|
||||||
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
||||||
impersonate: Optional[BrowserTypeLiteral] = _UNSET,
|
impersonate: ImpersonateType = _UNSET,
|
||||||
http3: Optional[bool] = _UNSET,
|
http3: Optional[bool] = _UNSET,
|
||||||
stealthy_headers: Optional[bool] = _UNSET,
|
stealthy_headers: Optional[bool] = _UNSET,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -742,7 +762,7 @@ class _ASyncSessionLogic(_ConfigurationLogic):
|
|||||||
auth: Optional[Tuple[str, str]] = None,
|
auth: Optional[Tuple[str, str]] = None,
|
||||||
verify: Optional[bool] = _UNSET,
|
verify: Optional[bool] = _UNSET,
|
||||||
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
||||||
impersonate: Optional[BrowserTypeLiteral] = _UNSET,
|
impersonate: ImpersonateType = _UNSET,
|
||||||
http3: Optional[bool] = _UNSET,
|
http3: Optional[bool] = _UNSET,
|
||||||
stealthy_headers: Optional[bool] = _UNSET,
|
stealthy_headers: Optional[bool] = _UNSET,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -819,7 +839,7 @@ class _ASyncSessionLogic(_ConfigurationLogic):
|
|||||||
auth: Optional[Tuple[str, str]] = None,
|
auth: Optional[Tuple[str, str]] = None,
|
||||||
verify: Optional[bool] = _UNSET,
|
verify: Optional[bool] = _UNSET,
|
||||||
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
||||||
impersonate: Optional[BrowserTypeLiteral] = _UNSET,
|
impersonate: ImpersonateType = _UNSET,
|
||||||
http3: Optional[bool] = _UNSET,
|
http3: Optional[bool] = _UNSET,
|
||||||
stealthy_headers: Optional[bool] = _UNSET,
|
stealthy_headers: Optional[bool] = _UNSET,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -896,7 +916,7 @@ class _ASyncSessionLogic(_ConfigurationLogic):
|
|||||||
auth: Optional[Tuple[str, str]] = None,
|
auth: Optional[Tuple[str, str]] = None,
|
||||||
verify: Optional[bool] = _UNSET,
|
verify: Optional[bool] = _UNSET,
|
||||||
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
cert: Optional[str | Tuple[str, str]] = _UNSET,
|
||||||
impersonate: Optional[BrowserTypeLiteral] = _UNSET,
|
impersonate: ImpersonateType = _UNSET,
|
||||||
http3: Optional[bool] = _UNSET,
|
http3: Optional[bool] = _UNSET,
|
||||||
stealthy_headers: Optional[bool] = _UNSET,
|
stealthy_headers: Optional[bool] = _UNSET,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -970,7 +990,7 @@ class FetcherSession:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
impersonate: Optional[BrowserTypeLiteral] = "chrome",
|
impersonate: ImpersonateType = "chrome",
|
||||||
http3: Optional[bool] = False,
|
http3: Optional[bool] = False,
|
||||||
stealthy_headers: Optional[bool] = True,
|
stealthy_headers: Optional[bool] = True,
|
||||||
proxies: Optional[Dict[str, str]] = None,
|
proxies: Optional[Dict[str, str]] = None,
|
||||||
@@ -987,7 +1007,7 @@ class FetcherSession:
|
|||||||
selector_config: Optional[Dict] = None,
|
selector_config: Optional[Dict] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
:param impersonate: Browser version to impersonate. Automatically defaults to the latest available Chrome version.
|
:param impersonate: Browser version to impersonate. Can be a single browser string or a list of browser strings for random selection. (Default: latest available Chrome version)
|
||||||
: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 the referer header as if this request came from a Google search of URL's domain.
|
:param stealthy_headers: If enabled (default), it creates and adds real browser headers. It also sets the referer header as if this request came from a Google search of URL's domain.
|
||||||
:param proxies: Dict of proxies to use. Format: {"http": proxy_url, "https": proxy_url}.
|
:param proxies: Dict of proxies to use. Format: {"http": proxy_url, "https": proxy_url}.
|
||||||
@@ -1004,7 +1024,7 @@ class FetcherSession:
|
|||||||
:param cert: Tuple of (cert, key) filenames for the client certificate.
|
:param cert: Tuple of (cert, key) filenames for the client certificate.
|
||||||
:param selector_config: Arguments passed when creating the final Selector class.
|
:param selector_config: Arguments passed when creating the final Selector class.
|
||||||
"""
|
"""
|
||||||
self._default_impersonate: Optional[BrowserTypeLiteral] = impersonate
|
self._default_impersonate: ImpersonateType = impersonate
|
||||||
self._stealth = stealthy_headers
|
self._stealth = stealthy_headers
|
||||||
self._default_proxies = proxies or {}
|
self._default_proxies = proxies or {}
|
||||||
self._default_proxy = proxy or None
|
self._default_proxy = proxy or None
|
||||||
|
|||||||
Reference in New Issue
Block a user