xiaohongshu_search.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. #!/usr/bin/env python3
  2. """
  3. 小红书笔记搜索工具
  4. 根据关键词搜索小红书笔记,支持多种筛选条件
  5. """
  6. import requests
  7. import json
  8. import os
  9. import argparse
  10. from datetime import datetime
  11. from typing import Dict, Any
  12. class XiaohongshuSearch:
  13. """小红书笔记搜索API封装类"""
  14. BASE_URL = "http://47.84.182.56:8001"
  15. TOOL_NAME = "xhs_note_search"
  16. PLATFORM = "xiaohongshu"
  17. def __init__(self, results_dir: str = None):
  18. """
  19. 初始化API客户端
  20. Args:
  21. results_dir: 结果输出目录,默认为项目根目录下的 data/search 文件夹
  22. """
  23. self.api_url = f"{self.BASE_URL}/tools/call/{self.TOOL_NAME}"
  24. # 设置结果输出目录
  25. if results_dir:
  26. self.results_base_dir = results_dir
  27. else:
  28. # 默认使用项目根目录的 data/search 文件夹
  29. script_dir = os.path.dirname(os.path.abspath(__file__))
  30. project_root = os.path.dirname(os.path.dirname(script_dir))
  31. self.results_base_dir = os.path.join(project_root, "data", "search")
  32. def search(
  33. self,
  34. keyword: str,
  35. content_type: str = "不限",
  36. sort_type: str = "综合",
  37. publish_time: str = "不限",
  38. cursor: str = "",
  39. timeout: int = 30
  40. ) -> Dict[str, Any]:
  41. """
  42. 搜索小红书笔记
  43. Args:
  44. keyword: 搜索关键词
  45. content_type: 内容类型,可选值:不限、视频、图文,默认为'不限'
  46. sort_type: 排序方式,可选值:综合、最新、最多点赞、最多评论,默认为'综合'
  47. publish_time: 发布时间筛选,可选值:不限、一天内、一周内、半年内,默认为'不限'
  48. cursor: 翻页游标,第一页默认为空,下一页的游标在上一页的返回值中获取
  49. timeout: 请求超时时间(秒),默认30秒
  50. Returns:
  51. API响应的JSON数据
  52. Raises:
  53. requests.exceptions.RequestException: 请求失败时抛出异常
  54. """
  55. payload = {
  56. "keyword": keyword,
  57. "content_type": content_type,
  58. "sort_type": sort_type,
  59. "publish_time": publish_time,
  60. "cursor": cursor
  61. }
  62. try:
  63. response = requests.post(
  64. self.api_url,
  65. json=payload,
  66. timeout=timeout,
  67. headers={"Content-Type": "application/json"}
  68. )
  69. response.raise_for_status()
  70. result = response.json()
  71. # 预处理返回数据:提取 image_list 中的 URL 字符串
  72. self._preprocess_response(result)
  73. return result
  74. except requests.exceptions.RequestException as e:
  75. print(f"请求失败: {e}")
  76. raise
  77. def _preprocess_response(self, result: Dict[str, Any]) -> None:
  78. """
  79. 预处理搜索结果,将 image_list 中的字典格式转换为 URL 字符串列表
  80. Args:
  81. result: API返回的原始结果字典(会直接修改)
  82. """
  83. # 获取帖子列表
  84. notes = result.get("data", {}).get("data", [])
  85. for note in notes:
  86. note_card = note.get("note_card", {})
  87. image_list_raw = note_card.get("image_list", [])
  88. # 提取 URL 字符串
  89. image_list = []
  90. for img in image_list_raw:
  91. if isinstance(img, dict) and "image_url" in img:
  92. image_list.append(img["image_url"])
  93. elif isinstance(img, str):
  94. # 如果已经是字符串,直接使用
  95. image_list.append(img)
  96. # 更新为预处理后的列表
  97. note_card["image_list"] = image_list
  98. def save_result(self, keyword: str, result: Dict[str, Any], page: int = 1) -> str:
  99. """
  100. 保存结果到文件
  101. 目录结构: results/xiaohongshu_search/关键词/时间戳_page{页码}.json
  102. Args:
  103. keyword: 搜索关键词
  104. result: API返回的结果
  105. page: 页码
  106. Returns:
  107. 保存的文件路径
  108. """
  109. # 创建目录结构: results/xiaohongshu_search/关键词/
  110. result_dir = os.path.join(self.results_base_dir, "xiaohongshu_search", keyword)
  111. os.makedirs(result_dir, exist_ok=True)
  112. # 文件名使用时间戳和页码
  113. timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
  114. filename = f"{timestamp}_page{page}.json"
  115. filepath = os.path.join(result_dir, filename)
  116. # 保存结果
  117. with open(filepath, 'w', encoding='utf-8') as f:
  118. json.dump(result, f, ensure_ascii=False, indent=2)
  119. return filepath
  120. def main():
  121. """示例使用"""
  122. # 解析命令行参数
  123. parser = argparse.ArgumentParser(description='小红书笔记搜索工具')
  124. parser.add_argument(
  125. '--results-dir',
  126. type=str,
  127. default='data/search',
  128. help='结果输出目录 (默认: data/search)'
  129. )
  130. parser.add_argument(
  131. '--keyword',
  132. type=str,
  133. required=True,
  134. help='搜索关键词 (必填)'
  135. )
  136. parser.add_argument(
  137. '--content-type',
  138. type=str,
  139. default='不限',
  140. choices=['不限', '视频', '图文'],
  141. help='内容类型 (默认: 不限)'
  142. )
  143. parser.add_argument(
  144. '--sort-type',
  145. type=str,
  146. default='综合',
  147. choices=['综合', '最新', '最多点赞', '最多评论'],
  148. help='排序方式 (默认: 综合)'
  149. )
  150. parser.add_argument(
  151. '--publish-time',
  152. type=str,
  153. default='不限',
  154. choices=['不限', '一天内', '一周内', '半年内'],
  155. help='发布时间筛选 (默认: 不限)'
  156. )
  157. parser.add_argument(
  158. '--cursor',
  159. type=str,
  160. default='',
  161. help='翻页游标 (默认为空,即第一页)'
  162. )
  163. parser.add_argument(
  164. '--page',
  165. type=int,
  166. default=1,
  167. help='页码标识,用于保存文件名 (默认: 1)'
  168. )
  169. args = parser.parse_args()
  170. # 创建API客户端实例
  171. client = XiaohongshuSearch(results_dir=args.results_dir)
  172. # 执行搜索并保存
  173. try:
  174. result = client.search(
  175. args.keyword,
  176. args.content_type,
  177. args.sort_type,
  178. args.publish_time,
  179. args.cursor
  180. )
  181. filepath = client.save_result(args.keyword, result, args.page)
  182. print(f"Output: {filepath}")
  183. except Exception as e:
  184. print(f"Error: {e}", file=__import__('sys').stderr)
  185. if __name__ == "__main__":
  186. main()