From 6b2ec018e23c60ec5ed6c5b0b7a5e823ce5d0c9e Mon Sep 17 00:00:00 2001 From: Bnyro Date: Mon, 29 Jun 2026 19:49:28 +0200 Subject: [PATCH] [fix] qwant: access denied due to mismatching query order We're now shuffling the order of query arguments, as it seems that Qwant actively blocks the query order of SearXNG. tgp seems to be short for "test group" (look at the XHR requests the browser sends to "https://www.qwant.com/action/ui") - its actual value doesn't matter, as long as it's sent and at the correct position in the query params and doesn't change too frequently --- searx/engines/qwant.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/searx/engines/qwant.py b/searx/engines/qwant.py index 0e75ee559..1c690146c 100644 --- a/searx/engines/qwant.py +++ b/searx/engines/qwant.py @@ -38,6 +38,7 @@ Implementations """ +import random import typing as t from datetime import ( @@ -89,6 +90,11 @@ qwant_categ: str = None # pyright: ignore[reportAssignmentType] safesearch = True +# tgp seems to be short for "test group" - its actual value doesn't matter, as +# long as it's sent and at the correct position in the query params and doesn't +# change too frequently +test_group_value = random.randint(1, 3) + # fmt: off qwant_news_locales = [ "ca_ad", "ca_es", "ca_fr", "co_fr", "de_at", "de_ch", "de_de", "en_au", @@ -114,17 +120,24 @@ def request(query: str, params: "OnlineParams") -> None: results_per_page = 10 if qwant_categ == "images": results_per_page = 50 + args = { "q": query, "count": results_per_page, "locale": q_locale, "offset": (params["pageno"] - 1) * results_per_page, + "tgp": test_group_value, "device": "desktop", "safesearch": params["safesearch"], - "tgp": 1, "display": True, "llm": True, } + + # shuffle query parameters to be harder to fingerprint + args = list(args.items()) + random.shuffle(args) + args = dict(args) + params["raise_for_httperror"] = False params["url"] = f"{api_url}{qwant_categ}?{urlencode(args)}" @@ -190,7 +203,6 @@ def response(resp: "SXNG_Response") -> EngineResults: mainline_items: list[dict[str, t.Any]] = row.get("items", []) for item in mainline_items: - title: str = item.get("title", "") res_url: str = item.get("url", "") pub_date: datetime | None = None