Repository: apify/crawlee-python
File: src/crawlee/fingerprint_suite/_browserforge_adapter.py (Python)

Code before:
            Accept-Language header string.
        """
        # First locale does not include quality factor, q=1 is considered as implicit.
        additional_locales = [f'{locale};q={0.9 - index * 0.1:.1f}' for index, locale in enumerate(locales[1:])]
        return ','.join((locales[0], *additional_locales))

    def generate(
        self,
        *,
        browser: Iterable[str | Browser] | None = None,
        os: ListOrString | None = None,
        device: ListOrString | None = None,
        locale: ListOrString | None = None,
        http_version: Literal[1, 2] | None = None,
        user_agent: ListOrString | None = None,
        strict: bool | None = None,
        request_dependent_headers: dict[str, str] | None = None,
    ) -> dict[str, str]:
        """Generate headers.

        browser_type = `chromium` is in general sense not just Google Chrome, but also other chromium based browsers.
        For example this Safari user agent can be generated for `chromium` input:
        `Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko)
         CriOS/130.0.6723.90 Mobile/15E148 Safari/604.1`
        To remain consistent with previous implementation only subset of `chromium` headers will be allowed.
        """
        # browserforge header generation can be flaky. Enforce basic QA on generated headers
        max_attempts = 10

        single_browser = self._get_single_browser_type(browser)

        if single_browser == 'chromium':
            # `BrowserForge` header generator considers `chromium` in general sense and therefore will generate also
            # other `Chromium` based browser headers. This adapter desires only specific subset of `chromium` headers
            # that contain all 'sec-ch-ua', 'sec-ch-ua-mobile', 'sec-ch-ua-platform' headers.
            # Increase max attempts as from `BrowserForge` header generator perspective even `chromium`
            # headers without `sec-...` headers are valid.
            max_attempts += 50

        # Browserforge uses term 'safari', we use term 'webkit'
        bf_browser_type = 'safari' if single_browser == 'webkit' else single_browser

        # Use browserforge to generate headers until it satisfies our additional requirements.
        for _attempt in range(max_attempts):
            generated_header: dict[str, str] = super().generate(
                browser=bf_browser_type,
                os=os,
                device=device,
                locale=locale,
                http_version=http_version,
                user_agent=user_agent,

Review comment (suggestion):
```suggestion
        """
        Generate HTTP headers based on the specified parameters.

        The `browser` parameter accepts `chromium` as a general category, which includes not only Google Chrome 
        but also other Chromium-based browsers. As a result, a Safari-like user agent may be generated for a `chromium` input,
        such as:          
        ```
        Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/130.0.6723.90 Mobile/15E148 Safari/604.1
        ```
        To maintain consistency with previous implementations, only a subset of Chromium headers will be allowed.

        Returns:
            A generated headers.
        """
```

Code after:
            Accept-Language header string.
        """
        # First locale does not include quality factor, q=1 is considered as implicit.
        additional_locales = [f'{locale};q={0.9 - index * 0.1:.1f}' for index, locale in enumerate(locales[1:])]
        return ','.join((locales[0], *additional_locales))

    def generate(
        self,
        *,
        browser: Iterable[str | Browser] | None = None,
        os: ListOrString | None = None,
        device: ListOrString | None = None,
        locale: ListOrString | None = None,
        http_version: Literal[1, 2] | None = None,
        user_agent: ListOrString | None = None,
        strict: bool | None = None,
        request_dependent_headers: dict[str, str] | None = None,
    ) -> dict[str, str]:
        """Generate HTTP headers based on the specified parameters.

        For detailed description of the original method see: `browserforge.headers.generator.HeaderGenerator.generate`
        This patched version of the method adds additional quality checks on the output of the original method. It tries
        to generate headers several times until they match the requirements.

        The `browser` parameter accepts `chromium` as a general category, which includes not only Google Chrome
        but also other Chromium-based browsers. As a result, a Safari-like user agent may be generated for a `chromium`
        input, such as:
        ```
        Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko)
         CriOS/130.0.6723.90 Mobile/15E148 Safari/604.1
        ```
        To maintain consistency with previous implementations, only a subset of Chromium headers will be allowed.

        Returns:
            A generated headers.
        """
        # browserforge header generation can be flaky. Enforce basic QA on generated headers
        max_attempts = 10

        single_browser = self._get_single_browser_type(browser)

        if single_browser == 'chromium':
            # `BrowserForge` header generator considers `chromium` in general sense and therefore will generate also
            # other `Chromium` based browser headers. This adapter desires only specific subset of `chromium` headers
            # that contain all 'sec-ch-ua', 'sec-ch-ua-mobile', 'sec-ch-ua-platform' headers.
            # Increase max attempts as from `BrowserForge` header generator perspective even `chromium`
            # headers without `sec-...` headers are valid.
            max_attempts += 50

        # Browserforge uses term 'safari', we use term 'webkit'
        bf_browser_type = 'safari' if single_browser == 'webkit' else single_browser