mcp-query-table 0.2.6__py3-none-any.whl → 0.3.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.
- mcp_query_table/__init__.py +2 -2
- mcp_query_table/_version.py +1 -1
- mcp_query_table/enums.py +7 -0
- mcp_query_table/providers/__init__.py +0 -0
- mcp_query_table/providers/baidu.py +82 -0
- mcp_query_table/providers/n.py +77 -0
- mcp_query_table/providers/yuanbao.py +82 -0
- mcp_query_table/server.py +18 -2
- mcp_query_table/tool.py +78 -28
- {mcp_query_table-0.2.6.dist-info → mcp_query_table-0.3.0.dist-info}/METADATA +36 -23
- mcp_query_table-0.3.0.dist-info/RECORD +19 -0
- mcp_query_table-0.2.6.dist-info/RECORD +0 -15
- {mcp_query_table-0.2.6.dist-info → mcp_query_table-0.3.0.dist-info}/WHEEL +0 -0
- {mcp_query_table-0.2.6.dist-info → mcp_query_table-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_query_table-0.2.6.dist-info → mcp_query_table-0.3.0.dist-info}/top_level.txt +0 -0
mcp_query_table/__init__.py
CHANGED
mcp_query_table/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.3.0"
|
mcp_query_table/enums.py
CHANGED
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
百度AI搜索
|
|
3
|
+
|
|
4
|
+
限制了输入长度为5000,很多时候会被截断,导致MCP无法正常工作
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
from playwright.async_api import Page
|
|
9
|
+
|
|
10
|
+
from mcp_query_table.tool import GlobalVars
|
|
11
|
+
|
|
12
|
+
_PAGE0_ = "https://chat.baidu.com/search"
|
|
13
|
+
_PAGE1_ = "https://chat.baidu.com/aichat/api/conversation"
|
|
14
|
+
_TIMEOUT_ = 1000 * 120
|
|
15
|
+
|
|
16
|
+
G = GlobalVars()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def read_event_stream(text):
|
|
20
|
+
text1 = []
|
|
21
|
+
text2 = []
|
|
22
|
+
for event in text.split('\n\n'):
|
|
23
|
+
if '"component":"thinkingSteps"' in event:
|
|
24
|
+
if '"reasoningContent":' not in event:
|
|
25
|
+
continue
|
|
26
|
+
lines = event.split('\n')
|
|
27
|
+
for line in lines:
|
|
28
|
+
if line.startswith('data:'):
|
|
29
|
+
t = line[5:]
|
|
30
|
+
t = json.loads(t)['data']['message']['content']['generator']['data']['reasoningContent']
|
|
31
|
+
text1.append(t)
|
|
32
|
+
if '"component":"markdown-yiyan"' in event:
|
|
33
|
+
lines = event.split('\n')
|
|
34
|
+
for line in lines:
|
|
35
|
+
if line.startswith('data:'):
|
|
36
|
+
t = line[5:]
|
|
37
|
+
t = json.loads(t)['data']['message']['content']['generator']['data']['value']
|
|
38
|
+
text2.append(t)
|
|
39
|
+
|
|
40
|
+
text2 = ''.join(text2)
|
|
41
|
+
if len(text1) == 0:
|
|
42
|
+
return text2
|
|
43
|
+
else:
|
|
44
|
+
text1 = ''.join(text1)
|
|
45
|
+
return f"<thinking>{text1}</thinking>\n\n{text2}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def on_response(response):
|
|
49
|
+
if response.url.startswith(_PAGE1_):
|
|
50
|
+
# print("on_response", response.url)
|
|
51
|
+
text = await response.text()
|
|
52
|
+
G.set_text(read_event_stream(text))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def on_route(route):
|
|
56
|
+
# 避免出现 Protocol error (Network.getResponseBody): No data found for resource with given identifier
|
|
57
|
+
# print("on_route", route.request.url)
|
|
58
|
+
if route.request.url.startswith(_PAGE1_):
|
|
59
|
+
# TODO 为何只要转发一下就没事了?
|
|
60
|
+
response = await route.fetch(timeout=_TIMEOUT_)
|
|
61
|
+
await route.fulfill(response=response)
|
|
62
|
+
else:
|
|
63
|
+
await route.continue_()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def chat(page: Page,
|
|
67
|
+
prompt: str,
|
|
68
|
+
create: bool,
|
|
69
|
+
) -> str:
|
|
70
|
+
if not page.url.startswith(_PAGE0_):
|
|
71
|
+
create = True
|
|
72
|
+
|
|
73
|
+
if create:
|
|
74
|
+
await page.goto(_PAGE0_)
|
|
75
|
+
|
|
76
|
+
await page.route(_PAGE1_, on_route)
|
|
77
|
+
async with page.expect_response(_PAGE1_, timeout=_TIMEOUT_) as response_info:
|
|
78
|
+
await page.locator("#chat-input-box").fill(prompt)
|
|
79
|
+
await page.locator("#chat-input-box").press("Enter")
|
|
80
|
+
await on_response(await response_info.value)
|
|
81
|
+
|
|
82
|
+
return G.get_text()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
360 纳米搜索
|
|
3
|
+
"""
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from playwright.async_api import Page
|
|
7
|
+
|
|
8
|
+
from mcp_query_table.tool import GlobalVars
|
|
9
|
+
|
|
10
|
+
_PAGE0_ = "https://www.n.cn"
|
|
11
|
+
_PAGE1_ = "https://www.n.cn/search"
|
|
12
|
+
_PAGE2_ = "https://www.n.cn/api/common/chat/v2"
|
|
13
|
+
_TIMEOUT_ = 1000 * 120
|
|
14
|
+
|
|
15
|
+
G = GlobalVars()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def read_event_stream(text):
|
|
19
|
+
text1 = []
|
|
20
|
+
text2 = []
|
|
21
|
+
for event in text.split('\n\n'):
|
|
22
|
+
if "event: 102" in event:
|
|
23
|
+
if 'data: {"type":"reasoning_text"' in event:
|
|
24
|
+
lines = event.split('\n')
|
|
25
|
+
for line in lines:
|
|
26
|
+
if line.startswith('data: '):
|
|
27
|
+
t = line[6:]
|
|
28
|
+
t = json.loads(t)['message']
|
|
29
|
+
text1.append(t)
|
|
30
|
+
if "event: 200" in event:
|
|
31
|
+
lines = event.split('\n')
|
|
32
|
+
for line in lines:
|
|
33
|
+
if line.startswith('data: '):
|
|
34
|
+
t = line[6:]
|
|
35
|
+
if t == '':
|
|
36
|
+
text2.append('\n')
|
|
37
|
+
elif t == ' ':
|
|
38
|
+
text2.append('\n')
|
|
39
|
+
else:
|
|
40
|
+
text2.append(t)
|
|
41
|
+
|
|
42
|
+
text2 = ''.join(text2)
|
|
43
|
+
if len(text1) == 0:
|
|
44
|
+
return text2
|
|
45
|
+
else:
|
|
46
|
+
text1 = ''.join(text1)
|
|
47
|
+
return f"<thinking>{text1}</thinking>\n\n{text2}"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def on_response(response):
|
|
51
|
+
if response == _PAGE2_:
|
|
52
|
+
# print("on_response", response.url)
|
|
53
|
+
text = await response.text()
|
|
54
|
+
G.set_text(read_event_stream(text))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def chat(page: Page,
|
|
58
|
+
prompt: str,
|
|
59
|
+
create: bool,
|
|
60
|
+
) -> str:
|
|
61
|
+
if not create:
|
|
62
|
+
if not page.url.startswith(_PAGE1_):
|
|
63
|
+
create = True
|
|
64
|
+
|
|
65
|
+
if create:
|
|
66
|
+
await page.goto(_PAGE0_)
|
|
67
|
+
name = "输入任何问题"
|
|
68
|
+
else:
|
|
69
|
+
name = "提出后续问题,Enter发送,Shift+Enter 换行"
|
|
70
|
+
|
|
71
|
+
async with page.expect_response(_PAGE2_, timeout=_TIMEOUT_) as response_info:
|
|
72
|
+
textbox = page.get_by_role("textbox", name=name)
|
|
73
|
+
await textbox.fill(prompt)
|
|
74
|
+
await textbox.press("Enter")
|
|
75
|
+
await on_response(await response_info.value)
|
|
76
|
+
|
|
77
|
+
return G.get_text()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
腾讯元宝
|
|
3
|
+
"""
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from playwright.async_api import Page
|
|
7
|
+
|
|
8
|
+
from mcp_query_table.tool import GlobalVars
|
|
9
|
+
|
|
10
|
+
_PAGE0_ = "https://yuanbao.tencent.com/"
|
|
11
|
+
_PAGE1_ = "https://yuanbao.tencent.com/api/chat"
|
|
12
|
+
_TIMEOUT_ = 1000 * 120
|
|
13
|
+
|
|
14
|
+
G = GlobalVars()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def read_event_stream(text):
|
|
18
|
+
text1 = []
|
|
19
|
+
text2 = []
|
|
20
|
+
for event in text.split('\n\n'):
|
|
21
|
+
if 'data: {"type":"think"' in event:
|
|
22
|
+
lines = event.split('\n')
|
|
23
|
+
for line in lines:
|
|
24
|
+
if line.startswith('data: '):
|
|
25
|
+
t = line[6:]
|
|
26
|
+
t = json.loads(t)['content']
|
|
27
|
+
text1.append(t)
|
|
28
|
+
if 'data: {"type":"text"' in event:
|
|
29
|
+
lines = event.split('\n')
|
|
30
|
+
for line in lines:
|
|
31
|
+
if line.startswith('data: '):
|
|
32
|
+
t = line[6:]
|
|
33
|
+
t = json.loads(t).get('msg', "")
|
|
34
|
+
text2.append(t)
|
|
35
|
+
|
|
36
|
+
text2 = ''.join(text2)
|
|
37
|
+
if len(text1) == 0:
|
|
38
|
+
return text2
|
|
39
|
+
else:
|
|
40
|
+
text1 = ''.join(text1)
|
|
41
|
+
return f"<thinking>{text1}</thinking>\n\n{text2}"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def on_response(response):
|
|
45
|
+
if response.url.startswith(_PAGE1_):
|
|
46
|
+
# print("on_response", response.url)
|
|
47
|
+
text = await response.text()
|
|
48
|
+
G.set_text(read_event_stream(text))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def on_route(route):
|
|
52
|
+
# print("on_route", route.request.url)
|
|
53
|
+
if route.request.url.startswith(_PAGE1_):
|
|
54
|
+
# TODO 这里会导致数据全部加载,逻辑变了,所以界面可能混乱
|
|
55
|
+
response = await route.fetch(timeout=_TIMEOUT_)
|
|
56
|
+
await route.fulfill(
|
|
57
|
+
# 强行加utf-8,否则编码搞不定
|
|
58
|
+
content_type="text/event-stream; charset=utf-8",
|
|
59
|
+
response=response,
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
await route.continue_()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def chat(page: Page,
|
|
66
|
+
prompt: str,
|
|
67
|
+
create: bool,
|
|
68
|
+
) -> str:
|
|
69
|
+
if not page.url.startswith(_PAGE0_):
|
|
70
|
+
create = True
|
|
71
|
+
|
|
72
|
+
if create:
|
|
73
|
+
await page.goto(_PAGE0_)
|
|
74
|
+
|
|
75
|
+
await page.route(f"{_PAGE1_}/*", on_route)
|
|
76
|
+
async with page.expect_response(f"{_PAGE1_}/*", timeout=_TIMEOUT_) as response_info:
|
|
77
|
+
textbox = page.locator(".ql-editor")
|
|
78
|
+
await textbox.fill(prompt)
|
|
79
|
+
await textbox.press("Enter")
|
|
80
|
+
await on_response(await response_info.value)
|
|
81
|
+
|
|
82
|
+
return G.get_text()
|
mcp_query_table/server.py
CHANGED
|
@@ -4,7 +4,8 @@ from loguru import logger
|
|
|
4
4
|
from mcp.server.fastmcp import FastMCP
|
|
5
5
|
from pydantic import Field
|
|
6
6
|
|
|
7
|
-
from mcp_query_table import QueryType, Site, query as
|
|
7
|
+
from mcp_query_table import QueryType, Site, query as qt_query, chat as qt_chat
|
|
8
|
+
from mcp_query_table.enums import Provider
|
|
8
9
|
from mcp_query_table.tool import BrowserManager
|
|
9
10
|
|
|
10
11
|
|
|
@@ -15,7 +16,7 @@ class QueryServer:
|
|
|
15
16
|
|
|
16
17
|
async def query(self, query_input: str, query_type: QueryType, max_page: int, site: Site):
|
|
17
18
|
page = await self.browser.get_page()
|
|
18
|
-
df = await
|
|
19
|
+
df = await qt_query(page, query_input, query_type, max_page, site)
|
|
19
20
|
self.browser.release_page(page)
|
|
20
21
|
|
|
21
22
|
if self.format == 'csv':
|
|
@@ -25,6 +26,12 @@ class QueryServer:
|
|
|
25
26
|
if self.format == 'json':
|
|
26
27
|
return df.to_json(force_ascii=False, indent=2)
|
|
27
28
|
|
|
29
|
+
async def chat(self, prompt: str, create: bool, provider: Provider):
|
|
30
|
+
page = await self.browser.get_page()
|
|
31
|
+
txt = await qt_chat(page, prompt, create, provider)
|
|
32
|
+
self.browser.release_page(page)
|
|
33
|
+
return txt
|
|
34
|
+
|
|
28
35
|
|
|
29
36
|
# !!!log_level这一句非常重要,否则Cline/MCP Server/Tools工作不正常
|
|
30
37
|
mcp = FastMCP("query_table_mcp", log_level="ERROR")
|
|
@@ -43,6 +50,15 @@ async def query(
|
|
|
43
50
|
return await qsv.query(query_input, query_type, max_page, site)
|
|
44
51
|
|
|
45
52
|
|
|
53
|
+
@mcp.tool(description="大语言模型对话")
|
|
54
|
+
async def chat(
|
|
55
|
+
prompt: Annotated[str, Field(description="提示词。如:`9.9大还是9.11大?`")],
|
|
56
|
+
create: Annotated[bool, Field(default=False, description="是否创建新对话")],
|
|
57
|
+
provider: Annotated[Provider, Field(default=Provider.N, description="提供商。支持`纳米搜索`、`腾讯元宝`")]
|
|
58
|
+
) -> str:
|
|
59
|
+
return await qsv.chat(prompt, create, provider)
|
|
60
|
+
|
|
61
|
+
|
|
46
62
|
def serve(format, cdp_port, browser_path, transport, mcp_host, mcp_port):
|
|
47
63
|
qsv.format = format
|
|
48
64
|
qsv.port = cdp_port
|
mcp_query_table/tool.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import subprocess
|
|
2
|
+
import sys
|
|
2
3
|
import time
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import Optional
|
|
@@ -7,7 +8,29 @@ import pandas as pd
|
|
|
7
8
|
from loguru import logger
|
|
8
9
|
from playwright.async_api import async_playwright, Playwright, Page
|
|
9
10
|
|
|
10
|
-
from mcp_query_table.enums import QueryType, Site
|
|
11
|
+
from mcp_query_table.enums import QueryType, Site, Provider
|
|
12
|
+
|
|
13
|
+
browsers_path = {
|
|
14
|
+
"chrome.exe": r"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
|
15
|
+
"msedge.exe": r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_detached_process(command):
|
|
20
|
+
# 设置通用参数
|
|
21
|
+
kwargs = {}
|
|
22
|
+
|
|
23
|
+
if sys.platform == 'win32':
|
|
24
|
+
kwargs.update({
|
|
25
|
+
# 在PyCharm中运行还是会出现新建进程被关闭
|
|
26
|
+
'creationflags': subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
|
|
27
|
+
})
|
|
28
|
+
else:
|
|
29
|
+
# Unix-like 系统(Linux, macOS)特定设置
|
|
30
|
+
kwargs.update({
|
|
31
|
+
'start_new_session': True # 创建新的会话
|
|
32
|
+
})
|
|
33
|
+
return subprocess.Popen(command, **kwargs)
|
|
11
34
|
|
|
12
35
|
|
|
13
36
|
class BrowserManager:
|
|
@@ -31,12 +54,12 @@ class BrowserManager:
|
|
|
31
54
|
|
|
32
55
|
"""
|
|
33
56
|
if browser_path is None:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
57
|
+
for k, v in browsers_path.items():
|
|
58
|
+
if Path(v).exists():
|
|
59
|
+
browser_path = v
|
|
60
|
+
break
|
|
61
|
+
if browser_path is None:
|
|
62
|
+
raise ValueError("未找到浏览器可执行文件")
|
|
40
63
|
|
|
41
64
|
self.port = port
|
|
42
65
|
self.browser_path = browser_path
|
|
@@ -62,31 +85,27 @@ class BrowserManager:
|
|
|
62
85
|
https://blog.csdn.net/qq_30576521/article/details/142370538
|
|
63
86
|
|
|
64
87
|
"""
|
|
65
|
-
|
|
88
|
+
endpoint = f"http://127.0.0.1:{self.port}"
|
|
89
|
+
command = [self.browser_path, f'--remote-debugging-port={self.port}', '--start-maximized']
|
|
90
|
+
name = Path(self.browser_path).name
|
|
91
|
+
if self.debug:
|
|
92
|
+
command.append('--auto-open-devtools-for-tabs')
|
|
66
93
|
|
|
67
|
-
|
|
68
|
-
# 尝试连接已打开的浏览器
|
|
69
|
-
self.browser = await self.playwright.chromium.connect_over_cdp(f"http://127.0.0.1:{self.port}",
|
|
70
|
-
slow_mo=1000,
|
|
71
|
-
timeout=5000)
|
|
72
|
-
except:
|
|
73
|
-
|
|
74
|
-
# 执行完成后不会关闭浏览器
|
|
75
|
-
if self.debug:
|
|
76
|
-
command = f'"{self.browser_path}" --remote-debugging-port={self.port} --start-maximized --auto-open-devtools-for-tabs'
|
|
77
|
-
else:
|
|
78
|
-
command = f'"{self.browser_path}" --remote-debugging-port={self.port} --start-maximized'
|
|
79
|
-
logger.info(f"start browser:{command}")
|
|
80
|
-
subprocess.Popen(command, shell=True)
|
|
81
|
-
time.sleep(3)
|
|
94
|
+
self.playwright = await async_playwright().start()
|
|
82
95
|
|
|
96
|
+
for i in range(2):
|
|
83
97
|
try:
|
|
84
|
-
self.browser = await self.playwright.chromium.connect_over_cdp(
|
|
85
|
-
|
|
86
|
-
timeout=5000)
|
|
98
|
+
self.browser = await self.playwright.chromium.connect_over_cdp(endpoint, timeout=10000, slow_mo=1000)
|
|
99
|
+
break
|
|
87
100
|
except:
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
if i == 0:
|
|
102
|
+
logger.info(f"start browser:{command}")
|
|
103
|
+
create_detached_process(command)
|
|
104
|
+
time.sleep(3)
|
|
105
|
+
continue
|
|
106
|
+
if i == 1:
|
|
107
|
+
raise ConnectionError(
|
|
108
|
+
f"已提前打开了浏览器,但未开启远程调试端口?请关闭浏览器全部进程后重试 `taskkill /f /im {name}`")
|
|
90
109
|
|
|
91
110
|
self.context = self.browser.contexts[0]
|
|
92
111
|
# 复用打开的page
|
|
@@ -124,6 +143,19 @@ class BrowserManager:
|
|
|
124
143
|
self.pages.append(page)
|
|
125
144
|
|
|
126
145
|
|
|
146
|
+
class GlobalVars:
|
|
147
|
+
"""全局变量"""
|
|
148
|
+
|
|
149
|
+
def __init__(self):
|
|
150
|
+
self.text = ""
|
|
151
|
+
|
|
152
|
+
def set_text(self, text):
|
|
153
|
+
self.text = text
|
|
154
|
+
|
|
155
|
+
def get_text(self):
|
|
156
|
+
return self.text
|
|
157
|
+
|
|
158
|
+
|
|
127
159
|
async def query(
|
|
128
160
|
page: Page,
|
|
129
161
|
query_input: str = "收盘价>100元",
|
|
@@ -163,3 +195,21 @@ async def query(
|
|
|
163
195
|
return await query(page, query_input, query_type, max_page)
|
|
164
196
|
|
|
165
197
|
raise ValueError(f"未支持的站点:{site}")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
async def chat(
|
|
201
|
+
page: Page,
|
|
202
|
+
prompt: str = "9.9大还是9.11大?",
|
|
203
|
+
create: bool = False,
|
|
204
|
+
provider: Provider = Provider.N) -> str:
|
|
205
|
+
if provider == Provider.N:
|
|
206
|
+
from mcp_query_table.providers.n import chat
|
|
207
|
+
return await chat(page, prompt, create)
|
|
208
|
+
if provider == Provider.YuanBao:
|
|
209
|
+
from mcp_query_table.providers.yuanbao import chat
|
|
210
|
+
return await chat(page, prompt, create)
|
|
211
|
+
if provider == Provider.BaiDu:
|
|
212
|
+
from mcp_query_table.providers.baidu import chat
|
|
213
|
+
return await chat(page, prompt, create)
|
|
214
|
+
|
|
215
|
+
raise ValueError(f"未支持的提供商:{provider}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp_query_table
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: query table from website, support MCP
|
|
5
5
|
Author-email: wukan <wu-kan@163.com>
|
|
6
6
|
License: MIT License
|
|
@@ -39,13 +39,20 @@ Dynamic: license-file
|
|
|
39
39
|
|
|
40
40
|
# mcp_query_table
|
|
41
41
|
|
|
42
|
-
基于`playwright`实现的财经网页表格爬虫,支持`Model Context Protocol (MCP) `。目前可查询来源为
|
|
42
|
+
1. 基于`playwright`实现的财经网页表格爬虫,支持`Model Context Protocol (MCP) `。目前可查询来源为
|
|
43
43
|
|
|
44
|
-
- [同花顺i问财](http://iwencai.com/)
|
|
45
|
-
- [通达信问小达](https://wenda.tdx.com.cn/)
|
|
46
|
-
- [东方财富条件选股](https://xuangu.eastmoney.com/)
|
|
44
|
+
- [同花顺i问财](http://iwencai.com/)
|
|
45
|
+
- [通达信问小达](https://wenda.tdx.com.cn/)
|
|
46
|
+
- [东方财富条件选股](https://xuangu.eastmoney.com/)
|
|
47
47
|
|
|
48
|
-
实盘时,如果某网站宕机或改版,可以立即切换到其他网站。(注意:不同网站的表格结构不同,需要提前做适配)
|
|
48
|
+
实盘时,如果某网站宕机或改版,可以立即切换到其他网站。(注意:不同网站的表格结构不同,需要提前做适配)
|
|
49
|
+
|
|
50
|
+
2. 基于`playwright`实现的大语言模型调用爬虫。目前可用来源为
|
|
51
|
+
- [纳米搜索](https://www.n.cn/)
|
|
52
|
+
- [腾讯元宝](https://yuanbao.tencent.com/)
|
|
53
|
+
- [百度AI搜索](https://chat.baidu.com/)
|
|
54
|
+
|
|
55
|
+
`RooCode`提供了`Human Reply`功能。但发现`纳米搜索`网页版复制时格式破坏,所以研发了此功能
|
|
49
56
|
|
|
50
57
|
## 安装
|
|
51
58
|
|
|
@@ -63,25 +70,31 @@ from mcp_query_table import *
|
|
|
63
70
|
|
|
64
71
|
|
|
65
72
|
async def main() -> None:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
async with BrowserManager(port=9222, browser_path=None, debug=True) as bm:
|
|
74
|
+
# 问财需要保证浏览器宽度>768,防止界面变成适应手机
|
|
75
|
+
page = await bm.get_page()
|
|
76
|
+
df = await query(page, '收益最好的200只ETF', query_type=QueryType.ETF, max_page=1, site=Site.THS)
|
|
77
|
+
print(df.to_markdown())
|
|
78
|
+
df = await query(page, '年初至今收益率前50', query_type=QueryType.Fund, max_page=1, site=Site.TDX)
|
|
79
|
+
print(df.to_csv())
|
|
80
|
+
df = await query(page, '流通市值前10的行业板块', query_type=QueryType.Index, max_page=1, site=Site.TDX)
|
|
81
|
+
print(df.to_csv())
|
|
82
|
+
# TODO 东财翻页要提前登录
|
|
83
|
+
df = await query(page, '今日涨幅前5的概念板块;', query_type=QueryType.Board, max_page=3, site=Site.EastMoney)
|
|
84
|
+
print(df)
|
|
85
|
+
|
|
86
|
+
output = await chat(page, "1+2等于多少?", provider=Provider.YuanBao)
|
|
87
|
+
print(output)
|
|
88
|
+
output = await chat(page, "3+4等于多少?", provider=Provider.YuanBao, create=True)
|
|
89
|
+
print(output)
|
|
90
|
+
|
|
91
|
+
print('done')
|
|
92
|
+
bm.release_page(page)
|
|
93
|
+
await page.wait_for_timeout(2000)
|
|
81
94
|
|
|
82
95
|
|
|
83
96
|
if __name__ == '__main__':
|
|
84
|
-
|
|
97
|
+
asyncio.run(main())
|
|
85
98
|
|
|
86
99
|
```
|
|
87
100
|
|
|
@@ -158,7 +171,7 @@ http://localhost:8000/sse
|
|
|
158
171
|
npx @modelcontextprotocol/inspector python -m mcp_query_table --format markdown
|
|
159
172
|
```
|
|
160
173
|
|
|
161
|
-
打开浏览器并翻页是一个比较耗时的操作,会导致`MCP Inspector`页面超时,可以`http://localhost:5173/?timeout=
|
|
174
|
+
打开浏览器并翻页是一个比较耗时的操作,会导致`MCP Inspector`页面超时,可以`http://localhost:5173/?timeout=600000` 表示超时时间为600秒
|
|
162
175
|
|
|
163
176
|
第一次尝试编写`MCP`项目,可能会有各种问题,欢迎大家交流。
|
|
164
177
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
mcp_query_table/__init__.py,sha256=HqE0MJttWSwaeAhmm-tsPN3kZ4nrZUKwlfnyYJGHVRs,126
|
|
2
|
+
mcp_query_table/__main__.py,sha256=SmOaVfIDX4dcF-SV_xcn-Sfn3io-XunsdwKPnGfzwAU,1150
|
|
3
|
+
mcp_query_table/_version.py,sha256=VrXpHDu3erkzwl_WXrqINBm9xWkcyUy53IQOj042dOs,22
|
|
4
|
+
mcp_query_table/enums.py,sha256=378RbWeuCoRdJ2TxK-yMKg3rrJS0IpeI7UP5J1uveD8,625
|
|
5
|
+
mcp_query_table/server.py,sha256=QbkpK0s_5CDORrKesH845tT_GqosHUkRxKOWBrFsOsc,3101
|
|
6
|
+
mcp_query_table/tool.py,sha256=qfjWJN3KWncLoOtZz9N5s-hm2_cWjVLL2iHgoO2MCP8,6591
|
|
7
|
+
mcp_query_table/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
mcp_query_table/providers/baidu.py,sha256=CsmSjtnuzo-1aZxK5kGhIQjM9RpBmfpWVaEU5rofJyU,2541
|
|
9
|
+
mcp_query_table/providers/n.py,sha256=GYGoKN0BOOWkUKiW1DAn_9yyAvUBhZ6j3aNLy2cI99w,2138
|
|
10
|
+
mcp_query_table/providers/yuanbao.py,sha256=lkRbxe9EqciCCnJiucqRpMwD-sm4XkXRW_gVO7ZwzNU,2322
|
|
11
|
+
mcp_query_table/sites/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
mcp_query_table/sites/eastmoney.py,sha256=LImjpYVuM5YnXwnNzB2hkKfHofocZZScetGqMOCHZpk,4477
|
|
13
|
+
mcp_query_table/sites/iwencai.py,sha256=g56pj3pbxu4mXLNnaaS3Hdx-DvEy_9OBrQJe26z4z08,5059
|
|
14
|
+
mcp_query_table/sites/tdx.py,sha256=RIUQaB7Tn4AVyWaevk9SzTKIDwVO2f9erIlI-adXPLY,4126
|
|
15
|
+
mcp_query_table-0.3.0.dist-info/licenses/LICENSE,sha256=rbvv_CTd7biGwT21tvhgQ2zkbPFXOoON7WFQWEdElBA,1063
|
|
16
|
+
mcp_query_table-0.3.0.dist-info/METADATA,sha256=BNAaJWqlddE_wS2saWGSNK-QzgIAmlwAobBQrPQ24Eo,8044
|
|
17
|
+
mcp_query_table-0.3.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
18
|
+
mcp_query_table-0.3.0.dist-info/top_level.txt,sha256=5M_8dkO1USOX7_EWbWS6O_TEsZ5yo-AodFNKeUEgvEQ,16
|
|
19
|
+
mcp_query_table-0.3.0.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
mcp_query_table/__init__.py,sha256=nHQOsX_pO9mwyq4a77irNpgvIXmc8NArQnj5-nEMW6k,110
|
|
2
|
-
mcp_query_table/__main__.py,sha256=SmOaVfIDX4dcF-SV_xcn-Sfn3io-XunsdwKPnGfzwAU,1150
|
|
3
|
-
mcp_query_table/_version.py,sha256=Oz5HbwHMyE87nmwV80AZzpkJPf-wBg7eDuJr_BXZkhU,22
|
|
4
|
-
mcp_query_table/enums.py,sha256=gVioM6lyI6lGfBBS9KY2IxCh4QKdLVAqXAgo19aAg6U,446
|
|
5
|
-
mcp_query_table/server.py,sha256=Hi9qUMH9vvhLwcgsnCGORjQB9Ac_N-SM0a-hcC2QB8w,2375
|
|
6
|
-
mcp_query_table/tool.py,sha256=_PjyjsgjDvwAGKVFv4W-Olim_qB7jGpg4CZxnsXzNDk,5641
|
|
7
|
-
mcp_query_table/sites/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
mcp_query_table/sites/eastmoney.py,sha256=LImjpYVuM5YnXwnNzB2hkKfHofocZZScetGqMOCHZpk,4477
|
|
9
|
-
mcp_query_table/sites/iwencai.py,sha256=g56pj3pbxu4mXLNnaaS3Hdx-DvEy_9OBrQJe26z4z08,5059
|
|
10
|
-
mcp_query_table/sites/tdx.py,sha256=RIUQaB7Tn4AVyWaevk9SzTKIDwVO2f9erIlI-adXPLY,4126
|
|
11
|
-
mcp_query_table-0.2.6.dist-info/licenses/LICENSE,sha256=rbvv_CTd7biGwT21tvhgQ2zkbPFXOoON7WFQWEdElBA,1063
|
|
12
|
-
mcp_query_table-0.2.6.dist-info/METADATA,sha256=gmVXsQZi17_AAJi9StVSPpIpcrrdNwj51OOVFdne7Ww,7490
|
|
13
|
-
mcp_query_table-0.2.6.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
14
|
-
mcp_query_table-0.2.6.dist-info/top_level.txt,sha256=5M_8dkO1USOX7_EWbWS6O_TEsZ5yo-AodFNKeUEgvEQ,16
|
|
15
|
-
mcp_query_table-0.2.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|