2222import json
2323import time
2424from typing import Any , Dict , Optional , Union
25- from urllib .parse import urlparse
25+ from urllib .parse import urlparse , quote
2626
2727from playwright .async_api import Page
2828
2929from .xhs_sign import b64_encode , encode_utf8 , get_trace_id , mrc
3030
3131
32- def _build_sign_string (uri : str , data : Optional [Union [Dict , str ]] = None ) -> str :
33- """构建待签名字符串"""
34- c = uri
35- if data is not None :
32+ def _build_sign_string (uri : str , data : Optional [Union [Dict , str ]] = None , method : str = "POST" ) -> str :
33+ """构建待签名字符串
34+
35+ Args:
36+ uri: API路径
37+ data: 请求数据
38+ method: 请求方法 (GET 或 POST)
39+
40+ Returns:
41+ 待签名字符串
42+ """
43+ if method .upper () == "POST" :
44+ # POST 请求使用 JSON 格式
45+ c = uri
46+ if data is not None :
47+ if isinstance (data , dict ):
48+ c += json .dumps (data , separators = ("," , ":" ), ensure_ascii = False )
49+ elif isinstance (data , str ):
50+ c += data
51+ return c
52+ else :
53+ # GET 请求使用查询字符串格式
54+ if not data or (isinstance (data , dict ) and len (data ) == 0 ):
55+ return uri
56+
3657 if isinstance (data , dict ):
37- c += json .dumps (data , separators = ("," , ":" ), ensure_ascii = False )
58+ params = []
59+ for key in data .keys ():
60+ value = data [key ]
61+ if isinstance (value , list ):
62+ value_str = "," .join (str (v ) for v in value )
63+ elif value is not None :
64+ value_str = str (value )
65+ else :
66+ value_str = ""
67+ # 使用URL编码(safe参数保留某些字符不编码)
68+ # 注意:httpx会对逗号、等号等字符进行编码,我们也需要同样处理
69+ value_str = quote (value_str , safe = '' )
70+ params .append (f"{ key } ={ value_str } " )
71+ return f"{ uri } ?{ '&' .join (params )} "
3872 elif isinstance (data , str ):
39- c += data
40- return c
73+ return f" { uri } ? { data } "
74+ return uri
4175
4276
4377def _md5_hex (s : str ) -> str :
@@ -113,6 +147,7 @@ async def sign_xs_with_playwright(
113147 page : Page ,
114148 uri : str ,
115149 data : Optional [Union [Dict , str ]] = None ,
150+ method : str = "POST" ,
116151) -> str :
117152 """
118153 通过 playwright 注入生成 x-s 签名
@@ -121,11 +156,12 @@ async def sign_xs_with_playwright(
121156 page: playwright Page 对象(必须已打开小红书页面)
122157 uri: API 路径,如 "/api/sns/web/v1/search/notes"
123158 data: 请求数据(GET 的 params 或 POST 的 payload)
159+ method: 请求方法 (GET 或 POST)
124160
125161 Returns:
126162 x-s 签名字符串
127163 """
128- sign_str = _build_sign_string (uri , data )
164+ sign_str = _build_sign_string (uri , data , method )
129165 md5_str = _md5_hex (sign_str )
130166 x3_value = await call_mnsv2 (page , sign_str , md5_str )
131167 data_type = "object" if isinstance (data , (dict , list )) else "string"
@@ -137,6 +173,7 @@ async def sign_with_playwright(
137173 uri : str ,
138174 data : Optional [Union [Dict , str ]] = None ,
139175 a1 : str = "" ,
176+ method : str = "POST" ,
140177) -> Dict [str , Any ]:
141178 """
142179 通过 playwright 生成完整的签名请求头
@@ -146,12 +183,13 @@ async def sign_with_playwright(
146183 uri: API 路径
147184 data: 请求数据
148185 a1: cookie 中的 a1 值
186+ method: 请求方法 (GET 或 POST)
149187
150188 Returns:
151189 包含 x-s, x-t, x-s-common, x-b3-traceid 的字典
152190 """
153191 b1 = await get_b1_from_localstorage (page )
154- x_s = await sign_xs_with_playwright (page , uri , data )
192+ x_s = await sign_xs_with_playwright (page , uri , data , method )
155193 x_t = str (int (time .time () * 1000 ))
156194
157195 return {
@@ -186,14 +224,17 @@ async def pre_headers_with_playwright(
186224 a1_value = cookie_dict .get ("a1" , "" )
187225 uri = urlparse (url ).path
188226
227+ # 确定请求数据和方法
189228 if params is not None :
190229 data = params
230+ method = "GET"
191231 elif payload is not None :
192232 data = payload
233+ method = "POST"
193234 else :
194235 raise ValueError ("params or payload is required" )
195236
196- signs = await sign_with_playwright (page , uri , data , a1_value )
237+ signs = await sign_with_playwright (page , uri , data , a1_value , method )
197238
198239 return {
199240 "X-S" : signs ["x-s" ],
0 commit comments