play7284 commited on
Commit
1a64d59
·
verified ·
1 Parent(s): 915fd5d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +150 -163
app.py CHANGED
@@ -1,163 +1,150 @@
1
-
2
- import gradio as gr
3
- import httpx
4
- import readabilipy
5
- import markdownify
6
- import asyncio
7
-
8
- # 使用与原始服务中用户发起请求时相同 User-Agent
9
- # 这有助于模拟常规浏览器行为,减少被网站屏蔽的概率
10
- USER_AGENT = "ModelContextProtocol/1.0 (User-Specified;
11
- +https://github.com/modelcontextprotocol/servers)"
12
-
13
- async def fetch(url: str, force_raw: bool) -> str:
14
- """
15
- 异步获取并处理给定 URL 的内容。
16
-
17
- Args:
18
- url: 要抓取的网页地址。
19
- force_raw: 如果为 True,则返回原始 HTML/文本,不进行 Markdown 转换。
20
-
21
- Returns:
22
- 处理后的内容字符串。
23
- """
24
- if not url or not url.strip():
25
- return "错误:URL 不能为空。"
26
-
27
- # 为了方便用户,如果 URL 缺少协议头,则自动添加 https://
28
- if not url.startswith(('http://', 'https://')):
29
- url = 'https://' + url
30
-
31
- try:
32
- # 使用 httpx 库发起异步 HTTP 请求,设置
33
- User-Agent、允许重定向并设置30秒超时
34
- async with httpx.AsyncClient(headers={"User-Agent": USER_AGENT},
35
- follow_redirects=True, timeout=30.0) as client:
36
- print(f"正在请求: {url}...")
37
- response = await client.get(url)
38
-
39
- # 检查请求是否成功,如果不成功(例如404或500错误),则会抛出异常
40
- response.raise_for_status()
41
- print(f"收到响应,状态码: {response.status_code}")
42
-
43
- page_raw = response.text
44
- content_type = response.headers.get("content-type", "").lower()
45
-
46
- # 判断内容是否为 HTML
47
- # 通过检查 Content-Type 或内容开头是否包含 <html> 标签来判断
48
- is_page_html = "text/html" in content_type or
49
- page_raw.strip().lower().startswith("<html")
50
-
51
- if is_page_html and not force_raw:
52
- # 1. 使用 readabilipy 从原始 HTML 中提取核心文章内容
53
- # 这可以有效去除广告、导航栏、页脚等无关元素
54
- article =
55
- readabilipy.simple_json.simple_json_from_html_string(page_raw,
56
- use_readability=True)
57
-
58
- if not article or not article.get("content"):
59
- return f"##
60
- 无法解析页面\n\n未能成功提取页面主要内容。以下是原始
61
- HTML:\n\n```html\n{page_raw}\n```"
62
-
63
- # 2. 将清理后的 HTML 转换为 Markdown 格式,使其更易于阅读
64
- html_content = article["content"]
65
- markdown_content = markdownify.markdownify(html_content,
66
- heading_style=markdownify.ATX)
67
-
68
- title = article.get('title', '无标题')
69
- return f"# {title}\n\n{markdown_content}"
70
- else:
71
- # 如果内容不是 HTML 或用户选择了"原始模式",则直接返回原始内容
72
- # 使用代码块包裹,以便更好地显示
73
- prefix = f"已获取原始网页内容 (Content-Type:
74
- {content_type}):\n\n"
75
- lang = "html" if is_page_html else "text"
76
- return f"{prefix}```{lang}\n{page_raw}\n```"
77
-
78
- except httpx.RequestError as e:
79
- return f"访问 URL 时发生网络错误: {url}\n\n详细信息: {e}"
80
- except Exception as e:
81
- return f"发生未知错误: {e}"
82
-
83
- def copy_to_clipboard(content: str) -> str:
84
- """
85
- 将内容复制到剪贴板
86
- """
87
- try:
88
- import pyperclip
89
- pyperclip.copy(content)
90
- return "✅ 内容已复制到剪贴板!"
91
- except ImportError:
92
- return "❌ 无法复制:请安装 pyperclip 库 (pip install pyperclip)"
93
- except Exception as e:
94
- return f"❌ 复制失败:{str(e)}"
95
-
96
- # 使用 gr.Blocks() 创建 Gradio 界面,可以更自由地布局
97
- with gr.Blocks(theme=gr.themes.Soft(), title="网页内容提取工具") as demo:
98
- gr.Markdown("# 网页内容提取工具 (Fetch)")
99
- gr.Markdown(
100
- "输入一个网址,此工具可以提其主要内容并将其转换为干净的 Markdown
101
- 格式。"
102
- "非常适合用于阅读文章,可以去除广告和导航等干扰元素。"
103
- )
104
-
105
- with gr.Row():
106
- url_input = gr.Textbox(
107
- label="输入网址",
108
- placeholder="例如:
109
- en.wikipedia.org/wiki/Python_(programming_language)",
110
- scale=4 # 使输入框更宽
111
- )
112
- raw_checkbox = gr.Checkbox(
113
- label="获取原始 HTML",
114
- info="如果勾选,将返回未经处理的完整网页 HTML 代码。",
115
- scale=1
116
- )
117
-
118
- with gr.Row():
119
- submit_btn = gr.Button("提取内容", variant="primary")
120
- copy_btn = gr.Button("📋 复制内容", variant="secondary")
121
-
122
- output_markdown = gr.Markdown(label="提结果")
123
- copy_status = gr.Textbox(label="复制状态", interactive=False, visible=False)
124
-
125
- # 提供一些示例,方便用户快速体验
126
- gr.Examples(
127
- examples=[
128
- ["https://modelcontextprotocol.io/", False],
129
- ["https://www.gradio.app/guides/quickstart", False],
130
- ["https://www.anthropic.com/news/claude-3-5-sonnet", False],
131
- ],
132
- inputs=[url_input, raw_checkbox],
133
- outputs=output_markdown,
134
- fn=fetch,
135
- cache_examples=False, # 禁用缓存,确保每次都获取最新网页内容
136
- )
137
-
138
- # 将按钮的点击事件与核心处理函数绑定
139
- submit_btn.click(
140
- fn=fetch,
141
- inputs=[url_input, raw_checkbox],
142
- outputs=output_markdown,
143
- api_name="fetch_content" # 为 API 模式命名
144
- )
145
-
146
- # 复制按钮点击事件
147
- copy_btn.click(
148
- fn=copy_to_clipboard,
149
- inputs=[output_markdown],
150
- outputs=[copy_status]
151
- ).then(
152
- lambda: gr.update(visible=True),
153
- outputs=[copy_status]
154
- ).then(
155
- lambda: gr.update(visible=False),
156
- inputs=None,
157
- outputs=[copy_status],
158
- _js="(x) => new Promise((resolve) => setTimeout(() => resolve(), 2000))"
159
- )
160
-
161
- if __name__ == "__main__":
162
- # 启动 Gradio 应用
163
- demo.launch(mcp_server=True)
 
