Files
searxng/searx/engines/yandex.py
Bnyro 77d07c8743 [mod] yandex: simplify request method and drop unused params/cookies
We previously set the 'cookie' cookie value with a timestamp
from 2024 to `yq=...`. The cookie is not actually used in Yandex,
instead we probably wanted to set the `yq` cookie instead. But
since the engine works without it, it can be dropped.

Also simplifies the request method a bit to be easier to read.
2026-09-17 09:46:21 +02:00

142 lines
4.9 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
"""Yandex (Web, images)"""
import typing as t
from json import JSONDecodeError, loads
from urllib.parse import urlencode
from lxml import html
from searx.exceptions import SearxEngineCaptchaException, SearxEngineResponseException
from searx.result_types import EngineResults
from searx.utils import humanize_bytes, eval_xpath, eval_xpath_list, extract_text, html_to_text
if t.TYPE_CHECKING:
from searx import logger # logger is injected by searx.engines.set_loggers()
from searx.extended_types import SXNG_Response
from searx.search.processors import OnlineParams
# Engine metadata
about = {
"website": "https://yandex.com/",
"wikidata_id": "Q5281",
"official_api_documentation": "?",
"use_official_api": False,
"require_api_key": False,
"results": "HTML",
}
# Engine configuration
categories = []
paging = True
enable_http3 = True
search_type = ""
# Search URL
base_url_web = "https://yandex.com/search/site/"
base_url_images = "https://yandex.com/images/search"
# Supported languages
yandex_supported_langs = [
"ru", # Russian
"en", # English
"be", # Belarusian
"fr", # French
"de", # German
"id", # Indonesian
"kk", # Kazakh
"tt", # Tatar
"tr", # Turkish
"uk", # Ukrainian
]
results_xpath = '//li[contains(@class, "serp-item")]'
url_xpath = './/a[@class="b-serp-item__title-link"]/@href'
title_xpath = './/h3[@class="b-serp-item__title"]/a[@class="b-serp-item__title-link"]/span'
content_xpath = './/div[@class="b-serp-item__content"]//div[@class="b-serp-item__text"]'
def catch_bad_response(resp: "SXNG_Response") -> None:
if resp.headers.get("x-yandex-captcha") == "captcha":
raise SearxEngineCaptchaException()
def request(query: str, params: "OnlineParams") -> None:
args: dict[str, t.Any] = {
"text": query,
}
if params['pageno'] > 1:
args["p"] = params["pageno"] - 1
if search_type == 'web':
lang = params["searxng_locale"].split("-")[0]
if lang in yandex_supported_langs:
args["lang"] = lang
args.update(
{
"tmpl_version": "releases",
"web": "1",
"frame": "1",
"searchid": "3131712",
}
)
params['url'] = f"{base_url_web}?{urlencode(args)}"
elif search_type == 'images':
params['url'] = f"{base_url_images}?{urlencode(args)}"
def _parse_json_results(dom: html.HtmlElement) -> dict[str, t.Any]:
# attempt to parse using xpath - finding element with "data-state" attribute
data_elements = dom.xpath("//*[@data-state]/@data-state")
for json_data in data_elements:
try:
json_resp = loads(json_data)
if json_resp.get("location") == "/images/search/":
return json_resp
except JSONDecodeError:
logger.debug("failed parsing data-state json")
raise SearxEngineResponseException("failed to parse JSON data")
def response(resp: "SXNG_Response") -> EngineResults:
catch_bad_response(resp)
results = EngineResults()
dom = html.fromstring(resp.text)
match search_type:
case "web":
for result in eval_xpath_list(dom, results_xpath):
url = extract_text(eval_xpath(result, url_xpath))
title = extract_text(eval_xpath(result, title_xpath))
content = extract_text(eval_xpath(result, content_xpath))
results.add(results.types.MainResult(url=url, title=str(title), content=str(content)))
case "images":
json_resp = _parse_json_results(dom)
# build results from loaded json values
for item_data in json_resp["initialState"]["serpList"]["items"]["entities"].values():
viewerData: dict[str, t.Any] = item_data["viewerData"]
snippet: dict[str, str] = viewerData.get("snippet", {})
# return the image with largest dimensions
image_sources = viewerData.get("dups", []) + viewerData.get("preview", [])
image_source = max(image_sources, key=lambda x: x["h"] * x["w"])
humanized_filesize = None
if image_source.get("fileSizeInBytes"):
humanized_filesize = humanize_bytes(image_source["fileSizeInBytes"])
results.add(
results.types.Image(
title=snippet.get("title"), # type: ignore
content=html_to_text(snippet.get("text")), # type: ignore
url=snippet.get("url"),
img_src=image_source["url"],
filesize=humanized_filesize, # type: ignore
thumbnail_src=item_data["image"],
resolution=f"{image_source['w']} x {image_source['h']}",
)
)
return results