diff --git "a/tools/tool_deepseek_fetch/1.0.0/DeepSeek\347\275\221\351\241\265\346\212\223\345\217\226.tool" "b/tools/tool_deepseek_fetch/1.0.0/DeepSeek\347\275\221\351\241\265\346\212\223\345\217\226.tool" new file mode 100644 index 0000000..f93ae43 Binary files /dev/null and "b/tools/tool_deepseek_fetch/1.0.0/DeepSeek\347\275\221\351\241\265\346\212\223\345\217\226.tool" differ diff --git a/tools/tool_deepseek_fetch/README.md b/tools/tool_deepseek_fetch/README.md new file mode 100644 index 0000000..22cb80b --- /dev/null +++ b/tools/tool_deepseek_fetch/README.md @@ -0,0 +1,121 @@ +# DeepSeek 网页抓取工具 + +基于 DeepSeek 官方 Fetch API()封装的网页抓取工具,输入 URL,返回页面标题与 Markdown 格式正文。 + +## 一、功能说明 + +- **单页抓取**:调用 `https://api.deepseek.com/v1/tools/fetch`,将目标网页转换为 Markdown 正文 +- **长度可控**:`max_chars` 参数控制返回正文的字符上限,超出自动截断并标注,避免撑爆模型上下文 +- **结构化输出**:同时返回 `title` / `content` / `markdown`(标题 + 来源 + 正文拼装)与截断标记 +- **降级友好**:任何失败统一返回 `success: false` 而非抛异常,并附带 `error` 与中文 `hint` +- **与搜索配套**:配合「DeepSeek 联网搜索」工具(`tool_deepseek_search`)可组成"搜索 → 抓正文 → 生成回答"的完整链路 + +## 二、参数说明 + +### 2.1 启动参数 + +| 参数 | 组件类型 | 必填 | 说明 | +| :--- | :--- | :--- | :--- | +| `api_key` | 密码框 | 是 | DeepSeek API Key,在 获取 | + +### 2.2 输入参数 + +| 参数名 | 数据类型 | 必填 | 来源 | 说明 | +| :--- | :--- | :--- | :--- | :--- | +| `url` | string | 是 | 引用参数 | 目标网页地址,必须为含 host 的 http/https URL,不能带用户名密码,长度不超过 8192 | +| `max_chars` | int | 否 | 自定义参数 | 返回正文的最大字符数,默认 8000,超出部分截断 | + +### 2.3 输出参数 + +| 字段 | 类型 | 说明 | +| :--- | :--- | :--- | +| `success` | bool | 是否成功 | +| `title` | string | 页面标题,缺失时为空串 | +| `url` | string | 实际抓取的地址 | +| `chars` | int | 返回正文长度 | +| `truncated` | bool | 正文是否被截断 | +| `content` | string | Markdown 正文 | +| `markdown` | string | `# 标题 + 来源链接 + 正文` 的拼装结果 | +| `error` / `hint` / `status` | string / string / int | 失败时的错误信息、排查建议与原样状态码 | + +## 三、工具内容(Python) + +完整源码见本目录下 `deepseek_fetch.py`(同时内嵌于 `1.0.0/DeepSeek网页抓取.tool`)。 + +```python +def main(api_key, url, max_chars=8000): + # 1. 参数校验:api_key / url 必填 + # 2. POST https://api.deepseek.com/v1/tools/fetch {"url": ...} + # headers: Authorization: Bearer , Content-Type: application/json + # 3. 状态码分级:400/401/402/403/404/429 分别给出排查提示 + # 4. 正文按 max_chars 截断,拼装 markdown + return {"success": True, "title": ..., "content": ..., "markdown": ...} +``` + +编写时遵守的三条 MaxKB 规范: + +1. 入口函数名必须是 `main`,形参名与工具面板中的参数名逐字一致(MaxKB 以 `main(**keywords)` 调用) +2. `main` 必须位于代码末尾(MaxKB 取最后一个定义的对象作为入口) +3. 返回值需可 JSON 序列化;`print` 会被重定向到 devnull,不要依赖它调试 + +## 四、调用示例 + +### 4.1 与搜索工具串联(推荐编排) + +``` +开始(question) + → DeepSeek 联网搜索(query={{question}}, count=5) + → AI 对话(从搜索结果中选出最相关的 1~2 条 url) + → DeepSeek 网页抓取(url={{AI选出的url}}, max_chars=6000) + → AI 对话(基于正文生成带引用的回答) +``` + +### 4.2 直接调用(Python) + +```python +from deepseek_fetch import main + +r = main(api_key="sk-xxxxxxxx", url="https://example.com/article", max_chars=6000) +print(r["title"]) +print(r["markdown"][:500]) +``` + +### 4.3 典型返回 + +```json +{ + "success": true, + "title": "Example article", + "url": "https://example.com/article", + "chars": 5210, + "truncated": false, + "content": "# Example article\n\nArticle content...", + "markdown": "# Example article\n\n来源:https://example.com/article\n\n# Example article..." +} +``` + +## 五、错误处理 + +| 现象 | 原因 | 处理 | +| :--- | :--- | :--- | +| `HTTP 400` | URL 不合法(无 host / 带账号密码 / 超 8192) | 检查 url 格式 | +| `HTTP 401` | Key 错误或失效 | 检查启动参数中的 `api_key` | +| `HTTP 402` | 账户余额不足 | 充值后重试 | +| `HTTP 403` | 被 DeepSeek 安全策略拒绝 | 目标站点不允许抓取,更换来源 | +| `HTTP 404` | 无法获取可用网页内容 | URL 失效/被站点拦截/无可提取正文,属正常业务失败 | +| `HTTP 429` | 超过 QPS 限制(账号级 5 QPS + 目标域名单独限速) | 稍后重试 | +| 请求异常 / 超时 | MaxKB 容器无出网权限 | 在容器内执行 `curl -I https://api.deepseek.com` 验证 | +| `ModuleNotFoundError: requests` | 精简镜像缺包 | 代码已内置 urllib 回退,通常无需安装 | + +注意:`403`/`404` 多数情况是目标站点策略所致,属预期业务结果,编排中应把 `success=false` 视为"跳过该链接继续",而非流程报错。 + +## 六、计费与限速 + +- 计费:Fetch 按成功请求计费,**5 元 / 1000 次**;失败、超时不计费(必须返回网页正文才计费) +- 限速:账号级 **5 QPS**,另外对目标域名有独立 QPS 限制,超限同样返回 429 + +## 七、版本记录 + +| 版本 | 日期 | 说明 | +| :--- | :--- | :--- | +| 1.0.0 | 2026-10-19 | 首发:Fetch 接口封装,支持 max_chars 截断、markdown 拼装与分级错误处理 | diff --git a/tools/tool_deepseek_fetch/data.yaml b/tools/tool_deepseek_fetch/data.yaml new file mode 100644 index 0000000..3747583 --- /dev/null +++ b/tools/tool_deepseek_fetch/data.yaml @@ -0,0 +1,5 @@ +name: DeepSeek 网页抓取 +tags: + - 联网搜索 +title: 使用 DeepSeek Fetch API 抓取单个网页并转换为 Markdown 正文 +description: 基于 DeepSeek 官方 Fetch API(https://api.deepseek.com/v1/tools/fetch)的网页抓取工具。输入 URL,返回页面标题与 Markdown 格式正文,支持控制正文字符上限;抓取失败时返回结构化错误与原因提示,不抛异常。与 DeepSeek 联网搜索工具配套使用,可完成"搜索 → 抓正文 → 生成回答"链路。 diff --git a/tools/tool_deepseek_fetch/deepseek_fetch.py b/tools/tool_deepseek_fetch/deepseek_fetch.py new file mode 100644 index 0000000..85e3d47 --- /dev/null +++ b/tools/tool_deepseek_fetch/deepseek_fetch.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# ============================================================================= +# MaxKB 脚本工具:DeepSeek 网页正文抓取(Fetch API) +# 接口文档:https://api-docs.deepseek.com/zh-cn/guides/search +# 规范同上:main() 必须放在最后,参数名与工具面板一致,返回 dict。 +# ============================================================================= +import json + +FETCH_URL = "https://api.deepseek.com/v1/tools/fetch" +TIMEOUT = 40 +MAX_CHARS = 8000 + +STATUS_HINT = { + 400: "url 不合法(必须为含 host 的 http/https 地址,不带账号密码,长度 ≤ 8192)", + 401: "API Key 缺失或无效", + 402: "DeepSeek 账户余额不足", + 403: "Fetch 被安全策略拒绝(目标站点不允许抓取)", + 404: "未能获取可用网页内容(URL 失效/被站点拦截/无可提取正文)", + 429: "触发 QPS 限速(Fetch 每账号 5 QPS,且按目标域名单独限速)", +} + + +def _http_post_json(url, headers, payload, timeout=TIMEOUT): + body = json.dumps(payload, ensure_ascii=False).encode("utf-8") + try: + import requests + except Exception: + requests = None + + if requests is not None: + resp = requests.post(url, headers=headers, data=body, timeout=timeout) + return resp.status_code, resp.text + + import urllib.request + import urllib.error + + req = urllib.request.Request(url, data=body, headers=headers, method="POST") + try: + with urllib.request.urlopen(req, timeout=timeout) as r: + return r.status, r.read().decode("utf-8", "replace") + except urllib.error.HTTPError as e: + return e.code, e.read().decode("utf-8", "replace") + + +def main(api_key, url, max_chars=8000): + api_key = (api_key or "").strip() + url = (url or "").strip() + + if not api_key: + return {"success": False, "error": "缺少 api_key", "title": "", "content": ""} + if not url: + return {"success": False, "error": "缺少 url", "title": "", "content": ""} + + body = {"url": url[:8192]} + headers = { + "Authorization": "Bearer " + api_key, + "Content-Type": "application/json", + "Accept": "application/json", + } + + try: + status, text = _http_post_json(FETCH_URL, headers, body) + except Exception as e: + return { + "success": False, + "error": "请求异常:%s" % e, + "hint": "检查 MaxKB 容器到 api.deepseek.com 的网络连通性", + "title": "", + "content": "", + } + + try: + data = json.loads(text) if text else {} + except Exception: + return {"success": False, "status": status, "error": (text or "")[:300], "title": "", "content": ""} + + if status != 200: + err = data.get("error") or {} + return { + "success": False, + "status": status, + "error": err.get("message") or ("HTTP %s" % status), + "hint": STATUS_HINT.get(status, "未预期状态码,请对照官方文档排查"), + "title": "", + "content": "", + } + + title = data.get("title") or "" + content = data.get("content") or "" + try: + limit = int(float(str(max_chars))) + except Exception: + limit = MAX_CHARS + truncated = len(content) > limit + if truncated: + content = content[:limit] + "\n\n……(正文已截断)" + + return { + "success": True, + "title": title, + "url": url, + "chars": len(content), + "truncated": truncated, + "content": content, + "markdown": ("# %s\n\n来源:%s\n\n%s" % (title or url, url, content)).strip(), + } diff --git a/tools/tool_deepseek_fetch/logo.png b/tools/tool_deepseek_fetch/logo.png new file mode 100644 index 0000000..982e0ce Binary files /dev/null and b/tools/tool_deepseek_fetch/logo.png differ