1
+ import gradio as gr
2
+ import httpx
3
+ import readabilipy
4
+ import markdownify
5
+ import asyncio
6
+
7
+ # 使用与原始服务器中用户发起请求时相同的 User-Agent
8
+ # 这有助于模拟常规浏览行为,减少被网站屏蔽概率
9
+ USER_AGENT = "ModelContextProtocol/1.0 (User-Specified; +https://github.com/modelcontextprotocol/servers)"
10
+
11
+ async def fetch(url: str, force_raw: bool) -> str:
12
+ """
13
+ 异步获取并处理给定 URL 的内容。
14
+
15
+ Args:
16
+ url: 要抓取的网页地址。
17
+ force_raw: 如果为 True,则返回原始 HTML/文本,不进行 Markdown 转换。
18
+
19
+ Returns:
20
+ 处理后的内容字符串。
21
+ """
22
+ if not url or not url.strip():
23
+ return "错误:URL 不能为空。"
24
+
25
+ # 为了方便用户,如果 URL 缺少协议头,则自动添加 https://
26
+ if not url.startswith(('http://', 'https://')):
27
+ url = 'https://' + url
28
+
29
+ try:
30
+ # 使用 httpx 库发起异步 HTTP 请求,设置 User-Agent、允许重定向并设置30秒超时
31
+ async with httpx.AsyncClient(headers={"User-Agent": USER_AGENT}, follow_redirects=True, timeout=30.0) as client:
32
+ print(f"正在请求: {url}...")
33
+ response = await client.get(url)
34
+
35
+ # 检查请求是否成功,如果不成功(例如404或500错误),则会抛出异常
36
+ response.raise_for_status()
37
+ print(f"收到响应,状态码: {response.status_code}")
38
+
39
+ page_raw = response.text
40
+ content_type = response.headers.get("content-type", "").lower()
41
+
42
+ # 判断内容是否为 HTML
43
+ # 通过检查 Content-Type 或内容开头是否包含 <html> 标签来判断
44
+ is_page_html = "text/html" in content_type or page_raw.strip().lower().startswith("<html")
45
+
46
+ if is_page_html and not force_raw:
47
+ # 1. 使用 readabilipy 从原始 HTML 中提取核心文章内容
48
+ # 这可以有效去除广告、导航栏、页脚等无关元素
49
+ article = readabilipy.simple_json.simple_json_from_html_string(page_raw, use_readability=True)
50
+
51
+ if not article or not article.get("content"):
52
+ return f"## 无法解析页面\n\n未能成功提取页面主要内容。以下是原始 HTML:\n\n```html\n{page_raw}\n```"
53
+
54
+ # 2. 将清理后的 HTML 转换为 Markdown 格式,使其更易于阅读
55
+ html_content = article["content"]
56
+ markdown_content = markdownify.markdownify(html_content, heading_style=markdownify.ATX)
57
+
58
+ title = article.get('title', '无标题')
59
+ return f"# {title}\n\n{markdown_content}"
60
+ else:
61
+ # 如果内容不是 HTML 或用户选择了"原始模式",则直接返回原始内容
62
+ # 使用代码块包裹,以便更好地显示
63
+ prefix = f"已获取原始网页内容 (Content-Type: {content_type}):\n\n"
64
+ lang = "html" if is_page_html else "text"
65
+ return f"{prefix}```{lang}\n{page_raw}\n```"
66
+
67
+ except httpx.RequestError as e:
68
+ return f"访问 URL 时发生网络错误: {url}\n\n详细信息: {e}"
69
+ except Exception as e:
70
+ return f"发生未知错误: {e}"
71
+
72
+ def copy_to_clipboard(content: str) -> str:
73
+ """
74
+ 将内容复制到剪贴板
75
+ """
76
+ try:
77
+ import pyperclip
78
+ pyperclip.copy(content)
79
+ return " 内容已复制到剪贴板!"
80
+ except ImportError:
81
+ return " 无法复制:请安装 pyperclip 库 (pip install pyperclip)"
82
+ except Exception as e:
83
+ return f"❌ 复制失败:{str(e)}"
84
+
85
+ # 使用 gr.Blocks() 创建 Gradio 界面,可以更自由地布局
86
+ with gr.Blocks(theme=gr.themes.Soft(), title="网页内容提取工具") as demo:
87
+ gr.Markdown("# 网页内容提取工具 (Fetch)")
88
+ gr.Markdown(
89
+ "输入一个网址,此工具可以提取其主要内容并将其转换为干净的 Markdown 格式。"
90
+ "非常适合用于阅读文章,可以去除广告和导航等干扰元素。"
91
+ )
92
+
93
+ with gr.Row():
94
+ url_input = gr.Textbox(
95
+ label="输入网址",
96
+ placeholder="例如: en.wikipedia.org/wiki/Python_(programming_language)",
97
+ scale=4 # 使输入框更宽
98
+ )
99
+ raw_checkbox = gr.Checkbox(
100
+ label="原始 HTML",
101
+ info="如果勾选,将返回未经处理的完整网页 HTML 代码。",
102
+ scale=1
103
+ )
104
+
105
+ with gr.Row():
106
+ submit_btn = gr.Button("提取内容", variant="primary")
107
+ copy_btn = gr.Button("📋 复制内容", variant="secondary")
108
+
109
+ output_markdown = gr.Markdown(label="提取结果")
110
+ copy_status = gr.Textbox(label="复制状态", interactive=False, visible=False)
111
+
112
+ # 提供一些示例,方便用户快速体验
113
+ gr.Examples(
114
+ examples=[
115
+ ["https://modelcontextprotocol.io/", False],
116
+ ["https://www.gradio.app/guides/quickstart", False],
117
+ ["https://www.anthropic.com/news/claude-3-5-sonnet", False],
118
+ ],
119
+ inputs=[url_input, raw_checkbox],
120
+ outputs=output_markdown,
121
+ fn=fetch,
122
+ cache_examples=False, # 禁用缓存,确保每次都获最新网页内容
123
+ )
124
+
125
+ # 将按钮的点击事件与核心处理函数绑定
126
+ submit_btn.click(
127
+ fn=fetch,
128
+ inputs=[url_input, raw_checkbox],
129
+ outputs=output_markdown,
130
+ api_name="fetch_content" # 为 API 模式命名
131
+ )
132
+
133
+ # 复制按钮点击事件
134
+ copy_btn.click(
135
+ fn=copy_to_clipboard,
136
+ inputs=[output_markdown],
137
+ outputs=[copy_status]
138
+ ).then(
139
+ lambda: gr.update(visible=True),
140
+ outputs=[copy_status]
141
+ ).then(
142
+ lambda: gr.update(visible=False),
143
+ inputs=None,
144
+ outputs=[copy_status],
145
+ _js="(x) => new Promise((resolve) => setTimeout(() => resolve(), 2000))"
146
+ )
147
+
148
+ if __name__ == "__main__":
149
+ # 启动 Gradio 应用
150
+ demo.launch(mcp_server=True)