standalone_searx.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. #!/usr/bin/env python
  2. # lint: pylint
  3. # SPDX-License-Identifier: AGPL-3.0-or-later
  4. # (C) Copyright Contributors to the SearXNG project.
  5. """Script to run SearXNG from terminal.
  6. DON'T USE THIS SCRIPT!!
  7. .. danger::
  8. Be warned, using the ``standalone_searx.py`` won't give you privacy!
  9. On the contrary, this script behaves like a SearXNG server: your IP is
  10. exposed and tracked by all active engines (google, bing, qwant, ... ), with
  11. every query!
  12. .. note::
  13. This is an old and grumpy hack / SearXNG is a Flask application with
  14. client/server structure, which can't be turned into a command line tool the
  15. way it was done here.
  16. Getting categories without initiate the engine will only return `['general']`
  17. >>> import searx.engines
  18. ... list(searx.engines.categories.keys())
  19. ['general']
  20. >>> import searx.search
  21. ... searx.search.initialize()
  22. ... list(searx.engines.categories.keys())
  23. ['general', 'it', 'science', 'images', 'news', 'videos', 'music', 'files', 'social media', 'map']
  24. Example to use this script:
  25. .. code:: bash
  26. $ python3 searxng_extra/standalone_searx.py rain
  27. """ # pylint: disable=line-too-long
  28. import argparse
  29. import sys
  30. from datetime import datetime
  31. from json import dumps
  32. from typing import Any, Dict, List, Optional
  33. import searx
  34. import searx.preferences
  35. import searx.query
  36. import searx.search
  37. import searx.webadapter
  38. EngineCategoriesVar = Optional[List[str]]
  39. def get_search_query(
  40. args: argparse.Namespace, engine_categories: EngineCategoriesVar = None
  41. ) -> searx.search.SearchQuery:
  42. """Get search results for the query"""
  43. if engine_categories is None:
  44. engine_categories = list(searx.engines.categories.keys())
  45. try:
  46. category = args.category.decode('utf-8')
  47. except AttributeError:
  48. category = args.category
  49. form = {
  50. "q": args.query,
  51. "categories": category,
  52. "pageno": str(args.pageno),
  53. "language": args.lang,
  54. "time_range": args.timerange,
  55. }
  56. preferences = searx.preferences.Preferences(['simple'], engine_categories, searx.engines.engines, [])
  57. preferences.key_value_settings['safesearch'].parse(args.safesearch)
  58. search_query = searx.webadapter.get_search_query_from_webapp(preferences, form)[0]
  59. return search_query
  60. def no_parsed_url(results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
  61. """Remove parsed url from dict."""
  62. for result in results:
  63. del result['parsed_url']
  64. return results
  65. def json_serial(obj: Any) -> Any:
  66. """JSON serializer for objects not serializable by default json code.
  67. :raise TypeError: raised when **obj** is not serializable
  68. """
  69. if isinstance(obj, datetime):
  70. serial = obj.isoformat()
  71. return serial
  72. if isinstance(obj, bytes):
  73. return obj.decode('utf8')
  74. if isinstance(obj, set):
  75. return list(obj)
  76. raise TypeError("Type ({}) not serializable".format(type(obj)))
  77. def to_dict(search_query: searx.search.SearchQuery) -> Dict[str, Any]:
  78. """Get result from parsed arguments."""
  79. result_container = searx.search.Search(search_query).search()
  80. result_container_json = {
  81. "search": {
  82. "q": search_query.query,
  83. "pageno": search_query.pageno,
  84. "lang": search_query.lang,
  85. "safesearch": search_query.safesearch,
  86. "timerange": search_query.time_range,
  87. },
  88. "results": no_parsed_url(result_container.get_ordered_results()),
  89. "infoboxes": result_container.infoboxes,
  90. "suggestions": list(result_container.suggestions),
  91. "answers": list(result_container.answers),
  92. "paging": result_container.paging,
  93. "number_of_results": result_container.number_of_results,
  94. }
  95. return result_container_json
  96. def parse_argument(
  97. args: Optional[List[str]] = None, category_choices: EngineCategoriesVar = None
  98. ) -> argparse.Namespace:
  99. """Parse command line.
  100. :raise SystemExit: Query argument required on `args`
  101. Examples:
  102. >>> import importlib
  103. ... # load module
  104. ... spec = importlib.util.spec_from_file_location(
  105. ... 'utils.standalone_searx', 'utils/standalone_searx.py')
  106. ... sas = importlib.util.module_from_spec(spec)
  107. ... spec.loader.exec_module(sas)
  108. ... sas.parse_argument()
  109. usage: ptipython [-h] [--category [{general}]] [--lang [LANG]] [--pageno [PAGENO]] [--safesearch [{0,1,2}]] [--timerange [{day,week,month,year}]]
  110. query
  111. SystemExit: 2
  112. >>> sas.parse_argument(['rain'])
  113. Namespace(category='general', lang='all', pageno=1, query='rain', safesearch='0', timerange=None)
  114. """ # noqa: E501
  115. if not category_choices:
  116. category_choices = list(searx.engines.categories.keys())
  117. parser = argparse.ArgumentParser(description='Standalone searx.')
  118. parser.add_argument('query', type=str, help='Text query')
  119. parser.add_argument(
  120. '--category', type=str, nargs='?', choices=category_choices, default='general', help='Search category'
  121. )
  122. parser.add_argument('--lang', type=str, nargs='?', default='all', help='Search language')
  123. parser.add_argument('--pageno', type=int, nargs='?', default=1, help='Page number starting from 1')
  124. parser.add_argument(
  125. '--safesearch',
  126. type=str,
  127. nargs='?',
  128. choices=['0', '1', '2'],
  129. default='0',
  130. help='Safe content filter from none to strict',
  131. )
  132. parser.add_argument(
  133. '--timerange', type=str, nargs='?', choices=['day', 'week', 'month', 'year'], help='Filter by time range'
  134. )
  135. return parser.parse_args(args)
  136. if __name__ == '__main__':
  137. settings_engines = searx.settings['engines']
  138. searx.search.load_engines(settings_engines)
  139. engine_cs = list(searx.engines.categories.keys())
  140. prog_args = parse_argument(category_choices=engine_cs)
  141. searx.search.initialize_network(settings_engines, searx.settings['outgoing'])
  142. searx.search.check_network_configuration()
  143. searx.search.initialize_metrics([engine['name'] for engine in settings_engines])
  144. searx.search.initialize_processors(settings_engines)
  145. search_q = get_search_query(prog_args, engine_categories=engine_cs)
  146. res_dict = to_dict(search_q)
  147. sys.stdout.write(dumps(res_dict, sort_keys=True, indent=4, ensure_ascii=False, default=json_serial))