cnks 0.3.1__py3-none-any.whl → 0.3.2__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.
- {cnks-0.3.1.dist-info → cnks-0.3.2.dist-info}/METADATA +1 -1
- {cnks-0.3.1.dist-info → cnks-0.3.2.dist-info}/RECORD +8 -8
- src/citzer.py +45 -136
- src/searcher.py +42 -155
- src/server.py +20 -170
- src/worker.py +29 -65
- {cnks-0.3.1.dist-info → cnks-0.3.2.dist-info}/WHEEL +0 -0
- {cnks-0.3.1.dist-info → cnks-0.3.2.dist-info}/entry_points.txt +0 -0
src/server.py
CHANGED
@@ -9,60 +9,16 @@
|
|
9
9
|
|
10
10
|
Exposed Tools:
|
11
11
|
- search_keyword: 搜索指定关键词并获取相关引用
|
12
|
-
- process_link: 处理特定链接并提取引用信息
|
13
|
-
- close_browser: 关闭浏览器实例
|
14
12
|
"""
|
15
13
|
|
16
14
|
import asyncio
|
17
15
|
import json
|
18
|
-
import logging
|
19
16
|
import os
|
20
17
|
import sys
|
21
18
|
import time
|
22
|
-
import traceback
|
23
19
|
import uuid
|
24
20
|
from typing import Dict, List, Any, Optional
|
25
21
|
|
26
|
-
# 配置日志记录
|
27
|
-
try:
|
28
|
-
# 尝试使用绝对路径
|
29
|
-
log_dir = os.path.dirname(os.path.abspath(__file__))
|
30
|
-
log_file = os.path.join(os.path.dirname(log_dir), "cnks_server.log")
|
31
|
-
|
32
|
-
# 创建处理器
|
33
|
-
file_handler = logging.FileHandler(log_file, mode="a")
|
34
|
-
console_handler = logging.StreamHandler()
|
35
|
-
|
36
|
-
# 设置格式
|
37
|
-
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
38
|
-
file_handler.setFormatter(formatter)
|
39
|
-
console_handler.setFormatter(formatter)
|
40
|
-
|
41
|
-
# 获取日志记录器并添加处理器
|
42
|
-
logger = logging.getLogger("cnks.server")
|
43
|
-
logger.setLevel(logging.DEBUG)
|
44
|
-
|
45
|
-
# 移除现有处理器以避免重复
|
46
|
-
if logger.handlers:
|
47
|
-
for handler in logger.handlers:
|
48
|
-
logger.removeHandler(handler)
|
49
|
-
|
50
|
-
logger.addHandler(file_handler)
|
51
|
-
logger.addHandler(console_handler)
|
52
|
-
|
53
|
-
# 打印确认信息
|
54
|
-
print(f"Server logger initialized, logging to: {log_file}")
|
55
|
-
logger.info(f"Server logging to: {log_file}")
|
56
|
-
except Exception as e:
|
57
|
-
# 回退到基本控制台日志记录
|
58
|
-
logging.basicConfig(
|
59
|
-
level=logging.DEBUG,
|
60
|
-
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
61
|
-
)
|
62
|
-
logger = logging.getLogger("cnks.server")
|
63
|
-
logger.error(f"Failed to set up file logging: {str(e)}")
|
64
|
-
print(f"Error setting up server file logging: {str(e)}")
|
65
|
-
|
66
22
|
# 导入MCP服务器模块
|
67
23
|
try:
|
68
24
|
from mcp.server.models import InitializationOptions
|
@@ -72,14 +28,14 @@ try:
|
|
72
28
|
MCP_AVAILABLE = True
|
73
29
|
except ImportError:
|
74
30
|
MCP_AVAILABLE = False
|
75
|
-
|
31
|
+
print("MCP不可用。请安装: pip install mcp-py")
|
76
32
|
|
77
33
|
# 尝试导入dotenv支持环境变量
|
78
34
|
try:
|
79
35
|
from dotenv import load_dotenv
|
80
36
|
load_dotenv()
|
81
37
|
except ImportError:
|
82
|
-
|
38
|
+
pass
|
83
39
|
|
84
40
|
# 导入Worker模块
|
85
41
|
try:
|
@@ -88,8 +44,8 @@ except ImportError:
|
|
88
44
|
try:
|
89
45
|
from worker import Worker
|
90
46
|
except ImportError:
|
91
|
-
|
92
|
-
raise ImportError("Worker
|
47
|
+
print("Worker模块不可用")
|
48
|
+
raise ImportError("Worker模块不可用")
|
93
49
|
|
94
50
|
# 初始化MCP服务器
|
95
51
|
server = Server("CNKS Server")
|
@@ -149,25 +105,6 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
149
105
|
},
|
150
106
|
"required": ["keyword"]
|
151
107
|
}
|
152
|
-
),
|
153
|
-
types.Tool(
|
154
|
-
name="process_link",
|
155
|
-
description="处理特定链接并提取引用信息",
|
156
|
-
inputSchema={
|
157
|
-
"type": "object",
|
158
|
-
"properties": {
|
159
|
-
"link": {"type": "string", "description": "要处理的文章链接"}
|
160
|
-
},
|
161
|
-
"required": ["link"]
|
162
|
-
}
|
163
|
-
),
|
164
|
-
types.Tool(
|
165
|
-
name="close_browser",
|
166
|
-
description="关闭浏览器资源",
|
167
|
-
inputSchema={
|
168
|
-
"type": "object",
|
169
|
-
"properties": {}
|
170
|
-
}
|
171
108
|
)
|
172
109
|
]
|
173
110
|
|
@@ -189,7 +126,7 @@ class SearchKeywordToolHandler(ToolHandler):
|
|
189
126
|
return [types.TextContent(type="text", text="错误: 关键词不能为空")]
|
190
127
|
|
191
128
|
message_id = str(uuid.uuid4())
|
192
|
-
|
129
|
+
print(f"开始处理关键词: {keyword}")
|
193
130
|
|
194
131
|
try:
|
195
132
|
# 记录请求
|
@@ -210,8 +147,7 @@ class SearchKeywordToolHandler(ToolHandler):
|
|
210
147
|
|
211
148
|
except Exception as e:
|
212
149
|
error_msg = f"处理关键词 '{keyword}' 时出错: {str(e)}"
|
213
|
-
|
214
|
-
logger.error(traceback.format_exc())
|
150
|
+
print(error_msg)
|
215
151
|
|
216
152
|
# 更新请求状态
|
217
153
|
active_requests[message_id]["status"] = "error"
|
@@ -219,61 +155,9 @@ class SearchKeywordToolHandler(ToolHandler):
|
|
219
155
|
|
220
156
|
return [types.TextContent(type="text", text=f"错误: {error_msg}")]
|
221
157
|
|
222
|
-
class ProcessLinkToolHandler(ToolHandler):
|
223
|
-
async def handle(self, name: str, arguments: dict | None) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
224
|
-
link = arguments.get("link", "")
|
225
|
-
|
226
|
-
if not link:
|
227
|
-
return [types.TextContent(type="text", text="错误: 链接不能为空")]
|
228
|
-
|
229
|
-
message_id = str(uuid.uuid4())
|
230
|
-
logger.info(f"开始处理链接: {link}")
|
231
|
-
|
232
|
-
try:
|
233
|
-
# 记录请求
|
234
|
-
active_requests[message_id] = {
|
235
|
-
"link": link,
|
236
|
-
"status": "processing",
|
237
|
-
"timestamp": time.time()
|
238
|
-
}
|
239
|
-
|
240
|
-
# 调用Worker API处理链接
|
241
|
-
result = await worker_instance.citzer.process_link(link)
|
242
|
-
logger.info(f"链接 '{link}' 处理完成")
|
243
|
-
|
244
|
-
# 更新请求状态
|
245
|
-
active_requests[message_id]["status"] = "completed"
|
246
|
-
|
247
|
-
# 返回结果
|
248
|
-
return [types.TextContent(type="text", text=json.dumps(result, ensure_ascii=False, indent=2))]
|
249
|
-
|
250
|
-
except Exception as e:
|
251
|
-
error_msg = f"处理链接 '{link}' 时出错: {str(e)}"
|
252
|
-
logger.error(error_msg)
|
253
|
-
logger.error(traceback.format_exc())
|
254
|
-
|
255
|
-
# 更新请求状态
|
256
|
-
active_requests[message_id]["status"] = "error"
|
257
|
-
active_requests[message_id]["error"] = str(e)
|
258
|
-
|
259
|
-
return [types.TextContent(type="text", text=f"错误: {error_msg}")]
|
260
|
-
|
261
|
-
class CloseBrowserToolHandler(ToolHandler):
|
262
|
-
async def handle(self, name: str, arguments: dict | None) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
263
|
-
try:
|
264
|
-
await worker_instance.close()
|
265
|
-
logger.info("浏览器资源已关闭")
|
266
|
-
return [types.TextContent(type="text", text="浏览器资源已成功关闭")]
|
267
|
-
except Exception as e:
|
268
|
-
error_msg = f"关闭浏览器失败: {str(e)}"
|
269
|
-
logger.error(error_msg)
|
270
|
-
return [types.TextContent(type="text", text=f"错误: {error_msg}")]
|
271
|
-
|
272
158
|
# 工具处理器映射表
|
273
159
|
tool_handlers = {
|
274
|
-
"search_keyword": SearchKeywordToolHandler()
|
275
|
-
"process_link": ProcessLinkToolHandler(),
|
276
|
-
"close_browser": CloseBrowserToolHandler()
|
160
|
+
"search_keyword": SearchKeywordToolHandler()
|
277
161
|
}
|
278
162
|
|
279
163
|
@server.call_tool()
|
@@ -290,12 +174,12 @@ async def handle_call_tool(
|
|
290
174
|
Returns:
|
291
175
|
list: 包含文本或图像内容的响应
|
292
176
|
"""
|
293
|
-
|
177
|
+
print(f"收到工具调用请求: {name}, 参数: {arguments}")
|
294
178
|
|
295
179
|
if name in tool_handlers:
|
296
180
|
return await tool_handlers[name].handle(name, arguments)
|
297
181
|
else:
|
298
|
-
|
182
|
+
print(f"未知工具: {name}")
|
299
183
|
return [types.TextContent(type="text", text=f"错误: 未知工具: {name}")]
|
300
184
|
|
301
185
|
def cleanup_expired_requests():
|
@@ -311,7 +195,7 @@ def cleanup_expired_requests():
|
|
311
195
|
# 移除过期请求
|
312
196
|
for msg_id in expired_ids:
|
313
197
|
active_requests.pop(msg_id, None)
|
314
|
-
|
198
|
+
print(f"已清理过期请求: {msg_id}")
|
315
199
|
|
316
200
|
async def handle_simple_request(reader, writer):
|
317
201
|
"""
|
@@ -323,11 +207,11 @@ async def handle_simple_request(reader, writer):
|
|
323
207
|
# 读取请求
|
324
208
|
request_line = await reader.readline()
|
325
209
|
if not request_line:
|
326
|
-
|
210
|
+
print("收到空请求")
|
327
211
|
return
|
328
212
|
|
329
213
|
request_data = json.loads(request_line.decode('utf-8'))
|
330
|
-
|
214
|
+
print(f"收到请求: {request_data}")
|
331
215
|
|
332
216
|
# 处理请求
|
333
217
|
if request_data.get("type") == "tool_call":
|
@@ -363,35 +247,6 @@ async def handle_simple_request(reader, writer):
|
|
363
247
|
response = {"status": "error", "message": "处理器未返回结果"}
|
364
248
|
else:
|
365
249
|
response = {"status": "error", "message": "找不到工具处理器"}
|
366
|
-
elif tool_name == "process_link":
|
367
|
-
# 获取参数
|
368
|
-
link = params.get("link", "")
|
369
|
-
|
370
|
-
if not link:
|
371
|
-
response = {"status": "error", "message": "链接不能为空"}
|
372
|
-
else:
|
373
|
-
# 调用处理器
|
374
|
-
handler = tool_handlers.get("process_link")
|
375
|
-
|
376
|
-
if handler:
|
377
|
-
result_content = await handler.handle("process_link", params)
|
378
|
-
if result_content and len(result_content) > 0:
|
379
|
-
# 从TextContent提取JSON字符串并解析
|
380
|
-
try:
|
381
|
-
result_data = json.loads(result_content[0].text)
|
382
|
-
response = {
|
383
|
-
"status": "success",
|
384
|
-
"result": result_data
|
385
|
-
}
|
386
|
-
except json.JSONDecodeError:
|
387
|
-
response = {
|
388
|
-
"status": "success",
|
389
|
-
"result": {"message": result_content[0].text}
|
390
|
-
}
|
391
|
-
else:
|
392
|
-
response = {"status": "error", "message": "处理器未返回结果"}
|
393
|
-
else:
|
394
|
-
response = {"status": "error", "message": "找不到工具处理器"}
|
395
250
|
else:
|
396
251
|
response = {"status": "error", "message": f"未知工具: {tool_name}"}
|
397
252
|
else:
|
@@ -402,12 +257,11 @@ async def handle_simple_request(reader, writer):
|
|
402
257
|
await writer.drain()
|
403
258
|
|
404
259
|
except json.JSONDecodeError:
|
405
|
-
|
260
|
+
print("无法解析JSON请求")
|
406
261
|
writer.write(json.dumps({"status": "error", "message": "无法解析JSON请求"}).encode('utf-8') + b'\n')
|
407
262
|
await writer.drain()
|
408
263
|
except Exception as e:
|
409
|
-
|
410
|
-
logger.error(traceback.format_exc())
|
264
|
+
print(f"处理请求时出错: {str(e)}")
|
411
265
|
writer.write(json.dumps({"status": "error", "message": f"服务器错误: {str(e)}"}).encode('utf-8') + b'\n')
|
412
266
|
await writer.drain()
|
413
267
|
finally:
|
@@ -426,7 +280,7 @@ async def run_simple_server():
|
|
426
280
|
)
|
427
281
|
|
428
282
|
addr = server.sockets[0].getsockname()
|
429
|
-
|
283
|
+
print(f'简易服务器运行在 {addr}')
|
430
284
|
|
431
285
|
async with server:
|
432
286
|
await server.serve_forever()
|
@@ -437,22 +291,21 @@ async def main():
|
|
437
291
|
"""
|
438
292
|
try:
|
439
293
|
print("正在启动CNKS服务器...")
|
440
|
-
logger.info("正在启动CNKS服务器")
|
441
294
|
|
442
295
|
# 检查MCP是否可用
|
443
296
|
if not MCP_AVAILABLE:
|
444
|
-
|
297
|
+
print("MCP模块不可用,使用简单服务器替代")
|
445
298
|
await run_simple_server()
|
446
299
|
return
|
447
300
|
|
448
301
|
# 检查是否从命令行直接运行或被导入
|
449
302
|
if sys.stdin.isatty():
|
450
303
|
# 命令行运行,启动简单服务器
|
451
|
-
|
304
|
+
print("从命令行运行,启动简单服务器")
|
452
305
|
await run_simple_server()
|
453
306
|
else:
|
454
307
|
# 标准输入/输出流可用,使用stdio模式运行MCP服务器
|
455
|
-
|
308
|
+
print("检测到标准输入/输出流,启动MCP标准服务器")
|
456
309
|
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
457
310
|
await server.run(
|
458
311
|
read_stream,
|
@@ -469,18 +322,15 @@ async def main():
|
|
469
322
|
|
470
323
|
except KeyboardInterrupt:
|
471
324
|
print("\n收到中断信号,服务器正在关闭...")
|
472
|
-
logger.info("收到中断信号,服务器正在关闭")
|
473
325
|
except Exception as e:
|
474
|
-
logger.error(f"服务器启动失败: {str(e)}")
|
475
|
-
logger.error(traceback.format_exc())
|
476
326
|
print(f"服务器启动失败: {str(e)}")
|
477
327
|
finally:
|
478
328
|
# 关闭Worker资源
|
479
329
|
try:
|
480
330
|
await worker_instance.close()
|
481
|
-
|
331
|
+
print("Worker资源已关闭")
|
482
332
|
except Exception as e:
|
483
|
-
|
333
|
+
print(f"关闭Worker资源时出错: {str(e)}")
|
484
334
|
|
485
335
|
if __name__ == "__main__":
|
486
336
|
# 启动主循环
|
src/worker.py
CHANGED
@@ -16,51 +16,41 @@
|
|
16
16
|
|
17
17
|
import asyncio
|
18
18
|
import json
|
19
|
-
import logging
|
20
19
|
import os
|
21
20
|
import traceback
|
22
21
|
import time
|
23
22
|
from typing import Dict, List, Any, Optional, Union
|
24
23
|
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
# 禁用日志记录
|
25
|
+
class DummyLogger:
|
26
|
+
"""空日志记录器,用于禁用日志输出"""
|
27
|
+
def __init__(self, *args, **kwargs):
|
28
|
+
pass
|
29
|
+
|
30
|
+
def debug(self, *args, **kwargs):
|
31
|
+
pass
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
console_handler = logging.StreamHandler()
|
33
|
+
def info(self, *args, **kwargs):
|
34
|
+
pass
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
file_handler.setFormatter(formatter)
|
38
|
-
console_handler.setFormatter(formatter)
|
36
|
+
def warning(self, *args, **kwargs):
|
37
|
+
pass
|
39
38
|
|
40
|
-
|
41
|
-
|
42
|
-
logger.setLevel(logging.DEBUG)
|
39
|
+
def error(self, *args, **kwargs):
|
40
|
+
pass
|
43
41
|
|
44
|
-
|
45
|
-
|
46
|
-
for handler in logger.handlers:
|
47
|
-
logger.removeHandler(handler)
|
42
|
+
def critical(self, *args, **kwargs):
|
43
|
+
pass
|
48
44
|
|
49
|
-
|
50
|
-
|
45
|
+
def addHandler(self, *args, **kwargs):
|
46
|
+
pass
|
51
47
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
level=logging.DEBUG,
|
59
|
-
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
60
|
-
)
|
61
|
-
logger = logging.getLogger("cnks.worker")
|
62
|
-
logger.error(f"Failed to set up file logging: {str(e)}")
|
63
|
-
print(f"Error setting up worker file logging: {str(e)}")
|
48
|
+
def setLevel(self, *args, **kwargs):
|
49
|
+
pass
|
50
|
+
|
51
|
+
# 使用空日志记录器
|
52
|
+
logger = DummyLogger()
|
53
|
+
print = lambda *args, **kwargs: None # 禁用print函数
|
64
54
|
|
65
55
|
# 导入其他模块
|
66
56
|
try:
|
@@ -73,7 +63,7 @@ except ImportError:
|
|
73
63
|
from citzer import Citzer
|
74
64
|
from cache import Cache
|
75
65
|
except ImportError:
|
76
|
-
|
66
|
+
pass
|
77
67
|
|
78
68
|
class Worker:
|
79
69
|
"""
|
@@ -90,7 +80,6 @@ class Worker:
|
|
90
80
|
self.citzer.browser_started = False
|
91
81
|
# 创建Cache实例
|
92
82
|
self.cache = Cache()
|
93
|
-
logger.info("Worker初始化完成")
|
94
83
|
|
95
84
|
async def process_keyword(self, keyword: str) -> Dict[str, Any]:
|
96
85
|
"""
|
@@ -102,29 +91,20 @@ class Worker:
|
|
102
91
|
Returns:
|
103
92
|
Dict[str, Any]: 包含处理结果的字典
|
104
93
|
"""
|
105
|
-
logger.info(f"处理关键词请求: {keyword}")
|
106
|
-
|
107
94
|
try:
|
108
95
|
# 检查缓存中是否有该关键词
|
109
96
|
if not self.cache.has_keyword(keyword):
|
110
|
-
logger.info(f"缓存中没有关键词 {keyword},执行搜索")
|
111
|
-
|
112
97
|
# 使用searcher搜索关键词
|
113
98
|
links = await self.searcher.search_keyword(keyword)
|
114
|
-
logger.info(f"搜索到 {len(links)} 个链接")
|
115
99
|
|
116
100
|
# 将结果存入缓存
|
117
101
|
self.cache.add_links(keyword, links)
|
118
|
-
logger.info(f"已将关键词 {keyword} 的链接存入缓存")
|
119
|
-
else:
|
120
|
-
logger.info(f"缓存中已有关键词 {keyword}")
|
121
102
|
|
122
103
|
# 将Searcher的浏览器实例共享给Citzer
|
123
104
|
if self.searcher.browser_started and not self.citzer.browser_started:
|
124
105
|
self.citzer.context = self.searcher.context
|
125
106
|
self.citzer.playwright = self.searcher.playwright
|
126
107
|
self.citzer.browser_started = True
|
127
|
-
logger.info("已将Searcher的浏览器实例共享给Citzer")
|
128
108
|
|
129
109
|
# 处理缓存中未处理的链接
|
130
110
|
while True:
|
@@ -132,26 +112,20 @@ class Worker:
|
|
132
112
|
link = self.cache.get_unprocessed_link(keyword)
|
133
113
|
|
134
114
|
if not link:
|
135
|
-
logger.info(f"关键词 {keyword} 的所有链接已处理完毕")
|
136
115
|
break
|
137
116
|
|
138
|
-
logger.info(f"处理链接: {link}")
|
139
|
-
|
140
117
|
# 使用citzer处理链接
|
141
118
|
result = await self.citzer.process_link(link)
|
142
119
|
|
143
120
|
if result:
|
144
121
|
# 将结果存入缓存
|
145
122
|
self.cache.add_result(link, result)
|
146
|
-
logger.info(f"已将链接 {link} 的处理结果存入缓存")
|
147
123
|
|
148
124
|
# 标记链接为已处理
|
149
125
|
self.cache.mark_as_processed(link)
|
150
|
-
logger.info(f"已标记链接 {link} 为已处理")
|
151
126
|
|
152
127
|
# 获取所有处理结果
|
153
128
|
results = self.cache.get_all_results(keyword)
|
154
|
-
logger.info(f"关键词 {keyword} 的处理结果数量: {len(results)}")
|
155
129
|
|
156
130
|
return {
|
157
131
|
"success": True,
|
@@ -160,9 +134,6 @@ class Worker:
|
|
160
134
|
}
|
161
135
|
|
162
136
|
except Exception as e:
|
163
|
-
logger.error(f"处理关键词 {keyword} 时出错: {str(e)}")
|
164
|
-
logger.error(traceback.format_exc())
|
165
|
-
|
166
137
|
return {
|
167
138
|
"success": False,
|
168
139
|
"keyword": keyword,
|
@@ -176,8 +147,8 @@ class Worker:
|
|
176
147
|
self.citzer.browser_started = False
|
177
148
|
# 关闭Searcher的浏览器
|
178
149
|
await self.searcher.close_browser()
|
179
|
-
except Exception
|
180
|
-
|
150
|
+
except Exception:
|
151
|
+
pass
|
181
152
|
|
182
153
|
async def close(self):
|
183
154
|
"""关闭工作者资源"""
|
@@ -189,10 +160,8 @@ class Worker:
|
|
189
160
|
|
190
161
|
# 关闭Searcher的浏览器
|
191
162
|
await self.searcher.close_browser()
|
192
|
-
|
193
|
-
|
194
|
-
logger.error(f"关闭工作者资源时出错: {str(e)}")
|
195
|
-
logger.error(traceback.format_exc())
|
163
|
+
except Exception:
|
164
|
+
pass
|
196
165
|
|
197
166
|
# 如果作为主程序运行,提供测试功能
|
198
167
|
async def main():
|
@@ -202,14 +171,9 @@ async def main():
|
|
202
171
|
try:
|
203
172
|
# 测试关键词
|
204
173
|
test_keyword = "人工智能"
|
205
|
-
print(f"测试处理关键词: {test_keyword}")
|
206
174
|
|
207
175
|
# 处理关键词
|
208
176
|
result = await worker.process_keyword(test_keyword)
|
209
|
-
|
210
|
-
# 打印结果
|
211
|
-
print(f"处理结果:")
|
212
|
-
print(json.dumps(result, ensure_ascii=False, indent=2))
|
213
177
|
finally:
|
214
178
|
# 关闭资源
|
215
179
|
await worker.close()
|
File without changes
|
File without changes
|