Skip to main content
Glama
README.md
# VisualWebScraper

基于 **MCP(Model Context Protocol)** 的网页抓取服务,使用 Playwright 无头 Chromium 渲染动态网页,
并可调用本地 LM Studio 大模型生成文章简报。

## 安装

```bash
pip install -r requirements.txt
playwright install chromium
```

## 启动

```bash
python screenshot_scraper_mcp.py
# 或
python -m webscraper.server
```

## 本地可视化操作台(Web UI)

除 MCP stdio 外,另提供基于标准库 `http.server` 的网页界面,无需额外依赖,
复用同一套工具层与浏览器池,供非开发者在浏览器中可视化操作。

### 统一启动台(推荐)

一条命令拉起全部功能,并打开功能选择入口页,选择后跳转至对应操作台:

```bash
python run_home.py                 # 启动台默认 http://127.0.0.1:8760
python run_home.py --port 9000     # 自定义入口端口
```

打开 `http://127.0.0.1:8760` 即进入**功能选择页(启动台)**,以卡片形式列出:

| 功能 | 操作台地址 | 说明 |
|---|---|---|
| 综合操作台 | http://127.0.0.1:8765 | 网页截图 / 文章检索 / 简报生成(需 LM Studio) |
| 文章详情 | http://127.0.0.1:8766 | 批量抓取列表页每篇文章的【标题、时间、正文】 |
| 教育部搜索 | http://127.0.0.1:8767 | so.moe.gov.cn 站内检索,支持筛选与排序 |

启动台会**自动拉起上述三个功能子服务**(各自独立进程,因浏览器池为进程内单例),
并通过 `/api/health` 实时显示各功能就绪状态;按 `Ctrl+C` 可一次性停止全部服务。

> 浏览器池是进程内单例,每个功能服务需独占自己的事件循环,故分进程运行,
> 互不干扰;启动台本身只提供导航页,不触碰浏览器池。

### 单独启动某个功能

若只需某一功能,也可单独运行对应入口(不依赖启动台):

```bash
python run_webui.py                 # 综合操作台  http://127.0.0.1:8765
python run_article_details_webui.py # 文章详情    http://127.0.0.1:8766
python run_moe_search_webui.py      # 教育部搜索  http://127.0.0.1:8767
```

打开页面后,可在「网页截图 / 文章检索 / 简报生成 / 文章详情」四个标签页填写参数并运行;
截图工具会直接渲染返回的长图,检索、简报与文章详情以文本形式展示(简报生成依赖 LM Studio)。

> 该 Web UI 与 MCP stdio 入口相互独立,互不影响;退出时浏览器实例同样会被释放。

### MCP 客户端配置

```json
{
  "mcpServers": {
    "VisualWebScraper": {
      "command": "python",
      "args": ["d:/BaiduSyncdisk/desktop/WebSearchMCP/screenshot_scraper_mcp.py"],
      "env": {
        "LMSTUDIO_BASE_URL": "http://localhost:1234/v1"
      }
    }
  }
}
```

## 提供的工具

### `fetch_webpage_with_screenshot`
抓取网页纯文本 + 整页长截图(PNG base64),供多模态模型做视觉分析。

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `url` | string | 是 | 目标网页链接 |

### `search_webpage_articles`
从栏目/列表页提取文章条目,按标题关键词筛选,按日期倒序输出。

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `url` | string | 是 | 栏目/列表页链接 |
| `keyword` | string | 是 | 标题筛选关键词 |
| `count` | integer | 是 | 输出条数,`<=0` 表示全部 |

### `generate_webpage_brief`
提取文章 → 抓取详情页正文 → 调用本地大模型逐篇生成摘要 → 输出简报。

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `url` | string | 是 | 栏目/列表页链接 |
| `count` | integer | 是 | 生成条数 |
| `keyword` | string | 否 | 标题筛选关键词,留空为全部 |
| `summary_len` | integer | 否 | 每条摘要目标字数,默认 60 |
| `model` | string | 否 | LM Studio 模型名,缺省用当前加载的模型 |

**三级降级链**(保证任何环境下都有输出):

| 层级 | 触发条件 | 行为 |
|---|---|---|
| L1 | 首次摘要为空 | 换更直接的指令重试 |
| L2 | 仍为空 | 取正文首段作为摘要 |
| L3 | 全部条目模型调用均失败 | 判定 LM Studio 不可用,落盘 Markdown 到 `briefs/` |

### `fetch_article_details`
抓取栏目/列表页,逐个访问其中的文章链接,提取每篇的【标题、时间、内容】。

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `list_url` | string | 是 | 栏目/列表页链接 |
| `limit` | integer | 否 | 最多处理几条(`0` 表示全部),条目多时建议限制 |
| `delay_ms` | integer | 否 | 每条之间的间隔(毫秒),默认 800,降低请求频率 |
| `timeout_ms` | integer | 否 | 单页导航超时(毫秒),默认 0 用内置 30s |

失败的条目也会保留并标注原因。

## 项目结构

```
webscraper/
├── config.py          # 集中配置:超时、限额、环境变量
├── browser.py         # Playwright 浏览器池(复用实例)
├── extractors.py      # 文章提取、正文抓取、文本清洗
├── llm.py             # LM Studio 调用(OpenAI 兼容接口)
├── reporter.py        # 简报 Markdown 落盘与文本渲染
├── server.py          # MCP 入口:handler 注册与生命周期
└── tools/
    ├── base.py        # 工具注册表、参数校验、结果构造
    ├── screenshot.py  # fetch_webpage_with_screenshot
    ├── search.py      # search_webpage_articles
    ├── brief.py       # generate_webpage_brief
    └── details.py     # fetch_article_details
```

## 站点适配

文章链接识别规则集中在 `extractors.py` 顶部,换站点时只需调整:

```python
ARTICLE_PATH_MARK = "/article/"                      # 文章链接特征
ARTICLE_DATE_RE = re.compile(r"/article/(\d{4})/(\d{1,2})/")  # URL 日期格式
```

## 环境变量

| 变量 | 默认值 | 说明 |
|---|---|---|
| `LMSTUDIO_BASE_URL` | `http://localhost:1234/v1` | LM Studio OpenAI 兼容接口地址 |