From e3387208fe377bee2222a097c5297b81943b30f3 Mon Sep 17 00:00:00 2001 From: gzzwp <38277434+gzzwp@users.noreply.github.com> Date: Mon, 19 Oct 2026 17:34:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20DeepSeek=20=E7=BD=91?= =?UTF-8?q?=E9=A1=B5=E6=8A=93=E5=8F=96=E5=B7=A5=E5=85=B7=EF=BC=88=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=8F=8F=E8=BF=B0=E8=B6=85=E9=95=BF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 配套 DeepSeek 联网搜索工具(#135):Search 负责检索,Fetch 负责抓正文。 - POST https://api.deepseek.com/v1/tools/fetch - max_chars 控制正文上限(默认 8000),超长截断并标注 - 403/404 等按业务失败处理(success=false + 中文 hint),不抛异常 - 描述压缩至 89 字符,避免 Tool.desc 的 varchar(128) 溢出 --- ...\351\241\265\346\212\223\345\217\226.tool" | Bin 0 -> 4745 bytes tools/tool_deepseek_fetch/README.md | 121 ++++++++++++++++++ tools/tool_deepseek_fetch/data.yaml | 5 + tools/tool_deepseek_fetch/deepseek_fetch.py | 106 +++++++++++++++ tools/tool_deepseek_fetch/logo.png | Bin 0 -> 186 bytes 5 files changed, 232 insertions(+) create mode 100644 "tools/tool_deepseek_fetch/1.0.0/DeepSeek\347\275\221\351\241\265\346\212\223\345\217\226.tool" create mode 100644 tools/tool_deepseek_fetch/README.md create mode 100644 tools/tool_deepseek_fetch/data.yaml create mode 100644 tools/tool_deepseek_fetch/deepseek_fetch.py create mode 100644 tools/tool_deepseek_fetch/logo.png 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 0000000000000000000000000000000000000000..057280cacd325a9372f218a8712a2f6ad6b288a3 GIT binary patch literal 4745 zcmb_g`)?H26($J|VS%Pfn>2}1^=4?Q-NyT{F<@JAC5M7QuED{J5{aPIc=p;eWp`#e zbH`witF?o}0)uVB*gRtMFklcbLqqXmOsxLaza#O??*7ET&~xvdeRxBgG|I}}dE9f) z<2&Cy_uS2_p7{G4tJueWi)dQPjF{L^#FTOb8)k&QaqRVRxa?9*BB~O0&N`tXXV3}S ztJx^Mds#_3_V2~!t>RV@H-)!0NGtUERd!-}M-j=c)*lGv0S`(J#-Jw36UyUk_YCyxuy?s}(Z zmU9cJQ#1}HwL>cM?p^aHkAIgLE@p=F&aLHzQEz-I|7gZLbG|S;w46I#c>MKunISJb z38P+iKL7NBdwijI_8V{X1_*c$hl}S&3Ug20kz1h0JNFn=Sw;%EOC!arPrdVV?)V)f zYbHVYi^q^mh#sY1CUU19xYL>Ei*|8h$z7ObmPQIo$5C@bOCuB&5)U6he+lZ95bD_WlL6 zA)zYpl`;1>MA@2&_EO-4Kzvu{_C5RJFsCUN=xqNqv2ADj-fnnoj>TeuKzF=7zOOs6 zbJuPvax{S8(-e!fB7yP@c;k*|y{{)A5u9^C?;qqGot2v{JeqaK=H0n3@>hn+L`2`7 zxXw5T*%a!1YYkux5{x?V0Lm{exYr-Tg7D5=@g}lmnMRN)69rVmVp)E6p*Su5MJkFkZ}DF0{y{P#4=R4n0f^E7h;(S8e~0xL-1kZ`^6V0z?)KfBEEI*@rn^_7@EPS zfDkykazWE-YN+*zgBUUhw(3z@Je-hZ(JAHq56I^infg z;vMChui$&$9~DeXlCWtCt%$7#fP{q&&jKxD56+b43NSJY=9&RMoq_OQq@bcv!4(CCuSnoBtV3|L zw%C<7$*Ph^qvjc}tvoO0WhHp#W^J08PjrNSDk(3_%|pNDE)Ki79JCc5m;(9$;R-ZN zT2^@0y=xio@(sTqg1yAOaLdg;pm?s>&1qW)TTcGzRAK2-F>}$&+^hnSX{_SaO2?`> zR`KFRkkXn1P_mj&_Xaaa!3`lOybNUwr5+vS6sbk$Q^Ul7eza%X}e*r9!d6Nl=}FUC;L;g#Lus1SUjjU6QLP ztc2m;?QdmM4nUa`K2twazs=0TAM|Lvn{(&?0d1Z)d5^YrrQMx14i#Qi?GSOps;UL$ z-GISa;XhEG=p(0XkaaLl*Y8n_o5&~(E6I>k)Trb{t! z(#hI&A|Zy!egs$xJOZ%#i0pK(LiXCUqWX^=FIjd#H#FT$C~B_;(srF;rLYO^QSa9H z>(;S<1^&VDX8}${`7QfR&JKElynfTYcP@W- z7EX|k{I>lHaNUvLwO^GilW1v2e(!|*+d)TuAO1HUaO@4xyNgzeB>Kga1@Wz>{fuK_ z`*lTCYM|tn*Z8do*p;`)Z_Dq<@5*8My)0E5v|rOj(>$aZ0FL~-j(^Lj<0X58DQkzq zl>z&8_JT@<8QYz#MIEvOJbye78@hZ=yJ%7!le$Yi_=EkMQg>C0lpM?gK%!k|O3;2n z;y&54x1+nOecN`phs22zQTj2YZH)!L?Ihs0n%(v~g457Z63idhgI5Pl9Xd?@ytY?M z!4bxPpl95KPm^u?y5oB~DGKZDbrQ6zpn`Vwj{Qz!tZ_@YAr@|EiNij@{yvN~!wvAq c(qW_UCFKswUegZ;B)封装的网页抓取工具,输入 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 0000000000000000000000000000000000000000..982e0ce4fa062bbe35214cdecfe09f9205b17e5a GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0B~Ajh-%!Ar-gY-Z;p4KtX^tP})cJ zTfM26f}WaIeW`lI*-sq8j+ZBQAD)_Uu!;4x{p-6Q4g_s{*4@bZ+T_}k_2NCQ(TtP( z&o0s}t1PR$6?G;{T40N5kw@XdtoV{EV(go8<$aEAV%Lg1<`WtHE#m3bj^Jy(;-0Ht j{d0NZB`&5DaW|APXOUT^a&a>=&@l|2u6{1-oD!M literal 0 HcmV?d00001