nonebot-plugin-zhihu-parser 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nonebot_plugin_zhihu_parser/__init__.py +331 -0
- nonebot_plugin_zhihu_parser/config.py +84 -0
- nonebot_plugin_zhihu_parser/core/clean.py +35 -0
- nonebot_plugin_zhihu_parser/core/constants.py +24 -0
- nonebot_plugin_zhihu_parser/core/cookie.py +438 -0
- nonebot_plugin_zhihu_parser/core/data.py +359 -0
- nonebot_plugin_zhihu_parser/core/debounce.py +36 -0
- nonebot_plugin_zhihu_parser/core/download.py +464 -0
- nonebot_plugin_zhihu_parser/core/exception.py +53 -0
- nonebot_plugin_zhihu_parser/core/parsers/__init__.py +1 -0
- nonebot_plugin_zhihu_parser/core/parsers/base.py +345 -0
- nonebot_plugin_zhihu_parser/core/parsers/zhihu/__init__.py +3 -0
- nonebot_plugin_zhihu_parser/core/parsers/zhihu/card.py +340 -0
- nonebot_plugin_zhihu_parser/core/parsers/zhihu/common.py +6 -0
- nonebot_plugin_zhihu_parser/core/parsers/zhihu/content.py +1268 -0
- nonebot_plugin_zhihu_parser/core/parsers/zhihu/handlers.py +269 -0
- nonebot_plugin_zhihu_parser/core/parsers/zhihu/identity.py +8 -0
- nonebot_plugin_zhihu_parser/core/parsers/zhihu/maintenance.py +261 -0
- nonebot_plugin_zhihu_parser/core/parsers/zhihu/parser.py +85 -0
- nonebot_plugin_zhihu_parser/core/parsers/zhihu/refresher.py +167 -0
- nonebot_plugin_zhihu_parser/core/parsers/zhihu/request.py +209 -0
- nonebot_plugin_zhihu_parser/core/parsers/zhihu/zse_signer.py +224 -0
- nonebot_plugin_zhihu_parser/core/render.py +712 -0
- nonebot_plugin_zhihu_parser/core/resources/HYSongYunLangHeiW-1.ttf +0 -0
- nonebot_plugin_zhihu_parser/core/resources/logos/bilibili.png +0 -0
- nonebot_plugin_zhihu_parser/core/resources/logos/douyin.png +0 -0
- nonebot_plugin_zhihu_parser/core/resources/logos/kuaishou.png +0 -0
- nonebot_plugin_zhihu_parser/core/resources/logos/tiktok.png +0 -0
- nonebot_plugin_zhihu_parser/core/resources/logos/twitter.png +0 -0
- nonebot_plugin_zhihu_parser/core/resources/logos/weibo.png +0 -0
- nonebot_plugin_zhihu_parser/core/resources/logos/xhs.png +0 -0
- nonebot_plugin_zhihu_parser/core/resources/logos/youtube.png +0 -0
- nonebot_plugin_zhihu_parser/core/resources/media_button.png +0 -0
- nonebot_plugin_zhihu_parser/core/utils.py +242 -0
- nonebot_plugin_zhihu_parser-0.1.0.dist-info/METADATA +128 -0
- nonebot_plugin_zhihu_parser-0.1.0.dist-info/RECORD +38 -0
- nonebot_plugin_zhihu_parser-0.1.0.dist-info/WHEEL +4 -0
- nonebot_plugin_zhihu_parser-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from http import cookiejar
|
|
6
|
+
from http.cookies import SimpleCookie
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
|
|
9
|
+
from nonebot.log import logger
|
|
10
|
+
|
|
11
|
+
from nonebot_plugin_zhihu_parser.config import ParserItem, PluginConfig
|
|
12
|
+
|
|
13
|
+
@dataclass(slots=True)
|
|
14
|
+
class Cookie:
|
|
15
|
+
domain: str
|
|
16
|
+
path: str
|
|
17
|
+
name: str
|
|
18
|
+
value: str
|
|
19
|
+
secure: bool
|
|
20
|
+
expires: int
|
|
21
|
+
|
|
22
|
+
def __post_init__(self) -> None:
|
|
23
|
+
self.domain = self.domain.lower()
|
|
24
|
+
|
|
25
|
+
def is_expired(self) -> bool:
|
|
26
|
+
return self.expires != 0 and self.expires < int(time.time())
|
|
27
|
+
|
|
28
|
+
def match(self, domain: str, path: str, secure: bool) -> bool:
|
|
29
|
+
if self.is_expired():
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
if self.secure and not secure:
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
if not self._domain_matches(domain):
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
return self._path_matches(path)
|
|
39
|
+
|
|
40
|
+
def _domain_matches(self, request_domain: str) -> bool:
|
|
41
|
+
cookie_domain = self.domain
|
|
42
|
+
|
|
43
|
+
if cookie_domain.startswith("."):
|
|
44
|
+
suffix = cookie_domain[1:]
|
|
45
|
+
return request_domain == suffix or request_domain.endswith(f".{suffix}")
|
|
46
|
+
|
|
47
|
+
return request_domain == cookie_domain
|
|
48
|
+
|
|
49
|
+
def _path_matches(self, request_path: str) -> bool:
|
|
50
|
+
if request_path == self.path:
|
|
51
|
+
return True
|
|
52
|
+
if not request_path.startswith(self.path):
|
|
53
|
+
return False
|
|
54
|
+
if self.path.endswith("/"):
|
|
55
|
+
return True
|
|
56
|
+
return (
|
|
57
|
+
len(request_path) > len(self.path) and request_path[len(self.path)] == "/"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class CookieJar:
|
|
62
|
+
def __init__(
|
|
63
|
+
self, config: PluginConfig, parser_cfg: ParserItem, domain: str
|
|
64
|
+
) -> None:
|
|
65
|
+
self.domain = domain.lower()
|
|
66
|
+
|
|
67
|
+
self.cookie_file = config.cookie_dir / f"{parser_cfg.name}_cookies.txt"
|
|
68
|
+
self.cookies: list[Cookie] = []
|
|
69
|
+
|
|
70
|
+
self.raw_cookies = parser_cfg.cookies
|
|
71
|
+
self.cookies_str = ""
|
|
72
|
+
|
|
73
|
+
if self.raw_cookies:
|
|
74
|
+
self.cookies_str = self.clean_cookies_str(self.raw_cookies)
|
|
75
|
+
self._load_from_cookies_str(self.cookies_str)
|
|
76
|
+
self.save_to_file()
|
|
77
|
+
|
|
78
|
+
if self.cookie_file.exists():
|
|
79
|
+
self.load_from_file()
|
|
80
|
+
|
|
81
|
+
# ---------------- public ----------------
|
|
82
|
+
|
|
83
|
+
def file_exists(self) -> bool:
|
|
84
|
+
return self.cookie_file.exists()
|
|
85
|
+
|
|
86
|
+
def get(
|
|
87
|
+
self, path: str = "/", secure: bool = True, domain: str | None = None
|
|
88
|
+
) -> dict[str, str]:
|
|
89
|
+
cookies: dict[str, str] = {}
|
|
90
|
+
for cookie in self._ordered_matching_cookies(
|
|
91
|
+
path=path, secure=secure, domain=domain
|
|
92
|
+
):
|
|
93
|
+
cookies.setdefault(cookie.name, cookie.value)
|
|
94
|
+
return cookies
|
|
95
|
+
|
|
96
|
+
def get_cookie_header(
|
|
97
|
+
self, path: str = "/", secure: bool = True, domain: str | None = None
|
|
98
|
+
) -> str:
|
|
99
|
+
cookies = self._ordered_matching_cookies(
|
|
100
|
+
path=path, secure=secure, domain=domain
|
|
101
|
+
)
|
|
102
|
+
return "; ".join(f"{cookie.name}={cookie.value}" for cookie in cookies)
|
|
103
|
+
|
|
104
|
+
def get_cookie_header_for_url(self, url: str) -> str:
|
|
105
|
+
parsed = urlparse(url)
|
|
106
|
+
if not parsed.hostname:
|
|
107
|
+
return ""
|
|
108
|
+
return self.get_cookie_header(
|
|
109
|
+
domain=parsed.hostname,
|
|
110
|
+
path=parsed.path or "/",
|
|
111
|
+
secure=parsed.scheme == "https",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def purge_expired(self) -> None:
|
|
115
|
+
self.cookies = [c for c in self.cookies if not c.is_expired()]
|
|
116
|
+
self._sync_cookies_str()
|
|
117
|
+
if self.cookie_file.exists() or self.cookies:
|
|
118
|
+
self.save_to_file()
|
|
119
|
+
|
|
120
|
+
def _matching_cookies(
|
|
121
|
+
self, path: str = "/", secure: bool = True, domain: str | None = None
|
|
122
|
+
) -> list[Cookie]:
|
|
123
|
+
request_domain = (domain or self.domain).lower()
|
|
124
|
+
return [c for c in self.cookies if c.match(request_domain, path, secure)]
|
|
125
|
+
|
|
126
|
+
def _ordered_matching_cookies(
|
|
127
|
+
self, path: str = "/", secure: bool = True, domain: str | None = None
|
|
128
|
+
) -> list[Cookie]:
|
|
129
|
+
return sorted(
|
|
130
|
+
self._matching_cookies(path=path, secure=secure, domain=domain),
|
|
131
|
+
key=lambda cookie: len(cookie.path),
|
|
132
|
+
reverse=True,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def to_dict(self) -> dict[str, str]:
|
|
136
|
+
"""将 cookies 字符串转换为字典"""
|
|
137
|
+
return self.get()
|
|
138
|
+
|
|
139
|
+
# ---------------- persistence ----------------
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def _normalize_cookie_newlines(cookies_str: str) -> str:
|
|
143
|
+
return cookies_str.replace("\r\n", "\n").replace("\r", "\n")
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def clean_cookies_str(cookies_str: str) -> str:
|
|
147
|
+
return CookieJar._normalize_cookie_newlines(cookies_str).strip(" \n")
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def _normalize_header_cookies_str(cookies_str: str) -> str:
|
|
151
|
+
return CookieJar.clean_cookies_str(cookies_str).replace("\n", "")
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def _is_netscape_cookie_file(cookies_str: str) -> bool:
|
|
155
|
+
valid_row_count = 0
|
|
156
|
+
for line in cookies_str.splitlines():
|
|
157
|
+
if line.strip().lower() == "# netscape http cookie file":
|
|
158
|
+
return True
|
|
159
|
+
if CookieJar._parse_netscape_cookie_line(line) is not None:
|
|
160
|
+
valid_row_count += 1
|
|
161
|
+
if valid_row_count >= 2:
|
|
162
|
+
return True
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def _parse_netscape_cookie_line(
|
|
167
|
+
line: str,
|
|
168
|
+
) -> tuple[str, str, str, str, int, str, str] | None:
|
|
169
|
+
if not line.strip():
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
line = line.lstrip()
|
|
173
|
+
if line.startswith("#") and not line.startswith("#HttpOnly_"):
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
if line.startswith("#HttpOnly_"):
|
|
177
|
+
line = line.removeprefix("#HttpOnly_")
|
|
178
|
+
|
|
179
|
+
parts = line.split("\t")
|
|
180
|
+
if len(parts) != 7:
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
domain, include_subdomains, path, secure, expires, name, value = parts
|
|
184
|
+
if not domain or not path or not name:
|
|
185
|
+
return None
|
|
186
|
+
if path[0] != "/":
|
|
187
|
+
return None
|
|
188
|
+
if any(sep in domain for sep in ("=", ";")):
|
|
189
|
+
return None
|
|
190
|
+
if any(char.isspace() for char in domain):
|
|
191
|
+
return None
|
|
192
|
+
if any(sep in name for sep in ("=", ";")):
|
|
193
|
+
return None
|
|
194
|
+
if any(char.isspace() for char in name):
|
|
195
|
+
return None
|
|
196
|
+
if include_subdomains.upper() not in {"TRUE", "FALSE"}:
|
|
197
|
+
return None
|
|
198
|
+
if secure.upper() not in {"TRUE", "FALSE"}:
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
expires_at = int(expires)
|
|
203
|
+
except ValueError:
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
return domain, include_subdomains, path, secure, expires_at, name, value
|
|
207
|
+
|
|
208
|
+
def _sync_cookies_str(self) -> None:
|
|
209
|
+
self.cookies_str = "; ".join(f"{c.name}={c.value}" for c in self.cookies)
|
|
210
|
+
|
|
211
|
+
def _load_from_cookies_str(self, cookies_str: str) -> None:
|
|
212
|
+
cookies_str = self.clean_cookies_str(cookies_str)
|
|
213
|
+
if not cookies_str:
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
if self._is_netscape_cookie_file(cookies_str):
|
|
217
|
+
self._load_from_netscape_cookies_str(cookies_str)
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
self._load_from_header_cookies_str(cookies_str)
|
|
221
|
+
|
|
222
|
+
def _load_from_header_cookies_str(self, cookies_str: str) -> None:
|
|
223
|
+
normalized = self._normalize_header_cookies_str(cookies_str)
|
|
224
|
+
|
|
225
|
+
for item in normalized.split(";"):
|
|
226
|
+
item = item.strip()
|
|
227
|
+
if not item or "=" not in item:
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
parts = item.split("=", 1)
|
|
231
|
+
if len(parts) != 2:
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
name, value = parts
|
|
235
|
+
if not name.strip():
|
|
236
|
+
continue
|
|
237
|
+
self.cookies.append(
|
|
238
|
+
Cookie(
|
|
239
|
+
domain=f".{self.domain}",
|
|
240
|
+
path="/",
|
|
241
|
+
name=name.strip(),
|
|
242
|
+
value=value.strip(),
|
|
243
|
+
secure=True,
|
|
244
|
+
expires=0,
|
|
245
|
+
)
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
self._sync_cookies_str()
|
|
249
|
+
|
|
250
|
+
def _load_from_netscape_cookies_str(self, cookies_str: str) -> None:
|
|
251
|
+
for line in cookies_str.splitlines():
|
|
252
|
+
parsed = self._parse_netscape_cookie_line(line)
|
|
253
|
+
if parsed is None:
|
|
254
|
+
continue
|
|
255
|
+
|
|
256
|
+
domain, include_subdomains, path, secure, expires_at, name, value = parsed
|
|
257
|
+
|
|
258
|
+
if include_subdomains.upper() == "TRUE":
|
|
259
|
+
if not domain.startswith("."):
|
|
260
|
+
domain = f".{domain}"
|
|
261
|
+
else:
|
|
262
|
+
domain = domain.lstrip(".")
|
|
263
|
+
|
|
264
|
+
self.cookies.append(
|
|
265
|
+
Cookie(
|
|
266
|
+
domain=domain,
|
|
267
|
+
path=path,
|
|
268
|
+
name=name,
|
|
269
|
+
value=value,
|
|
270
|
+
secure=secure.upper() == "TRUE",
|
|
271
|
+
expires=expires_at,
|
|
272
|
+
)
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
self._sync_cookies_str()
|
|
276
|
+
|
|
277
|
+
def save_to_file(self) -> None:
|
|
278
|
+
cj = cookiejar.MozillaCookieJar(self.cookie_file)
|
|
279
|
+
|
|
280
|
+
for c in self.cookies:
|
|
281
|
+
cj.set_cookie(
|
|
282
|
+
cookiejar.Cookie(
|
|
283
|
+
version=0,
|
|
284
|
+
name=c.name,
|
|
285
|
+
value=c.value,
|
|
286
|
+
port=None,
|
|
287
|
+
port_specified=False,
|
|
288
|
+
domain=c.domain,
|
|
289
|
+
domain_specified=True,
|
|
290
|
+
domain_initial_dot=c.domain.startswith("."),
|
|
291
|
+
path=c.path,
|
|
292
|
+
path_specified=True,
|
|
293
|
+
secure=c.secure,
|
|
294
|
+
expires=c.expires,
|
|
295
|
+
discard=c.expires == 0,
|
|
296
|
+
comment=None,
|
|
297
|
+
comment_url=None,
|
|
298
|
+
rest={"HttpOnly": ""},
|
|
299
|
+
rfc2109=False,
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
cj.save(ignore_discard=True, ignore_expires=True)
|
|
304
|
+
logger.debug(f"已保存 {len(cj)} 个 Cookie 到 {self.cookie_file}")
|
|
305
|
+
|
|
306
|
+
def load_from_file(self) -> None:
|
|
307
|
+
cj = cookiejar.MozillaCookieJar(self.cookie_file)
|
|
308
|
+
try:
|
|
309
|
+
cj.load(ignore_discard=True, ignore_expires=True)
|
|
310
|
+
except Exception:
|
|
311
|
+
logger.warning(f"加载 cookie 文件失败:{self.cookie_file}")
|
|
312
|
+
return
|
|
313
|
+
|
|
314
|
+
self.cookies = []
|
|
315
|
+
for c in cj:
|
|
316
|
+
self.cookies.append(
|
|
317
|
+
Cookie(
|
|
318
|
+
domain=c.domain,
|
|
319
|
+
path=c.path,
|
|
320
|
+
name=c.name,
|
|
321
|
+
value=c.value or "",
|
|
322
|
+
secure=c.secure,
|
|
323
|
+
expires=c.expires or 0,
|
|
324
|
+
)
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
self._sync_cookies_str()
|
|
328
|
+
logger.debug(f"从文件加载 {len(self.cookies)} 个 Cookie")
|
|
329
|
+
|
|
330
|
+
# ---------------- update from response ----------------
|
|
331
|
+
|
|
332
|
+
def update_from_response(self, set_cookie_headers: list[str]) -> None:
|
|
333
|
+
if not set_cookie_headers:
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
logger.debug(
|
|
337
|
+
f"开始更新 cookies,收到 {len(set_cookie_headers)} 个 Set-Cookie 头"
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
updated = False
|
|
341
|
+
updated_items = []
|
|
342
|
+
added_items = []
|
|
343
|
+
ignored_items = []
|
|
344
|
+
|
|
345
|
+
for header in set_cookie_headers:
|
|
346
|
+
logger.debug(f"解析 Set-Cookie: {header}")
|
|
347
|
+
|
|
348
|
+
sc = SimpleCookie()
|
|
349
|
+
sc.load(header)
|
|
350
|
+
|
|
351
|
+
if not sc:
|
|
352
|
+
logger.debug("解析结果为空,跳过该 header")
|
|
353
|
+
continue
|
|
354
|
+
|
|
355
|
+
for name, morsel in sc.items():
|
|
356
|
+
value = morsel.value
|
|
357
|
+
path = morsel["path"] or "/"
|
|
358
|
+
domain = morsel["domain"] or f".{self.domain}"
|
|
359
|
+
secure = bool(morsel["secure"])
|
|
360
|
+
|
|
361
|
+
expires = 0
|
|
362
|
+
if morsel["expires"]:
|
|
363
|
+
try:
|
|
364
|
+
expires = int(
|
|
365
|
+
time.mktime(
|
|
366
|
+
time.strptime(
|
|
367
|
+
morsel["expires"], "%a, %d-%b-%Y %H:%M:%S %Z"
|
|
368
|
+
)
|
|
369
|
+
)
|
|
370
|
+
)
|
|
371
|
+
except Exception as e:
|
|
372
|
+
logger.debug(
|
|
373
|
+
f"解析 expires 失败: {morsel['expires']},错误: {e}"
|
|
374
|
+
)
|
|
375
|
+
expires = 0
|
|
376
|
+
|
|
377
|
+
existing = next(
|
|
378
|
+
(
|
|
379
|
+
c
|
|
380
|
+
for c in self.cookies
|
|
381
|
+
if c.name == name and c.domain == domain and c.path == path
|
|
382
|
+
),
|
|
383
|
+
None,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
if existing:
|
|
387
|
+
# 如果值完全一样,仍然记录但标记为“未变更”
|
|
388
|
+
if (
|
|
389
|
+
existing.value == value
|
|
390
|
+
and existing.secure == secure
|
|
391
|
+
and existing.expires == expires
|
|
392
|
+
):
|
|
393
|
+
ignored_items.append((name, domain, path))
|
|
394
|
+
logger.debug(
|
|
395
|
+
f"Cookie 未变更,忽略: {name} (domain={domain}, path={path})"
|
|
396
|
+
)
|
|
397
|
+
continue
|
|
398
|
+
|
|
399
|
+
old_value = existing.value
|
|
400
|
+
existing.value = value
|
|
401
|
+
existing.secure = secure
|
|
402
|
+
existing.expires = expires
|
|
403
|
+
|
|
404
|
+
updated_items.append(
|
|
405
|
+
(name, domain, path, old_value, value, secure, expires)
|
|
406
|
+
)
|
|
407
|
+
logger.debug(
|
|
408
|
+
f"Cookie 更新: {name} (domain={domain}, path={path}) "
|
|
409
|
+
f"old_value={old_value} new_value={value} secure={secure} expires={expires}"
|
|
410
|
+
)
|
|
411
|
+
else:
|
|
412
|
+
self.cookies.append(
|
|
413
|
+
Cookie(
|
|
414
|
+
domain=domain,
|
|
415
|
+
path=path,
|
|
416
|
+
name=name,
|
|
417
|
+
value=value,
|
|
418
|
+
secure=secure,
|
|
419
|
+
expires=expires,
|
|
420
|
+
)
|
|
421
|
+
)
|
|
422
|
+
added_items.append((name, domain, path, value, secure, expires))
|
|
423
|
+
logger.debug(
|
|
424
|
+
f"Cookie 新增: {name} (domain={domain}, path={path}) "
|
|
425
|
+
f"value={value} secure={secure} expires={expires}"
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
updated = True
|
|
429
|
+
|
|
430
|
+
if updated:
|
|
431
|
+
self.purge_expired()
|
|
432
|
+
self.save_to_file()
|
|
433
|
+
logger.debug(
|
|
434
|
+
"Cookies 已更新并保存 "
|
|
435
|
+
f"(新增 {len(added_items)},更新 {len(updated_items)},忽略 {len(ignored_items)})"
|
|
436
|
+
)
|
|
437
|
+
logger.debug(f"当前 Cookie 总数: {len(self.cookies)}")
|
|
438
|
+
logger.debug(f"当前 cookies_str: {self.cookies_str}")
|