Skip to content
Back to top

# Source code for searx.engines.springer

    # SPDX-License-Identifier: AGPL-3.0-or-later
    """`Springer Nature`_ is a global publisher dedicated to providing service to
    research community with official Springer-API_ (API-Playground_).

    .. note::

       The Springer engine requires an API key, which can be obtained via the
       `Springer subscription`_.

    Since the search term is passed 1:1 to the API, SearXNG users can use the
    `Supported Query Parameters`_.

    - ``!springer (doi:10.1007/s10948-025-07019-1 OR doi:10.1007/s10948-025-07035-1)``
    - ``!springer keyword:ybco``

    However, please note that the available options depend on the subscription type.

    For example, the ``year:`` filter requires a *Premium Plan* subscription.

    - ``!springer keyword:ybco year:2024``

    The engine uses the REST Meta-API_ `v2` endpoint, but there is also a `Python
    API Wrapper`_.

    .. _Python API Wrapper: https://pypi.org/project/springernature-api-client/
    .. _Springer Nature: https://www.springernature.com/
    .. _Springer subscription:  https://dev.springernature.com/subscription/
    .. _Springer-API: https://dev.springernature.com/docs/introduction/
    .. _API-Playground: https://dev.springernature.com/docs/live-documentation/
    .. _Meta-API: https://dev.springernature.com/docs/api-endpoints/meta-api/
    .. _Supported Query Parameters: https://dev.springernature.com/docs/supported-query-params/

    Configuration
    =============

    The engine has the following additional settings:

    - :py:obj:`api_key`

    .. code:: yaml

      - name: springer nature
        api_key: "..."
        inactive: false

    Implementations
    ===============

    """

    import typing as t

    from datetime import datetime
    from urllib.parse import urlencode

    from searx.network import raise_for_httperror
    from searx.result_types import EngineResults

    if t.TYPE_CHECKING:
        from searx.extended_types import SXNG_Response
        from searx.search.processors import OnlineParams

    about = {
        "website": "https://www.springernature.com/",
        "wikidata_id": "Q21096327",
        "official_api_documentation": "https://dev.springernature.com/docs/live-documentation/",
        "use_official_api": True,
        "require_api_key": True,
        "results": "JSON",
    }

    categories = ["science", "scientific publications"]

    paging = True
    page_size = 10
    """Number of results to return in the request, see `Pagination and Limits`_ for
    more details.

    .. _Pagination and Limits:
        https://dev.springernature.com/docs/advanced-querying/pagination-limits/
    """

    api_key = ""
    """Key used for the Meta-API_.  Get your API key from: `Springer subscription`_"""

    base_url = "https://api.springernature.com/meta/v2/json"
    """An enhanced endpoint with additional metadata fields and optimized queries
    for more efficient and comprehensive retrieval (Meta-API_ `v2`).
    """

    def setup(engine_settings: dict[str, t.Any]) -> bool:
        """Initialization of the Springer engine, checks whether the
        :py:obj:`api_key` is set, otherwise the engine is inactive.
        """
        key: str = engine_settings.get("api_key", "")
        try:
            # Springer's API key is a hex value
            int(key, 16)
            return True
        except ValueError:
            logger.error("Springer's API key is not set or invalid.")
            return False

    def request(query: str, params: "OnlineParams") -> None:
        args = {
            "api_key": api_key,
            "q": query,
            "s": page_size * (params["pageno"] - 1),
            "p": page_size,
        }
        params["url"] = f"{base_url}?{urlencode(args)}"
        # For example, the ``year:`` filter requires a *Premium Plan* subscription.
        params["raise_for_httperror"] = False

    def response(resp: "SXNG_Response") -> EngineResults:

        res = EngineResults()
        json_data = resp.json()

        if (
            resp.status_code == 403
            and json_data["status"].lower() == "fail"
            and "premium feature" in json_data["message"].lower()
        ):
            return res
        raise_for_httperror(resp)

        def field(k: str) -> str:
            return str(record.get(k, ""))

        for record in json_data["records"]:
            published = datetime.strptime(record["publicationDate"], "%Y-%m-%d")
            authors: list[str] = [" ".join(author["creator"].split(", ")[::-1]) for author in record["creators"]]

            pdf_url = ""
            html_url = ""
            url_list: list[dict[str, str]] = record["url"]

            for item in url_list:
                if item["platform"] != "web":
                    continue
                val = item["value"].replace("http://", "https://", 1)
                if item["format"] == "html":
                    html_url = val
                elif item["format"] == "pdf":
                    pdf_url = val

            paper = res.types.Paper(
                url=html_url,
                # html_url=html_url,
                pdf_url=pdf_url,
                title=field("title"),
                content=field("abstract"),
                comments=field("publicationName"),
                tags=record.get("keyword", []),
                publishedDate=published,
                type=field("contentType"),
                authors=authors,
                publisher=field("publisher"),
                journal=field("publicationName"),
                volume=field("volume"),
                pages="-".join([x for x in [field("startingPage"), field("endingPage")] if x]),
                number=field("number"),
                doi=field("doi"),
                issn=[x for x in [field("issn")] if x],
                isbn=[x for x in [field("isbn")] if x],
            )
            res.add(paper)

        return res