sdpy-kit 0.3.0__tar.gz → 0.3.1__tar.gz
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.
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/PKG-INFO +86 -4
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/README.md +82 -3
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/__init__.py +1 -1
- sdpy_kit-0.3.1/kit/article_models.py +162 -0
- sdpy_kit-0.3.1/kit/web_article_kit.py +784 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/wx_article_kit.py +37 -75
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/pyproject.toml +3 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/.gitignore +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/ai/__init__.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/ai/base_service.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/ai/chat_service.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/ai/embedding_service.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/ai/exceptions.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/ai/failover.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/ai/image_service.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/ai/model_builder.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/ai/model_client.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/ai/pool.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/ai/profiles.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/ai/rerank_service.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/ai/runner.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/ai/types.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/anydoc_kit.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/common_kit.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/config/__init__.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/config/kit_config.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/config/log_config.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/db/__init__.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/db/base_db_kit.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/db/crud.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/db/engine.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/db/exceptions.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/db/models/__init__.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/db/models/base.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/db/mysql_kit.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/db/pg_kit.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/db/query.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/db/session.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/db/sqlite_kit.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/db/transaction.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/docx_kit.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/file_kit.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/laya_kit.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/marker_kit.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/mcp_kit.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/md2doc_kit.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/mineru_kit.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/redis_kit.py +0 -0
- {sdpy_kit-0.3.0 → sdpy_kit-0.3.1}/kit/zvec_kit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sdpy-kit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/MCP/Redis/文件/文档/向量工具集
|
|
5
5
|
Author-email: Clark <changhongyuan@126.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -42,6 +42,7 @@ Requires-Dist: python-dotenv>=1.2.4; extra == 'all'
|
|
|
42
42
|
Requires-Dist: redis>=8.1.0; extra == 'all'
|
|
43
43
|
Requires-Dist: requests>=2.34.2; extra == 'all'
|
|
44
44
|
Requires-Dist: sqliteai-vector>=1.1.2; extra == 'all'
|
|
45
|
+
Requires-Dist: trafilatura>=2.3.1; extra == 'all'
|
|
45
46
|
Requires-Dist: zvec>=0.7.0; extra == 'all'
|
|
46
47
|
Provides-Extra: anydoc
|
|
47
48
|
Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'anydoc'
|
|
@@ -69,6 +70,8 @@ Requires-Dist: redis>=8.1.0; extra == 'redis'
|
|
|
69
70
|
Provides-Extra: sqliteai
|
|
70
71
|
Requires-Dist: numpy>=2.5.4; extra == 'sqliteai'
|
|
71
72
|
Requires-Dist: sqliteai-vector>=1.1.2; extra == 'sqliteai'
|
|
73
|
+
Provides-Extra: web-article
|
|
74
|
+
Requires-Dist: trafilatura>=2.3.1; extra == 'web-article'
|
|
72
75
|
Provides-Extra: zvec
|
|
73
76
|
Requires-Dist: zvec>=0.7.0; extra == 'zvec'
|
|
74
77
|
Description-Content-Type: text/markdown
|
|
@@ -80,7 +83,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
80
83
|
- **统一数据库访问层**:PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致,切换数据库只改一行 import;
|
|
81
84
|
- **AI 多模型服务池**:chat / embedding / rerank / image 四类能力,多厂商候选链 + 故障自动转移;
|
|
82
85
|
- **MCP 客户端**:streamable-http / sse 双传输,档案驱动多服务器;
|
|
83
|
-
- **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(md + images) / 向量库 /
|
|
86
|
+
- **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(md + images) / Laya 类型化决策 / 向量库 / 微信公众号文章抓取存储 / 通用网页文章抓取存储。
|
|
84
87
|
|
|
85
88
|
> 要求 Python 3.13+
|
|
86
89
|
|
|
@@ -100,8 +103,11 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
100
103
|
| `kit.md2doc_kit` | `Md2DocKit` | Markdown → Word / PDF / HTML | `md2doc` |
|
|
101
104
|
| `kit.mineru_kit` | `MinerUKit` | MinerU 文档解析(精准 + Agent 轻量) | `mineru` |
|
|
102
105
|
| `kit.marker_kit` | `MarkerKit` | 本地文档解析(PDF / 图片 / Office → Markdown + Images) | `marker` |
|
|
106
|
+
| `kit.laya_kit` | `LayaKit` | 类型化决策:choice / score / noul,返回判断结果 + 置信度 | `laya` |
|
|
103
107
|
| `kit.zvec_kit` | `ZvecKit` | zvec 本地向量库 | `zvec` |
|
|
108
|
+
| `kit.article_models` | `ArticleInfo` / `ArticleResult` / `SavedOutput` 等 | 文章抓取通用模型(两 `*ArticleKit` 共用,含 `reason` 分类与非致命 `warnings`) | 无 |
|
|
104
109
|
| `kit.wx_article_kit` | `WxArticleKit` | 微信公众号文章 → Markdown + 图片转存 | 无 |
|
|
110
|
+
| `kit.web_article_kit` | `WebArticleKit` | 通用网页文章 → Markdown + 图片转存(**不含微信**,微信请用 `WxArticleKit`;明确支持头条) | `web-article` |
|
|
105
111
|
|
|
106
112
|
## 安装
|
|
107
113
|
|
|
@@ -127,6 +133,8 @@ uv add sdpy-kit
|
|
|
127
133
|
| `docx` | DocxKit 模板动态填充(docxtpl) | `pip install "sdpy-kit[docx]"` |
|
|
128
134
|
| `mineru` | MinerUKit 解析 API(精准解析 + Agent 轻量解析) | `pip install "sdpy-kit[mineru]"` |
|
|
129
135
|
| `marker` | MarkerKit 本地文档解析(marker-pdf[full] → Markdown + Images,依赖体积大) | `pip install "sdpy-kit[marker]"` |
|
|
136
|
+
| `laya` | LayaKit 类型化决策(laya + torch,本地小模型 choice / score / noul) | `pip install "sdpy-kit[laya]"` |
|
|
137
|
+
| `web-article` | WebArticleKit 通用网页文章抓取转存(trafilatura,纯 HTTP 无浏览器) | `pip install "sdpy-kit[web-article]"` |
|
|
130
138
|
| `all` | 全部能力 | `pip install "sdpy-kit[all]"` |
|
|
131
139
|
|
|
132
140
|
## 配置(.env)
|
|
@@ -177,11 +185,17 @@ SQLITE_DB_PATH=./data/sqlite.db
|
|
|
177
185
|
# MARKER_HF_ENDPOINT=https://hf-mirror.com
|
|
178
186
|
# MARKER_HF_DISABLE_XET=true
|
|
179
187
|
# MARKER_LLAMA_CPP_BINARY=
|
|
188
|
+
|
|
189
|
+
# Laya(类型化决策,可选依赖 sdpy-kit[laya])
|
|
190
|
+
# LAYA_MODEL=multilingual # multilingual / english / typed-decisions
|
|
191
|
+
# LAYA_DEVICE= # 留空由 laya 自动选择
|
|
192
|
+
# LAYA_HF_ENDPOINT=https://hf-mirror.com
|
|
193
|
+
# LAYA_HF_DISABLE_XET=true
|
|
180
194
|
```
|
|
181
195
|
|
|
182
196
|
### 环境变量 ↔ 能力对照表
|
|
183
197
|
|
|
184
|
-
> 表中未出现的模块(`kit.common_kit`、`kit.docx_kit`、`kit.md2doc_kit`、`kit.wx_article_kit`)不读取任何环境变量。
|
|
198
|
+
> 表中未出现的模块(`kit.article_models`、`kit.common_kit`、`kit.docx_kit`、`kit.md2doc_kit`、`kit.wx_article_kit`、`kit.web_article_kit`)不读取任何环境变量。
|
|
185
199
|
|
|
186
200
|
**基础与日志**(影响 `kit.config` 及所有模块的日志输出)
|
|
187
201
|
|
|
@@ -274,6 +288,18 @@ SQLITE_DB_PATH=./data/sqlite.db
|
|
|
274
288
|
|
|
275
289
|
> 这四项由 kit 在**导入 marker 之前**经 `os.environ.setdefault` 应用:仅当用户未显式设置对应环境变量时生效,不覆盖显式配置。
|
|
276
290
|
|
|
291
|
+
**Laya** → `kit.laya_kit`
|
|
292
|
+
|
|
293
|
+
| 环境变量 | 默认值 | 作用 |
|
|
294
|
+
|----------|--------|------|
|
|
295
|
+
| `LAYA_MODEL` | `multilingual` | 检查点:`multilingual`(多语言含中文)/ `english` / `typed-decisions` |
|
|
296
|
+
| `LAYA_DEVICE` | 无(不写入 config) | 推理设备(`cpu` / `cuda`);不配置则由 laya 自动选择 |
|
|
297
|
+
| `LAYA_HF_ENDPOINT` | `https://hf-mirror.com` | 检查点权重下载入口(国内镜像);置空则用 HuggingFace 官方源 |
|
|
298
|
+
| `LAYA_HF_DISABLE_XET` | `true` | 禁用 HF Xet 存储后端(国内镜像不支持,开启会导致权重下载挂起) |
|
|
299
|
+
|
|
300
|
+
> `LAYA_HF_*` 同样由 kit 在**导入 laya 之前**经 `os.environ.setdefault` 应用,不覆盖显式配置;
|
|
301
|
+
> `LAYA_MODEL` / `LAYA_DEVICE` 由 `Settings` 读取(进程内缓存,改后需重启进程)。
|
|
302
|
+
|
|
277
303
|
**云 OCR**(任何 `ocr="hosted"` 的转换)→ `kit.anydoc_kit`
|
|
278
304
|
|
|
279
305
|
| 环境变量 | 默认值 | 作用 |
|
|
@@ -306,6 +332,7 @@ SQLITE_DB_PATH=./data/sqlite.db
|
|
|
306
332
|
|----------|----------|
|
|
307
333
|
| `LOG_*`、`DB_*`、`MYSQL_*`、`REDIS_*`、`FILE_STORAGE_ROOT`、`ZVEC_*`、`MINERU_*`、`MARKER_MODE` | `get_settings()` 带 `lru_cache`,进程内首次读取后固定 → **重启进程**(或调用 `AI.reload_config()`,它会顺带清空该缓存) |
|
|
308
334
|
| `MARKER_HF_ENDPOINT` / `MARKER_HF_DISABLE_XET` / `MARKER_LLAMA_CPP_BINARY` | 仅在 `import kit.marker_kit` 时经 `os.environ.setdefault` 应用 → 改动需**重启进程**重新导入模块 |
|
|
335
|
+
| `LAYA_HF_ENDPOINT` / `LAYA_HF_DISABLE_XET` | 仅在 `import kit.laya_kit` 时经 `os.environ.setdefault` 应用 → 改动需**重启进程**重新导入模块 |
|
|
309
336
|
| AI 密钥写在 `.env` 里 | `.env` 只在加载档案时注入一次 → 重启,或 `AI.reload_config(reload_file=True)` |
|
|
310
337
|
| AI 密钥由自己写入 `os.environ` | `AI.reload_config()`(默认 `reload_file=False`,只重读 `os.environ`) |
|
|
311
338
|
| `ai_models.json`(增删档案、换 model) | `AI.reload_config(reload_file=True)` |
|
|
@@ -1118,7 +1145,7 @@ WxArticleKit.save(url, "./output", "12345", write_meta=True)
|
|
|
1118
1145
|
|
|
1119
1146
|
```
|
|
1120
1147
|
<output_dir>/<file_name>/
|
|
1121
|
-
├── <file_name>.md # 正文 Markdown(YAML 头:title / author / publish_time / source_url)
|
|
1148
|
+
├── <file_name>.md # 正文 Markdown(YAML 头:title / author / publish_time / source / source_url)
|
|
1122
1149
|
├── images/ # 图片转存(NNN.ext,正文引用已本地化)
|
|
1123
1150
|
└── meta.json # 可选:结构化元信息(write_meta=True 时产出)
|
|
1124
1151
|
```
|
|
@@ -1127,10 +1154,65 @@ WxArticleKit.save(url, "./output", "12345", write_meta=True)
|
|
|
1127
1154
|
`ArticleInfo.publish_time` 与 `meta.json` 内仍为 ISO 8601(结构化数据)。
|
|
1128
1155
|
- 无登录态抓取:命中微信风控(环境异常 / 访问过于频繁)时返回 `ok=False` + 原因,不破解验证码;
|
|
1129
1156
|
抓取或图片下载失败不抛异常,单张图片失败时正文保留原远程 URL。
|
|
1157
|
+
- 模型与 `WebArticleKit` 共用 `kit.article_models`(`ArticleInfo` 含 `source` / `site_host`);
|
|
1158
|
+
失败时 `reason` 为类型化分类(微信常用 `risk_control` / `no_content` / `fetch_failed`)。
|
|
1130
1159
|
- 同名子目录**覆盖写**(不追加序号);`file_name` 带 `.md` 后缀会自动去除,为空则抛 `ValueError`。
|
|
1131
1160
|
|
|
1132
1161
|
---
|
|
1133
1162
|
|
|
1163
|
+
## kit.web_article_kit —— 通用网页文章抓取存储
|
|
1164
|
+
|
|
1165
|
+
可选依赖(`trafilatura`),需安装 `pip install "sdpy-kit[web-article]"`,按需 import:
|
|
1166
|
+
|
|
1167
|
+
```python
|
|
1168
|
+
from kit.web_article_kit import WebArticleKit
|
|
1169
|
+
|
|
1170
|
+
# 抓取解析(不落盘)
|
|
1171
|
+
result = WebArticleKit.fetch("https://blog.csdn.net/xxx/article/details/123")
|
|
1172
|
+
if result.ok:
|
|
1173
|
+
print(result.info.source, result.info.title, result.info.publish_time)
|
|
1174
|
+
|
|
1175
|
+
# 抓取并存储:<output_dir>/<file_name>/ 下 <file_name>.md + images/
|
|
1176
|
+
saved = WebArticleKit.save(url, output_dir="./output", file_name="12345")
|
|
1177
|
+
print(saved.md_path, saved.images_saved)
|
|
1178
|
+
|
|
1179
|
+
# 需要结构化元信息时再产出 meta.json(默认不产出)
|
|
1180
|
+
WebArticleKit.save(url, "./output", "12345", write_meta=True)
|
|
1181
|
+
```
|
|
1182
|
+
|
|
1183
|
+
| 方法 | 说明 |
|
|
1184
|
+
|------|------|
|
|
1185
|
+
| `fetch(url, timeout=20.0) -> ArticleResult` | 抓取并解析正文(不落盘);结果对象字段 `ok` / `error` / `info`(`ArticleInfo`)/ `markdown` |
|
|
1186
|
+
| `save(url, output_dir, file_name, write_meta=False, timeout=20.0) -> SavedOutput` | 抓取并存储到 `<output_dir>/<file_name>/`;字段与 `WxArticleKit.save` 一致 |
|
|
1187
|
+
|
|
1188
|
+
落盘结构与 `WxArticleKit` 完全一致(`<file_name>.md` + `images/`,`write_meta=True` 时多出 `meta.json`);
|
|
1189
|
+
`<file_name>.md` 的 YAML 头为 `title / author / publish_time / source / source_url`(`publish_time` 渲染为中文日期)。
|
|
1190
|
+
|
|
1191
|
+
**站点覆盖范围**(规则表只允许「转交」与「接口适配」两类条目,不做逐站 DOM 补丁):
|
|
1192
|
+
|
|
1193
|
+
| 站点类型 | 行为 |
|
|
1194
|
+
|---|---|
|
|
1195
|
+
| 通用静态 / 服务端渲染页面(博客、文档站、CSDN 等) | ✅ 直接抽取 |
|
|
1196
|
+
| 头条(文章页为 JS 空壳) | ✅ **明确支持**:走移动端 JSON 接口 |
|
|
1197
|
+
| 微信公众号 | ❌ **不覆盖**:返回提示,请改用 `kit.wx_article_kit.WxArticleKit`(该来源有专属解析,二者各自独立) |
|
|
1198
|
+
| 正文依赖 JavaScript 动态渲染或需登录态的站点 | ❌ 失败并给出**类型化说明**(不维护域名名单) |
|
|
1199
|
+
|
|
1200
|
+
`ArticleInfo` 为平台中立的通用字段集:`url` / `title` / `author` / `publish_time`(ISO 8601)/ `source`
|
|
1201
|
+
(来源平台名)/ `site_host` / `summary` / `images` / `links` / `code_blocks`,便于多平台抓取后写入同一张表;
|
|
1202
|
+
`to_dict()` 可直接 JSON 化(即 `meta.json` 的内容)。模型定义在 `kit.article_models`,与
|
|
1203
|
+
`WxArticleKit` **共用同一套**。
|
|
1204
|
+
|
|
1205
|
+
质量信号(均不改变成功语义):
|
|
1206
|
+
|
|
1207
|
+
| 字段 | 取值 / 含义 |
|
|
1208
|
+
|---|---|
|
|
1209
|
+
| `reason` | 失败时的类型化分类:`unsupported_source` / `fetch_blocked` / `fetch_failed` / `no_content` / `risk_control`(成功为空串) |
|
|
1210
|
+
| `warnings` | **非致命**抽取自检提示(`tuple[str, ...]`):正文疑似过短、页面含图片但产物无图;同时写入 warning 日志 |
|
|
1211
|
+
|
|
1212
|
+
标题统一去除站点后缀(如 CSDN 的 `标题-CSDN博客`);刻意不做「与页面 h1 择优」(h1 可能是导航文案)。
|
|
1213
|
+
|
|
1214
|
+
---
|
|
1215
|
+
|
|
1134
1216
|
## AI / MCP 档案文件
|
|
1135
1217
|
|
|
1136
1218
|
AI 与 MCP 功能需要在项目根目录提供两个 JSON 档案文件:
|
|
@@ -5,7 +5,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
5
5
|
- **统一数据库访问层**:PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致,切换数据库只改一行 import;
|
|
6
6
|
- **AI 多模型服务池**:chat / embedding / rerank / image 四类能力,多厂商候选链 + 故障自动转移;
|
|
7
7
|
- **MCP 客户端**:streamable-http / sse 双传输,档案驱动多服务器;
|
|
8
|
-
- **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(md + images) / 向量库 /
|
|
8
|
+
- **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(md + images) / Laya 类型化决策 / 向量库 / 微信公众号文章抓取存储 / 通用网页文章抓取存储。
|
|
9
9
|
|
|
10
10
|
> 要求 Python 3.13+
|
|
11
11
|
|
|
@@ -25,8 +25,11 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
25
25
|
| `kit.md2doc_kit` | `Md2DocKit` | Markdown → Word / PDF / HTML | `md2doc` |
|
|
26
26
|
| `kit.mineru_kit` | `MinerUKit` | MinerU 文档解析(精准 + Agent 轻量) | `mineru` |
|
|
27
27
|
| `kit.marker_kit` | `MarkerKit` | 本地文档解析(PDF / 图片 / Office → Markdown + Images) | `marker` |
|
|
28
|
+
| `kit.laya_kit` | `LayaKit` | 类型化决策:choice / score / noul,返回判断结果 + 置信度 | `laya` |
|
|
28
29
|
| `kit.zvec_kit` | `ZvecKit` | zvec 本地向量库 | `zvec` |
|
|
30
|
+
| `kit.article_models` | `ArticleInfo` / `ArticleResult` / `SavedOutput` 等 | 文章抓取通用模型(两 `*ArticleKit` 共用,含 `reason` 分类与非致命 `warnings`) | 无 |
|
|
29
31
|
| `kit.wx_article_kit` | `WxArticleKit` | 微信公众号文章 → Markdown + 图片转存 | 无 |
|
|
32
|
+
| `kit.web_article_kit` | `WebArticleKit` | 通用网页文章 → Markdown + 图片转存(**不含微信**,微信请用 `WxArticleKit`;明确支持头条) | `web-article` |
|
|
30
33
|
|
|
31
34
|
## 安装
|
|
32
35
|
|
|
@@ -52,6 +55,8 @@ uv add sdpy-kit
|
|
|
52
55
|
| `docx` | DocxKit 模板动态填充(docxtpl) | `pip install "sdpy-kit[docx]"` |
|
|
53
56
|
| `mineru` | MinerUKit 解析 API(精准解析 + Agent 轻量解析) | `pip install "sdpy-kit[mineru]"` |
|
|
54
57
|
| `marker` | MarkerKit 本地文档解析(marker-pdf[full] → Markdown + Images,依赖体积大) | `pip install "sdpy-kit[marker]"` |
|
|
58
|
+
| `laya` | LayaKit 类型化决策(laya + torch,本地小模型 choice / score / noul) | `pip install "sdpy-kit[laya]"` |
|
|
59
|
+
| `web-article` | WebArticleKit 通用网页文章抓取转存(trafilatura,纯 HTTP 无浏览器) | `pip install "sdpy-kit[web-article]"` |
|
|
55
60
|
| `all` | 全部能力 | `pip install "sdpy-kit[all]"` |
|
|
56
61
|
|
|
57
62
|
## 配置(.env)
|
|
@@ -102,11 +107,17 @@ SQLITE_DB_PATH=./data/sqlite.db
|
|
|
102
107
|
# MARKER_HF_ENDPOINT=https://hf-mirror.com
|
|
103
108
|
# MARKER_HF_DISABLE_XET=true
|
|
104
109
|
# MARKER_LLAMA_CPP_BINARY=
|
|
110
|
+
|
|
111
|
+
# Laya(类型化决策,可选依赖 sdpy-kit[laya])
|
|
112
|
+
# LAYA_MODEL=multilingual # multilingual / english / typed-decisions
|
|
113
|
+
# LAYA_DEVICE= # 留空由 laya 自动选择
|
|
114
|
+
# LAYA_HF_ENDPOINT=https://hf-mirror.com
|
|
115
|
+
# LAYA_HF_DISABLE_XET=true
|
|
105
116
|
```
|
|
106
117
|
|
|
107
118
|
### 环境变量 ↔ 能力对照表
|
|
108
119
|
|
|
109
|
-
> 表中未出现的模块(`kit.common_kit`、`kit.docx_kit`、`kit.md2doc_kit`、`kit.wx_article_kit`)不读取任何环境变量。
|
|
120
|
+
> 表中未出现的模块(`kit.article_models`、`kit.common_kit`、`kit.docx_kit`、`kit.md2doc_kit`、`kit.wx_article_kit`、`kit.web_article_kit`)不读取任何环境变量。
|
|
110
121
|
|
|
111
122
|
**基础与日志**(影响 `kit.config` 及所有模块的日志输出)
|
|
112
123
|
|
|
@@ -199,6 +210,18 @@ SQLITE_DB_PATH=./data/sqlite.db
|
|
|
199
210
|
|
|
200
211
|
> 这四项由 kit 在**导入 marker 之前**经 `os.environ.setdefault` 应用:仅当用户未显式设置对应环境变量时生效,不覆盖显式配置。
|
|
201
212
|
|
|
213
|
+
**Laya** → `kit.laya_kit`
|
|
214
|
+
|
|
215
|
+
| 环境变量 | 默认值 | 作用 |
|
|
216
|
+
|----------|--------|------|
|
|
217
|
+
| `LAYA_MODEL` | `multilingual` | 检查点:`multilingual`(多语言含中文)/ `english` / `typed-decisions` |
|
|
218
|
+
| `LAYA_DEVICE` | 无(不写入 config) | 推理设备(`cpu` / `cuda`);不配置则由 laya 自动选择 |
|
|
219
|
+
| `LAYA_HF_ENDPOINT` | `https://hf-mirror.com` | 检查点权重下载入口(国内镜像);置空则用 HuggingFace 官方源 |
|
|
220
|
+
| `LAYA_HF_DISABLE_XET` | `true` | 禁用 HF Xet 存储后端(国内镜像不支持,开启会导致权重下载挂起) |
|
|
221
|
+
|
|
222
|
+
> `LAYA_HF_*` 同样由 kit 在**导入 laya 之前**经 `os.environ.setdefault` 应用,不覆盖显式配置;
|
|
223
|
+
> `LAYA_MODEL` / `LAYA_DEVICE` 由 `Settings` 读取(进程内缓存,改后需重启进程)。
|
|
224
|
+
|
|
202
225
|
**云 OCR**(任何 `ocr="hosted"` 的转换)→ `kit.anydoc_kit`
|
|
203
226
|
|
|
204
227
|
| 环境变量 | 默认值 | 作用 |
|
|
@@ -231,6 +254,7 @@ SQLITE_DB_PATH=./data/sqlite.db
|
|
|
231
254
|
|----------|----------|
|
|
232
255
|
| `LOG_*`、`DB_*`、`MYSQL_*`、`REDIS_*`、`FILE_STORAGE_ROOT`、`ZVEC_*`、`MINERU_*`、`MARKER_MODE` | `get_settings()` 带 `lru_cache`,进程内首次读取后固定 → **重启进程**(或调用 `AI.reload_config()`,它会顺带清空该缓存) |
|
|
233
256
|
| `MARKER_HF_ENDPOINT` / `MARKER_HF_DISABLE_XET` / `MARKER_LLAMA_CPP_BINARY` | 仅在 `import kit.marker_kit` 时经 `os.environ.setdefault` 应用 → 改动需**重启进程**重新导入模块 |
|
|
257
|
+
| `LAYA_HF_ENDPOINT` / `LAYA_HF_DISABLE_XET` | 仅在 `import kit.laya_kit` 时经 `os.environ.setdefault` 应用 → 改动需**重启进程**重新导入模块 |
|
|
234
258
|
| AI 密钥写在 `.env` 里 | `.env` 只在加载档案时注入一次 → 重启,或 `AI.reload_config(reload_file=True)` |
|
|
235
259
|
| AI 密钥由自己写入 `os.environ` | `AI.reload_config()`(默认 `reload_file=False`,只重读 `os.environ`) |
|
|
236
260
|
| `ai_models.json`(增删档案、换 model) | `AI.reload_config(reload_file=True)` |
|
|
@@ -1043,7 +1067,7 @@ WxArticleKit.save(url, "./output", "12345", write_meta=True)
|
|
|
1043
1067
|
|
|
1044
1068
|
```
|
|
1045
1069
|
<output_dir>/<file_name>/
|
|
1046
|
-
├── <file_name>.md # 正文 Markdown(YAML 头:title / author / publish_time / source_url)
|
|
1070
|
+
├── <file_name>.md # 正文 Markdown(YAML 头:title / author / publish_time / source / source_url)
|
|
1047
1071
|
├── images/ # 图片转存(NNN.ext,正文引用已本地化)
|
|
1048
1072
|
└── meta.json # 可选:结构化元信息(write_meta=True 时产出)
|
|
1049
1073
|
```
|
|
@@ -1052,10 +1076,65 @@ WxArticleKit.save(url, "./output", "12345", write_meta=True)
|
|
|
1052
1076
|
`ArticleInfo.publish_time` 与 `meta.json` 内仍为 ISO 8601(结构化数据)。
|
|
1053
1077
|
- 无登录态抓取:命中微信风控(环境异常 / 访问过于频繁)时返回 `ok=False` + 原因,不破解验证码;
|
|
1054
1078
|
抓取或图片下载失败不抛异常,单张图片失败时正文保留原远程 URL。
|
|
1079
|
+
- 模型与 `WebArticleKit` 共用 `kit.article_models`(`ArticleInfo` 含 `source` / `site_host`);
|
|
1080
|
+
失败时 `reason` 为类型化分类(微信常用 `risk_control` / `no_content` / `fetch_failed`)。
|
|
1055
1081
|
- 同名子目录**覆盖写**(不追加序号);`file_name` 带 `.md` 后缀会自动去除,为空则抛 `ValueError`。
|
|
1056
1082
|
|
|
1057
1083
|
---
|
|
1058
1084
|
|
|
1085
|
+
## kit.web_article_kit —— 通用网页文章抓取存储
|
|
1086
|
+
|
|
1087
|
+
可选依赖(`trafilatura`),需安装 `pip install "sdpy-kit[web-article]"`,按需 import:
|
|
1088
|
+
|
|
1089
|
+
```python
|
|
1090
|
+
from kit.web_article_kit import WebArticleKit
|
|
1091
|
+
|
|
1092
|
+
# 抓取解析(不落盘)
|
|
1093
|
+
result = WebArticleKit.fetch("https://blog.csdn.net/xxx/article/details/123")
|
|
1094
|
+
if result.ok:
|
|
1095
|
+
print(result.info.source, result.info.title, result.info.publish_time)
|
|
1096
|
+
|
|
1097
|
+
# 抓取并存储:<output_dir>/<file_name>/ 下 <file_name>.md + images/
|
|
1098
|
+
saved = WebArticleKit.save(url, output_dir="./output", file_name="12345")
|
|
1099
|
+
print(saved.md_path, saved.images_saved)
|
|
1100
|
+
|
|
1101
|
+
# 需要结构化元信息时再产出 meta.json(默认不产出)
|
|
1102
|
+
WebArticleKit.save(url, "./output", "12345", write_meta=True)
|
|
1103
|
+
```
|
|
1104
|
+
|
|
1105
|
+
| 方法 | 说明 |
|
|
1106
|
+
|------|------|
|
|
1107
|
+
| `fetch(url, timeout=20.0) -> ArticleResult` | 抓取并解析正文(不落盘);结果对象字段 `ok` / `error` / `info`(`ArticleInfo`)/ `markdown` |
|
|
1108
|
+
| `save(url, output_dir, file_name, write_meta=False, timeout=20.0) -> SavedOutput` | 抓取并存储到 `<output_dir>/<file_name>/`;字段与 `WxArticleKit.save` 一致 |
|
|
1109
|
+
|
|
1110
|
+
落盘结构与 `WxArticleKit` 完全一致(`<file_name>.md` + `images/`,`write_meta=True` 时多出 `meta.json`);
|
|
1111
|
+
`<file_name>.md` 的 YAML 头为 `title / author / publish_time / source / source_url`(`publish_time` 渲染为中文日期)。
|
|
1112
|
+
|
|
1113
|
+
**站点覆盖范围**(规则表只允许「转交」与「接口适配」两类条目,不做逐站 DOM 补丁):
|
|
1114
|
+
|
|
1115
|
+
| 站点类型 | 行为 |
|
|
1116
|
+
|---|---|
|
|
1117
|
+
| 通用静态 / 服务端渲染页面(博客、文档站、CSDN 等) | ✅ 直接抽取 |
|
|
1118
|
+
| 头条(文章页为 JS 空壳) | ✅ **明确支持**:走移动端 JSON 接口 |
|
|
1119
|
+
| 微信公众号 | ❌ **不覆盖**:返回提示,请改用 `kit.wx_article_kit.WxArticleKit`(该来源有专属解析,二者各自独立) |
|
|
1120
|
+
| 正文依赖 JavaScript 动态渲染或需登录态的站点 | ❌ 失败并给出**类型化说明**(不维护域名名单) |
|
|
1121
|
+
|
|
1122
|
+
`ArticleInfo` 为平台中立的通用字段集:`url` / `title` / `author` / `publish_time`(ISO 8601)/ `source`
|
|
1123
|
+
(来源平台名)/ `site_host` / `summary` / `images` / `links` / `code_blocks`,便于多平台抓取后写入同一张表;
|
|
1124
|
+
`to_dict()` 可直接 JSON 化(即 `meta.json` 的内容)。模型定义在 `kit.article_models`,与
|
|
1125
|
+
`WxArticleKit` **共用同一套**。
|
|
1126
|
+
|
|
1127
|
+
质量信号(均不改变成功语义):
|
|
1128
|
+
|
|
1129
|
+
| 字段 | 取值 / 含义 |
|
|
1130
|
+
|---|---|
|
|
1131
|
+
| `reason` | 失败时的类型化分类:`unsupported_source` / `fetch_blocked` / `fetch_failed` / `no_content` / `risk_control`(成功为空串) |
|
|
1132
|
+
| `warnings` | **非致命**抽取自检提示(`tuple[str, ...]`):正文疑似过短、页面含图片但产物无图;同时写入 warning 日志 |
|
|
1133
|
+
|
|
1134
|
+
标题统一去除站点后缀(如 CSDN 的 `标题-CSDN博客`);刻意不做「与页面 h1 择优」(h1 可能是导航文案)。
|
|
1135
|
+
|
|
1136
|
+
---
|
|
1137
|
+
|
|
1059
1138
|
## AI / MCP 档案文件
|
|
1060
1139
|
|
|
1061
1140
|
AI 与 MCP 功能需要在项目根目录提供两个 JSON 档案文件:
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2021-2026 Clark Chang. All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
|
|
16
|
+
Project: sdpy
|
|
17
|
+
Author: Clark Chang
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# date: 2026-10-14
|
|
21
|
+
"""文章抓取通用模型(多平台共用,零第三方依赖)
|
|
22
|
+
|
|
23
|
+
`kit.wx_article_kit.WxArticleKit`(微信公众号)与 `kit.web_article_kit.WebArticleKit`
|
|
24
|
+
(通用网页)共用同一套模型,便于**多平台抓取后写入同一张表**:
|
|
25
|
+
|
|
26
|
+
- `ArticleImage` / `ArticleLink` / `CodeBlock`:正文清单条目;
|
|
27
|
+
- `ArticleInfo`:平台中立的通用字段集(`publish_time` 保持 ISO 8601,
|
|
28
|
+
仅落盘 YAML 头渲染为中文日期);
|
|
29
|
+
- `ArticleResult`:`fetch()` 的非落盘结果;
|
|
30
|
+
- `SavedOutput`:`save()` 的落盘结果。
|
|
31
|
+
|
|
32
|
+
失败原因(`ArticleResult.reason` / `SavedOutput.reason`)取值:
|
|
33
|
+
|
|
34
|
+
| 取值 | 含义 |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `""` | 成功 |
|
|
37
|
+
| `unsupported_source` | 该来源有专属工具类(如微信 → `WxArticleKit`) |
|
|
38
|
+
| `fetch_blocked` | 抓取被站点拒绝(403 等):正文通常依赖 JavaScript 渲染或需登录态 |
|
|
39
|
+
| `fetch_failed` | 网络/超时等抓取失败 |
|
|
40
|
+
| `no_content` | 抓到页面但无法识别正文(非文章页或正文由 JavaScript 动态渲染) |
|
|
41
|
+
| `risk_control` | 命中站点风控特征(访问过于频繁/环境异常) |
|
|
42
|
+
|
|
43
|
+
`warnings` 为**非致命**自检提示(如「页面含图片但产物无图」「正文疑似过短」),
|
|
44
|
+
**不改变 `ok` 语义**:抓取成功一律 `ok=True`,提示仅供调用方判断质量。
|
|
45
|
+
|
|
46
|
+
使用方式(按需 import,无需经 kit 包顶层导出):
|
|
47
|
+
|
|
48
|
+
from kit.article_models import ArticleInfo, ArticleResult
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
from dataclasses import dataclass, field
|
|
52
|
+
from typing import Any, NamedTuple
|
|
53
|
+
|
|
54
|
+
# 失败原因取值(字符串常量,便于业务侧分支判断;成功为空串)
|
|
55
|
+
REASON_UNSUPPORTED_SOURCE = "unsupported_source"
|
|
56
|
+
REASON_FETCH_BLOCKED = "fetch_blocked"
|
|
57
|
+
REASON_FETCH_FAILED = "fetch_failed"
|
|
58
|
+
REASON_NO_CONTENT = "no_content"
|
|
59
|
+
REASON_RISK_CONTROL = "risk_control"
|
|
60
|
+
|
|
61
|
+
# 全部合法取值(便于校验收敛)
|
|
62
|
+
REASONS: tuple[str, ...] = (
|
|
63
|
+
"",
|
|
64
|
+
REASON_UNSUPPORTED_SOURCE,
|
|
65
|
+
REASON_FETCH_BLOCKED,
|
|
66
|
+
REASON_FETCH_FAILED,
|
|
67
|
+
REASON_NO_CONTENT,
|
|
68
|
+
REASON_RISK_CONTROL,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ArticleImage(NamedTuple):
|
|
73
|
+
"""图片条目(转存后地址 + 原始地址 + alt + 本地相对路径)"""
|
|
74
|
+
|
|
75
|
+
src: str
|
|
76
|
+
original: str
|
|
77
|
+
alt: str = ""
|
|
78
|
+
local: str = "" # 转存后的本地相对路径(如 images/001.png),失败为空
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ArticleLink(NamedTuple):
|
|
82
|
+
"""正文链接条目"""
|
|
83
|
+
|
|
84
|
+
text: str
|
|
85
|
+
href: str
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class CodeBlock(NamedTuple):
|
|
89
|
+
"""代码块条目"""
|
|
90
|
+
|
|
91
|
+
language: str
|
|
92
|
+
code: str
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class ArticleInfo:
|
|
97
|
+
"""文章元信息(平台中立的通用字段集,`to_dict()` 可 JSON 化后自行持久化)
|
|
98
|
+
|
|
99
|
+
字段说明:
|
|
100
|
+
|
|
101
|
+
- `url`:原文链接;
|
|
102
|
+
- `title` / `author` / `summary`:标题 / 作者 / 摘要,取不到时为空串;
|
|
103
|
+
- `publish_time`:ISO 8601(精度不足时退化为 `YYYY-MM-DD`),
|
|
104
|
+
落盘 YAML 头渲染中文日期;
|
|
105
|
+
- `source`:来源平台名(如「今日头条」「CSDN博客」「微信公众平台」);
|
|
106
|
+
- `site_host`:站点域名(DB 归类/去重辅助);
|
|
107
|
+
- `images` / `links` / `code_blocks`:正文清单(图片 `local` 在落盘阶段填充)。
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
url: str
|
|
111
|
+
title: str
|
|
112
|
+
author: str = ""
|
|
113
|
+
publish_time: str = ""
|
|
114
|
+
source: str = ""
|
|
115
|
+
site_host: str = ""
|
|
116
|
+
summary: str = ""
|
|
117
|
+
images: list[ArticleImage] = field(default_factory=list)
|
|
118
|
+
links: list[ArticleLink] = field(default_factory=list)
|
|
119
|
+
code_blocks: list[CodeBlock] = field(default_factory=list)
|
|
120
|
+
|
|
121
|
+
def to_dict(self) -> dict[str, Any]:
|
|
122
|
+
"""序列化为可 JSON 化的 dict"""
|
|
123
|
+
return {
|
|
124
|
+
"url": self.url,
|
|
125
|
+
"title": self.title,
|
|
126
|
+
"author": self.author,
|
|
127
|
+
"publish_time": self.publish_time,
|
|
128
|
+
"source": self.source,
|
|
129
|
+
"site_host": self.site_host,
|
|
130
|
+
"summary": self.summary,
|
|
131
|
+
"images": [img._asdict() for img in self.images],
|
|
132
|
+
"links": [link._asdict() for link in self.links],
|
|
133
|
+
"code_blocks": [code._asdict() for code in self.code_blocks],
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ArticleResult(NamedTuple):
|
|
138
|
+
"""fetch() 的非落盘结果
|
|
139
|
+
|
|
140
|
+
`reason` 见模块 docstring;`warnings` 为非致命自检提示(不可变元组)。
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
ok: bool
|
|
144
|
+
error: str = ""
|
|
145
|
+
info: ArticleInfo | None = None
|
|
146
|
+
markdown: str = ""
|
|
147
|
+
reason: str = ""
|
|
148
|
+
warnings: tuple[str, ...] = ()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class SavedOutput(NamedTuple):
|
|
152
|
+
"""save() 的落盘结果"""
|
|
153
|
+
|
|
154
|
+
ok: bool
|
|
155
|
+
error: str = ""
|
|
156
|
+
output_dir: str = "" # 实际写入的文章子目录(<output_dir>/<file_name>)
|
|
157
|
+
md_path: str = ""
|
|
158
|
+
images_dir: str = ""
|
|
159
|
+
title: str = ""
|
|
160
|
+
images_saved: int = 0 # 成功转存的图片张数
|
|
161
|
+
reason: str = ""
|
|
162
|
+
warnings: tuple[str, ...] = ()
|