Skip to main content
Glama
fancyboi999

Xiaohongshu (Little Red Book) MCP Server

by fancyboi999
xhs_api.py9.73 kB
import asyncio import json import time from collections.abc import Mapping from urllib.parse import urlencode import requests from curl_cffi.requests import AsyncSession, Response from typing import Dict import os import execjs from numbers import Integral from typing import Iterable, List, Optional, Tuple import random import base64 from xhs_auto_mcp.tools.log_utils import logger class XhsApi: def __init__(self,cookie): self._cookie=cookie self._base_url="https://edith.xiaohongshu.com" self._headers = { 'content-type': 'application/json;charset=UTF-8', 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36', } def init_session(self): return AsyncSession( verify=True, impersonate="chrome124" ) def _parse_cookie(self, cookie: str) -> Dict: cookie_dict = {} if cookie: pairs = cookie.split(';') for pair in pairs: key, value = pair.strip().split('=', 1) cookie_dict[key] = value return cookie_dict async def request(self,uri: str,session=None, method="GET",headers=None,params=None,data=None) -> Dict: if session is None: session=self.init_session() if headers is None: headers = {} logger.debug(f"发送请求: {method} {self._base_url}{uri}") if params: logger.debug(f"请求参数: {params}") if data: logger.debug(f"请求数据: {data}") response: Response = await session.request( method=method, url=f"{self._base_url}{uri}", params=params, json=data, cookies=self._parse_cookie(self._cookie), quote=False, stream=True, headers=headers ) content = await response.acontent() result = json.loads(content) if 'success' in result: if result['success']: logger.debug(f"请求成功: {uri}") else: logger.warning(f"请求失败: {uri}, 错误信息: {result.get('msg', '未知错误')}") return result def base36encode(self,number: Integral, alphabet: Iterable[str] = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ') -> str: base36 = '' alphabet = ''.join(alphabet) sign = '-' if number < 0 else '' number = abs(number) while number: number, i = divmod(number, len(alphabet)) base36 = alphabet[i] + base36 return sign + (base36 or alphabet[0]) def search_id(self): e = int(time.time() * 1000) << 64 t = int(random.uniform(0, 2147483646)) return self.base36encode((e + t)) def get_xs_xt(self,uri, data, cookie): current_directory = os.path.dirname(os.path.abspath(__file__)) file_path = os.path.join(current_directory, "xhsvm.js") try: return execjs.compile(open(file_path, 'r', encoding='utf-8').read()).call('GetXsXt', uri, data, cookie) except FileNotFoundError: logger.error(f"找不到文件: {file_path}") raise async def get_me(self) -> Dict: uri = '/api/sns/web/v2/user/me' logger.info("获取用户信息") return await self.request(uri,method="GET") async def search_notes(self, keywords: str, limit: int = 20) -> Dict: logger.info(f"搜索笔记: {keywords}, 限制: {limit}") data={ "keyword":keywords, "page":1, "page_size":limit, "search_id":self.search_id(), "sort":"general", "note_type":0, "ext_flags":[], "geo":"", "image_formats":json.dumps(["jpg","webp","avif"], separators=(",", ":")) } return await self.request("/api/sns/web/v1/search/notes",method="POST",data=data) async def home_feed(self) -> Dict: data={ "category":"homefeed_recommend", "cursor_score":"", "image_formats":json.dumps(["jpg","webp","avif"], separators=(",", ":")), "need_filter_image":False, "need_num":8, "num":18, "note_index":33, "refresh_type":1, "search_key":"", "unread_begin_note_id":"", "unread_end_note_id":"", "unread_note_count":0 } uri="/api/sns/web/v1/homefeed" p={"uri":uri,"method":"POST","data":data} headers = { 'content-type': 'application/json;charset=UTF-8', 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36', } xsxt=json.loads(self.get_xs_xt(uri,data,self._cookie)) headers['x-s']=xsxt['X-s'] headers['x-t']=str(xsxt['X-t']) return await self.request(**p,headers=headers) async def get_note_content(self, note_id: str, xsec_token: str) -> Dict: data = { "source_note_id": note_id, "image_formats": ["jpg","webp","avif"], "extra": {"need_body_topic":"1"}, "xsec_source": "pc_feed", "xsec_token": xsec_token } uri="/api/sns/web/v1/feed" p={"uri":uri,"method":"POST","data":data} headers = { 'content-type': 'application/json;charset=UTF-8', 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36', } xsxt=json.loads(self.get_xs_xt(uri,data,self._cookie)) headers['x-s']=xsxt['X-s'] headers['x-t']=str(xsxt['X-t']) headers['x-s-common']='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' return await self.request(**p,headers=headers) async def get_note_comments(self,note_id:str,xsec_token:str) -> Dict: uri = '/api/sns/web/v2/comment/page' # 680a25a4000000001c02d251 # ABzm9YfVyNA1hsY-KwU7ybKNWlkpb8__t-jF9FwGKzZz0= params = { 'note_id': note_id, 'cursor': '', 'top_comment_id': '', 'image_formats': 'jpg,webp,avif', 'xsec_token': xsec_token } return await self.request(uri,method="GET",params=params) async def post_comment(self,note_id:str, comment: str) -> Dict: uri='/api/sns/web/v1/comment/post' # 680ce9d1000000001c02cb9f data={ "note_id":note_id, "content":comment, "at_users":[] } headers = { 'content-type': 'application/json;charset=UTF-8', 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36', } xsxt = json.loads(self.get_xs_xt(uri, data, self._cookie)) headers['x-s'] = xsxt['X-s'] headers['x-t'] = str(xsxt['X-t']) return await self.request(uri, method="POST",headers=headers, data=data)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/fancyboi999/xhs-auto-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server