From 71d1504e572074327f851688a4caac90a5e41fe8 Mon Sep 17 00:00:00 2001 From: Zhijie He Date: Sun, 23 Feb 2025 13:35:23 +0800 Subject: [PATCH] [feat] add 360search engine for searxng Co-authored-by: Bnyro --- docs/admin/settings/settings_search.rst | 1 + searx/autocomplete.py | 16 ++++++ searx/engines/360search.py | 67 +++++++++++++++++++++++++ searx/engines/360search_videos.py | 64 +++++++++++++++++++++++ searx/settings.yml | 12 ++++- 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 searx/engines/360search.py create mode 100644 searx/engines/360search_videos.py diff --git a/docs/admin/settings/settings_search.rst b/docs/admin/settings/settings_search.rst index 83f3bca0b..a85640fa5 100644 --- a/docs/admin/settings/settings_search.rst +++ b/docs/admin/settings/settings_search.rst @@ -33,6 +33,7 @@ ``autocomplete``: Existing autocomplete backends, leave blank to turn it off. + - ``360search`` - ``baidu`` - ``brave`` - ``dbpedia`` diff --git a/searx/autocomplete.py b/searx/autocomplete.py index a0aa2a730..2ef6189a5 100644 --- a/searx/autocomplete.py +++ b/searx/autocomplete.py @@ -148,6 +148,21 @@ def mwmbl(query, _lang): return [result for result in results if not result.startswith("go: ") and not result.startswith("search: ")] +def qihu360search(query, _lang): + # 360Search search autocompleter + url = f"https://sug.so.360.cn/suggest?{urlencode({'format': 'json', 'word': query})}" + response = get(url) + + results = [] + + if response.ok: + data = response.json() + if 'result' in data: + for item in data['result']: + results.append(item['word']) + return results + + def seznam(query, _lang): # seznam search autocompleter url = 'https://suggest.seznam.cz/fulltext/cs?{query}' @@ -246,6 +261,7 @@ def yandex(query, _lang): backends = { + '360search': qihu360search, 'baidu': baidu, 'brave': brave, 'dbpedia': dbpedia, diff --git a/searx/engines/360search.py b/searx/engines/360search.py new file mode 100644 index 000000000..9d575f1b3 --- /dev/null +++ b/searx/engines/360search.py @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +# pylint: disable=invalid-name +"""360Search search engine for searxng""" + +from urllib.parse import urlencode +from lxml import html + +from searx.utils import extract_text + +# Metadata +about = { + "website": "https://www.so.com/", + "wikidata_id": "Q10846064", + "use_official_api": False, + "require_api_key": False, + "results": "HTML", +} + +# Engine Configuration +categories = ["general"] +paging = True +time_range_support = True + +time_range_dict = {'day': 'd', 'week': 'w', 'month': 'm', 'year': 'y'} + +# Base URL +base_url = "https://www.so.com" + + +def request(query, params): + query_params = { + "pn": params["pageno"], + "q": query, + } + + if time_range_dict.get(params['time_range']): + query_params["adv_t"] = time_range_dict.get(params['time_range']) + + params["url"] = f"{base_url}/s?{urlencode(query_params)}" + return params + + +def response(resp): + dom = html.fromstring(resp.text) + results = [] + + for item in dom.xpath('//li[contains(@class, "res-list")]'): + title = extract_text(item.xpath('.//h3[contains(@class, "res-title")]/a')) + + url = extract_text(item.xpath('.//h3[contains(@class, "res-title")]/a/@data-mdurl')) + if not url: + url = extract_text(item.xpath('.//h3[contains(@class, "res-title")]/a/@href')) + + content = extract_text(item.xpath('.//p[@class="res-desc"]')) + if not content: + content = extract_text(item.xpath('.//span[@class="res-list-summary"]')) + + if title and url: + results.append( + { + "title": title, + "url": url, + "content": content, + } + ) + + return results diff --git a/searx/engines/360search_videos.py b/searx/engines/360search_videos.py new file mode 100644 index 000000000..a4a59223d --- /dev/null +++ b/searx/engines/360search_videos.py @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +# pylint: disable=invalid-name +"""360Search-Videos: A search engine for retrieving videos from 360Search.""" + +from urllib.parse import urlencode +from datetime import datetime + +from searx.exceptions import SearxEngineAPIException +from searx.utils import html_to_text + +about = { + "website": "https://tv.360kan.com/", + "use_official_api": False, + "require_api_key": False, + "results": "JSON", +} + +paging = True +results_per_page = 10 +categories = ["videos"] + +base_url = "https://tv.360kan.com" + + +def request(query, params): + query_params = {"count": 10, "q": query, "start": params["pageno"] * 10} + + params["url"] = f"{base_url}/v1/video/list?{urlencode(query_params)}" + return params + + +def response(resp): + try: + data = resp.json() + except Exception as e: + raise SearxEngineAPIException(f"Invalid response: {e}") from e + results = [] + + if "data" not in data or "result" not in data["data"]: + raise SearxEngineAPIException("Invalid response") + + for entry in data["data"]["result"]: + if not entry.get("title") or not entry.get("play_url"): + continue + + published_date = None + if entry.get("publish_time"): + try: + published_date = datetime.fromtimestamp(int(entry["publish_time"])) + except (ValueError, TypeError): + published_date = None + + results.append( + { + 'url': entry["play_url"], + 'title': html_to_text(entry["title"]), + 'content': html_to_text(entry["description"]), + 'template': 'videos.html', + 'publishedDate': published_date, + 'thumbnail': entry["cover_img"], + } + ) + + return results diff --git a/searx/settings.yml b/searx/settings.yml index 5a9bc0fa6..496828464 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -33,7 +33,7 @@ brand: search: # Filter results. 0: None, 1: Moderate, 2: Strict safe_search: 0 - # Existing autocomplete backends: "baidu", "brave", "dbpedia", "duckduckgo", "google", "yandex", + # Existing autocomplete backends: "360search", "baidu", "brave", "dbpedia", "duckduckgo", "google", "yandex", # "mwmbl", "seznam", "stract", "swisscows", "qwant", "wikipedia" - # leave blank to turn it off by default. autocomplete: "" @@ -337,6 +337,16 @@ categories_as_tabs: social media: engines: + - name: 360search + engine: 360search + shortcut: 360so + disabled: true + + - name: 360search videos + engine: 360search_videos + shortcut: 360sov + disabled: true + - name: 9gag engine: 9gag shortcut: 9g