sdpy-kit 0.2.6__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.2.6/README.md → sdpy_kit-0.3.1/PKG-INFO +160 -3
- sdpy_kit-0.2.6/PKG-INFO → sdpy_kit-0.3.1/README.md +82 -73
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/__init__.py +1 -1
- sdpy_kit-0.3.1/kit/article_models.py +162 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/config/kit_config.py +20 -0
- sdpy_kit-0.3.1/kit/laya_kit.py +302 -0
- sdpy_kit-0.3.1/kit/web_article_kit.py +784 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/wx_article_kit.py +37 -75
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/pyproject.toml +10 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/.gitignore +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/__init__.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/base_service.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/chat_service.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/embedding_service.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/exceptions.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/failover.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/image_service.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/model_builder.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/model_client.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/pool.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/profiles.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/rerank_service.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/runner.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/ai/types.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/anydoc_kit.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/common_kit.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/config/__init__.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/config/log_config.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/__init__.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/base_db_kit.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/crud.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/engine.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/exceptions.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/models/__init__.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/models/base.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/mysql_kit.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/pg_kit.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/query.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/session.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/sqlite_kit.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/db/transaction.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/docx_kit.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/file_kit.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/marker_kit.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/mcp_kit.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/md2doc_kit.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/mineru_kit.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/redis_kit.py +0 -0
- {sdpy_kit-0.2.6 → sdpy_kit-0.3.1}/kit/zvec_kit.py +0 -0
|
@@ -1,3 +1,81 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: sdpy-kit
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/MCP/Redis/文件/文档/向量工具集
|
|
5
|
+
Author-email: Clark <changhongyuan@126.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: ai,kit,mcp,mysql,postgresql,redis,sdpy,sqlalchemy,sqlite,sqlmodel,向量检索,工具集,开发工具,数据库
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Natural Language :: Chinese (Simplified)
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Database
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.13
|
|
20
|
+
Requires-Dist: pydantic-settings>=2.15.0
|
|
21
|
+
Requires-Dist: pydantic>=2.14.1
|
|
22
|
+
Requires-Dist: sqlalchemy~=2.0.54
|
|
23
|
+
Requires-Dist: sqlmodel>=0.0.48
|
|
24
|
+
Provides-Extra: ai
|
|
25
|
+
Requires-Dist: httpx>=0.28.1; extra == 'ai'
|
|
26
|
+
Requires-Dist: python-dotenv>=1.2.4; extra == 'ai'
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: docxtpl>=0.20.2; extra == 'all'
|
|
29
|
+
Requires-Dist: fastmcp>=4.1.0; extra == 'all'
|
|
30
|
+
Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'all'
|
|
31
|
+
Requires-Dist: httpx>=0.28.1; extra == 'all'
|
|
32
|
+
Requires-Dist: huggingface-hub<2,>=1.5; extra == 'all'
|
|
33
|
+
Requires-Dist: laya>=0.4.6; extra == 'all'
|
|
34
|
+
Requires-Dist: markdown-it-py>=4.2.0; extra == 'all'
|
|
35
|
+
Requires-Dist: marker-pdf[full]>=2.0.0; extra == 'all'
|
|
36
|
+
Requires-Dist: numpy>=2.5.4; extra == 'all'
|
|
37
|
+
Requires-Dist: pikepdf>=10.17.0; extra == 'all'
|
|
38
|
+
Requires-Dist: pimd[pdfa]>=2.2.5; extra == 'all'
|
|
39
|
+
Requires-Dist: psycopg2-binary>=2.9.13; extra == 'all'
|
|
40
|
+
Requires-Dist: pymysql>=1.2.3; extra == 'all'
|
|
41
|
+
Requires-Dist: python-dotenv>=1.2.4; extra == 'all'
|
|
42
|
+
Requires-Dist: redis>=8.1.0; extra == 'all'
|
|
43
|
+
Requires-Dist: requests>=2.34.2; extra == 'all'
|
|
44
|
+
Requires-Dist: sqliteai-vector>=1.1.2; extra == 'all'
|
|
45
|
+
Requires-Dist: trafilatura>=2.3.1; extra == 'all'
|
|
46
|
+
Requires-Dist: zvec>=0.7.0; extra == 'all'
|
|
47
|
+
Provides-Extra: anydoc
|
|
48
|
+
Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'anydoc'
|
|
49
|
+
Requires-Dist: pikepdf>=10.17.0; extra == 'anydoc'
|
|
50
|
+
Provides-Extra: docx
|
|
51
|
+
Requires-Dist: docxtpl>=0.20.2; extra == 'docx'
|
|
52
|
+
Provides-Extra: laya
|
|
53
|
+
Requires-Dist: huggingface-hub<2,>=1.5; extra == 'laya'
|
|
54
|
+
Requires-Dist: laya>=0.4.6; extra == 'laya'
|
|
55
|
+
Provides-Extra: marker
|
|
56
|
+
Requires-Dist: marker-pdf[full]>=2.0.0; extra == 'marker'
|
|
57
|
+
Provides-Extra: mcp
|
|
58
|
+
Requires-Dist: fastmcp>=4.1.0; extra == 'mcp'
|
|
59
|
+
Provides-Extra: md2doc
|
|
60
|
+
Requires-Dist: markdown-it-py>=4.2.0; extra == 'md2doc'
|
|
61
|
+
Requires-Dist: pimd[pdfa]>=2.2.5; extra == 'md2doc'
|
|
62
|
+
Provides-Extra: mineru
|
|
63
|
+
Requires-Dist: requests>=2.34.2; extra == 'mineru'
|
|
64
|
+
Provides-Extra: mysql
|
|
65
|
+
Requires-Dist: pymysql>=1.2.3; extra == 'mysql'
|
|
66
|
+
Provides-Extra: pg
|
|
67
|
+
Requires-Dist: psycopg2-binary>=2.9.13; extra == 'pg'
|
|
68
|
+
Provides-Extra: redis
|
|
69
|
+
Requires-Dist: redis>=8.1.0; extra == 'redis'
|
|
70
|
+
Provides-Extra: sqliteai
|
|
71
|
+
Requires-Dist: numpy>=2.5.4; extra == 'sqliteai'
|
|
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'
|
|
75
|
+
Provides-Extra: zvec
|
|
76
|
+
Requires-Dist: zvec>=0.7.0; extra == 'zvec'
|
|
77
|
+
Description-Content-Type: text/markdown
|
|
78
|
+
|
|
1
79
|
# sdpy-kit
|
|
2
80
|
|
|
3
81
|
SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
@@ -5,7 +83,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
5
83
|
- **统一数据库访问层**:PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致,切换数据库只改一行 import;
|
|
6
84
|
- **AI 多模型服务池**:chat / embedding / rerank / image 四类能力,多厂商候选链 + 故障自动转移;
|
|
7
85
|
- **MCP 客户端**:streamable-http / sse 双传输,档案驱动多服务器;
|
|
8
|
-
- **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(md + images) / 向量库 /
|
|
86
|
+
- **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(md + images) / Laya 类型化决策 / 向量库 / 微信公众号文章抓取存储 / 通用网页文章抓取存储。
|
|
9
87
|
|
|
10
88
|
> 要求 Python 3.13+
|
|
11
89
|
|
|
@@ -25,8 +103,11 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
25
103
|
| `kit.md2doc_kit` | `Md2DocKit` | Markdown → Word / PDF / HTML | `md2doc` |
|
|
26
104
|
| `kit.mineru_kit` | `MinerUKit` | MinerU 文档解析(精准 + Agent 轻量) | `mineru` |
|
|
27
105
|
| `kit.marker_kit` | `MarkerKit` | 本地文档解析(PDF / 图片 / Office → Markdown + Images) | `marker` |
|
|
106
|
+
| `kit.laya_kit` | `LayaKit` | 类型化决策:choice / score / noul,返回判断结果 + 置信度 | `laya` |
|
|
28
107
|
| `kit.zvec_kit` | `ZvecKit` | zvec 本地向量库 | `zvec` |
|
|
108
|
+
| `kit.article_models` | `ArticleInfo` / `ArticleResult` / `SavedOutput` 等 | 文章抓取通用模型(两 `*ArticleKit` 共用,含 `reason` 分类与非致命 `warnings`) | 无 |
|
|
29
109
|
| `kit.wx_article_kit` | `WxArticleKit` | 微信公众号文章 → Markdown + 图片转存 | 无 |
|
|
110
|
+
| `kit.web_article_kit` | `WebArticleKit` | 通用网页文章 → Markdown + 图片转存(**不含微信**,微信请用 `WxArticleKit`;明确支持头条) | `web-article` |
|
|
30
111
|
|
|
31
112
|
## 安装
|
|
32
113
|
|
|
@@ -52,6 +133,8 @@ uv add sdpy-kit
|
|
|
52
133
|
| `docx` | DocxKit 模板动态填充(docxtpl) | `pip install "sdpy-kit[docx]"` |
|
|
53
134
|
| `mineru` | MinerUKit 解析 API(精准解析 + Agent 轻量解析) | `pip install "sdpy-kit[mineru]"` |
|
|
54
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]"` |
|
|
55
138
|
| `all` | 全部能力 | `pip install "sdpy-kit[all]"` |
|
|
56
139
|
|
|
57
140
|
## 配置(.env)
|
|
@@ -102,11 +185,17 @@ SQLITE_DB_PATH=./data/sqlite.db
|
|
|
102
185
|
# MARKER_HF_ENDPOINT=https://hf-mirror.com
|
|
103
186
|
# MARKER_HF_DISABLE_XET=true
|
|
104
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
|
|
105
194
|
```
|
|
106
195
|
|
|
107
196
|
### 环境变量 ↔ 能力对照表
|
|
108
197
|
|
|
109
|
-
> 表中未出现的模块(`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`)不读取任何环境变量。
|
|
110
199
|
|
|
111
200
|
**基础与日志**(影响 `kit.config` 及所有模块的日志输出)
|
|
112
201
|
|
|
@@ -199,6 +288,18 @@ SQLITE_DB_PATH=./data/sqlite.db
|
|
|
199
288
|
|
|
200
289
|
> 这四项由 kit 在**导入 marker 之前**经 `os.environ.setdefault` 应用:仅当用户未显式设置对应环境变量时生效,不覆盖显式配置。
|
|
201
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
|
+
|
|
202
303
|
**云 OCR**(任何 `ocr="hosted"` 的转换)→ `kit.anydoc_kit`
|
|
203
304
|
|
|
204
305
|
| 环境变量 | 默认值 | 作用 |
|
|
@@ -231,6 +332,7 @@ SQLITE_DB_PATH=./data/sqlite.db
|
|
|
231
332
|
|----------|----------|
|
|
232
333
|
| `LOG_*`、`DB_*`、`MYSQL_*`、`REDIS_*`、`FILE_STORAGE_ROOT`、`ZVEC_*`、`MINERU_*`、`MARKER_MODE` | `get_settings()` 带 `lru_cache`,进程内首次读取后固定 → **重启进程**(或调用 `AI.reload_config()`,它会顺带清空该缓存) |
|
|
233
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` 应用 → 改动需**重启进程**重新导入模块 |
|
|
234
336
|
| AI 密钥写在 `.env` 里 | `.env` 只在加载档案时注入一次 → 重启,或 `AI.reload_config(reload_file=True)` |
|
|
235
337
|
| AI 密钥由自己写入 `os.environ` | `AI.reload_config()`(默认 `reload_file=False`,只重读 `os.environ`) |
|
|
236
338
|
| `ai_models.json`(增删档案、换 model) | `AI.reload_config(reload_file=True)` |
|
|
@@ -1043,7 +1145,7 @@ WxArticleKit.save(url, "./output", "12345", write_meta=True)
|
|
|
1043
1145
|
|
|
1044
1146
|
```
|
|
1045
1147
|
<output_dir>/<file_name>/
|
|
1046
|
-
├── <file_name>.md # 正文 Markdown(YAML 头:title / author / publish_time / source_url)
|
|
1148
|
+
├── <file_name>.md # 正文 Markdown(YAML 头:title / author / publish_time / source / source_url)
|
|
1047
1149
|
├── images/ # 图片转存(NNN.ext,正文引用已本地化)
|
|
1048
1150
|
└── meta.json # 可选:结构化元信息(write_meta=True 时产出)
|
|
1049
1151
|
```
|
|
@@ -1052,10 +1154,65 @@ WxArticleKit.save(url, "./output", "12345", write_meta=True)
|
|
|
1052
1154
|
`ArticleInfo.publish_time` 与 `meta.json` 内仍为 ISO 8601(结构化数据)。
|
|
1053
1155
|
- 无登录态抓取:命中微信风控(环境异常 / 访问过于频繁)时返回 `ok=False` + 原因,不破解验证码;
|
|
1054
1156
|
抓取或图片下载失败不抛异常,单张图片失败时正文保留原远程 URL。
|
|
1157
|
+
- 模型与 `WebArticleKit` 共用 `kit.article_models`(`ArticleInfo` 含 `source` / `site_host`);
|
|
1158
|
+
失败时 `reason` 为类型化分类(微信常用 `risk_control` / `no_content` / `fetch_failed`)。
|
|
1055
1159
|
- 同名子目录**覆盖写**(不追加序号);`file_name` 带 `.md` 后缀会自动去除,为空则抛 `ValueError`。
|
|
1056
1160
|
|
|
1057
1161
|
---
|
|
1058
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
|
+
|
|
1059
1216
|
## AI / MCP 档案文件
|
|
1060
1217
|
|
|
1061
1218
|
AI 与 MCP 功能需要在项目根目录提供两个 JSON 档案文件:
|
|
@@ -1,73 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: sdpy-kit
|
|
3
|
-
Version: 0.2.6
|
|
4
|
-
Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/MCP/Redis/文件/文档/向量工具集
|
|
5
|
-
Author-email: Clark <changhongyuan@126.com>
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
Keywords: ai,kit,mcp,mysql,postgresql,redis,sdpy,sqlalchemy,sqlite,sqlmodel,向量检索,工具集,开发工具,数据库
|
|
8
|
-
Classifier: Development Status :: 4 - Beta
|
|
9
|
-
Classifier: Intended Audience :: Developers
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Natural Language :: Chinese (Simplified)
|
|
12
|
-
Classifier: Operating System :: OS Independent
|
|
13
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
-
Classifier: Topic :: Database
|
|
17
|
-
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
-
Requires-Python: >=3.13
|
|
20
|
-
Requires-Dist: pydantic-settings>=2.15.0
|
|
21
|
-
Requires-Dist: pydantic>=2.14.1
|
|
22
|
-
Requires-Dist: sqlalchemy~=2.0.54
|
|
23
|
-
Requires-Dist: sqlmodel>=0.0.48
|
|
24
|
-
Provides-Extra: ai
|
|
25
|
-
Requires-Dist: httpx>=0.28.1; extra == 'ai'
|
|
26
|
-
Requires-Dist: python-dotenv>=1.2.4; extra == 'ai'
|
|
27
|
-
Provides-Extra: all
|
|
28
|
-
Requires-Dist: docxtpl>=0.20.2; extra == 'all'
|
|
29
|
-
Requires-Dist: fastmcp>=4.1.0; extra == 'all'
|
|
30
|
-
Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'all'
|
|
31
|
-
Requires-Dist: httpx>=0.28.1; extra == 'all'
|
|
32
|
-
Requires-Dist: markdown-it-py>=4.2.0; extra == 'all'
|
|
33
|
-
Requires-Dist: marker-pdf[full]>=2.0.0; extra == 'all'
|
|
34
|
-
Requires-Dist: numpy>=2.5.4; extra == 'all'
|
|
35
|
-
Requires-Dist: pikepdf>=10.17.0; extra == 'all'
|
|
36
|
-
Requires-Dist: pimd[pdfa]>=2.2.5; extra == 'all'
|
|
37
|
-
Requires-Dist: psycopg2-binary>=2.9.13; extra == 'all'
|
|
38
|
-
Requires-Dist: pymysql>=1.2.3; extra == 'all'
|
|
39
|
-
Requires-Dist: python-dotenv>=1.2.4; extra == 'all'
|
|
40
|
-
Requires-Dist: redis>=8.1.0; extra == 'all'
|
|
41
|
-
Requires-Dist: requests>=2.34.2; extra == 'all'
|
|
42
|
-
Requires-Dist: sqliteai-vector>=1.1.2; extra == 'all'
|
|
43
|
-
Requires-Dist: zvec>=0.7.0; extra == 'all'
|
|
44
|
-
Provides-Extra: anydoc
|
|
45
|
-
Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'anydoc'
|
|
46
|
-
Requires-Dist: pikepdf>=10.17.0; extra == 'anydoc'
|
|
47
|
-
Provides-Extra: docx
|
|
48
|
-
Requires-Dist: docxtpl>=0.20.2; extra == 'docx'
|
|
49
|
-
Provides-Extra: marker
|
|
50
|
-
Requires-Dist: marker-pdf[full]>=2.0.0; extra == 'marker'
|
|
51
|
-
Provides-Extra: mcp
|
|
52
|
-
Requires-Dist: fastmcp>=4.1.0; extra == 'mcp'
|
|
53
|
-
Provides-Extra: md2doc
|
|
54
|
-
Requires-Dist: markdown-it-py>=4.2.0; extra == 'md2doc'
|
|
55
|
-
Requires-Dist: pimd[pdfa]>=2.2.5; extra == 'md2doc'
|
|
56
|
-
Provides-Extra: mineru
|
|
57
|
-
Requires-Dist: requests>=2.34.2; extra == 'mineru'
|
|
58
|
-
Provides-Extra: mysql
|
|
59
|
-
Requires-Dist: pymysql>=1.2.3; extra == 'mysql'
|
|
60
|
-
Provides-Extra: pg
|
|
61
|
-
Requires-Dist: psycopg2-binary>=2.9.13; extra == 'pg'
|
|
62
|
-
Provides-Extra: redis
|
|
63
|
-
Requires-Dist: redis>=8.1.0; extra == 'redis'
|
|
64
|
-
Provides-Extra: sqliteai
|
|
65
|
-
Requires-Dist: numpy>=2.5.4; extra == 'sqliteai'
|
|
66
|
-
Requires-Dist: sqliteai-vector>=1.1.2; extra == 'sqliteai'
|
|
67
|
-
Provides-Extra: zvec
|
|
68
|
-
Requires-Dist: zvec>=0.7.0; extra == 'zvec'
|
|
69
|
-
Description-Content-Type: text/markdown
|
|
70
|
-
|
|
71
1
|
# sdpy-kit
|
|
72
2
|
|
|
73
3
|
SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
@@ -75,7 +5,7 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
75
5
|
- **统一数据库访问层**:PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致,切换数据库只改一行 import;
|
|
76
6
|
- **AI 多模型服务池**:chat / embedding / rerank / image 四类能力,多厂商候选链 + 故障自动转移;
|
|
77
7
|
- **MCP 客户端**:streamable-http / sse 双传输,档案驱动多服务器;
|
|
78
|
-
- **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(md + images) / 向量库 /
|
|
8
|
+
- **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(md + images) / Laya 类型化决策 / 向量库 / 微信公众号文章抓取存储 / 通用网页文章抓取存储。
|
|
79
9
|
|
|
80
10
|
> 要求 Python 3.13+
|
|
81
11
|
|
|
@@ -95,8 +25,11 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
95
25
|
| `kit.md2doc_kit` | `Md2DocKit` | Markdown → Word / PDF / HTML | `md2doc` |
|
|
96
26
|
| `kit.mineru_kit` | `MinerUKit` | MinerU 文档解析(精准 + Agent 轻量) | `mineru` |
|
|
97
27
|
| `kit.marker_kit` | `MarkerKit` | 本地文档解析(PDF / 图片 / Office → Markdown + Images) | `marker` |
|
|
28
|
+
| `kit.laya_kit` | `LayaKit` | 类型化决策:choice / score / noul,返回判断结果 + 置信度 | `laya` |
|
|
98
29
|
| `kit.zvec_kit` | `ZvecKit` | zvec 本地向量库 | `zvec` |
|
|
30
|
+
| `kit.article_models` | `ArticleInfo` / `ArticleResult` / `SavedOutput` 等 | 文章抓取通用模型(两 `*ArticleKit` 共用,含 `reason` 分类与非致命 `warnings`) | 无 |
|
|
99
31
|
| `kit.wx_article_kit` | `WxArticleKit` | 微信公众号文章 → Markdown + 图片转存 | 无 |
|
|
32
|
+
| `kit.web_article_kit` | `WebArticleKit` | 通用网页文章 → Markdown + 图片转存(**不含微信**,微信请用 `WxArticleKit`;明确支持头条) | `web-article` |
|
|
100
33
|
|
|
101
34
|
## 安装
|
|
102
35
|
|
|
@@ -122,6 +55,8 @@ uv add sdpy-kit
|
|
|
122
55
|
| `docx` | DocxKit 模板动态填充(docxtpl) | `pip install "sdpy-kit[docx]"` |
|
|
123
56
|
| `mineru` | MinerUKit 解析 API(精准解析 + Agent 轻量解析) | `pip install "sdpy-kit[mineru]"` |
|
|
124
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]"` |
|
|
125
60
|
| `all` | 全部能力 | `pip install "sdpy-kit[all]"` |
|
|
126
61
|
|
|
127
62
|
## 配置(.env)
|
|
@@ -172,11 +107,17 @@ SQLITE_DB_PATH=./data/sqlite.db
|
|
|
172
107
|
# MARKER_HF_ENDPOINT=https://hf-mirror.com
|
|
173
108
|
# MARKER_HF_DISABLE_XET=true
|
|
174
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
|
|
175
116
|
```
|
|
176
117
|
|
|
177
118
|
### 环境变量 ↔ 能力对照表
|
|
178
119
|
|
|
179
|
-
> 表中未出现的模块(`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`)不读取任何环境变量。
|
|
180
121
|
|
|
181
122
|
**基础与日志**(影响 `kit.config` 及所有模块的日志输出)
|
|
182
123
|
|
|
@@ -269,6 +210,18 @@ SQLITE_DB_PATH=./data/sqlite.db
|
|
|
269
210
|
|
|
270
211
|
> 这四项由 kit 在**导入 marker 之前**经 `os.environ.setdefault` 应用:仅当用户未显式设置对应环境变量时生效,不覆盖显式配置。
|
|
271
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
|
+
|
|
272
225
|
**云 OCR**(任何 `ocr="hosted"` 的转换)→ `kit.anydoc_kit`
|
|
273
226
|
|
|
274
227
|
| 环境变量 | 默认值 | 作用 |
|
|
@@ -301,6 +254,7 @@ SQLITE_DB_PATH=./data/sqlite.db
|
|
|
301
254
|
|----------|----------|
|
|
302
255
|
| `LOG_*`、`DB_*`、`MYSQL_*`、`REDIS_*`、`FILE_STORAGE_ROOT`、`ZVEC_*`、`MINERU_*`、`MARKER_MODE` | `get_settings()` 带 `lru_cache`,进程内首次读取后固定 → **重启进程**(或调用 `AI.reload_config()`,它会顺带清空该缓存) |
|
|
303
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` 应用 → 改动需**重启进程**重新导入模块 |
|
|
304
258
|
| AI 密钥写在 `.env` 里 | `.env` 只在加载档案时注入一次 → 重启,或 `AI.reload_config(reload_file=True)` |
|
|
305
259
|
| AI 密钥由自己写入 `os.environ` | `AI.reload_config()`(默认 `reload_file=False`,只重读 `os.environ`) |
|
|
306
260
|
| `ai_models.json`(增删档案、换 model) | `AI.reload_config(reload_file=True)` |
|
|
@@ -1113,7 +1067,7 @@ WxArticleKit.save(url, "./output", "12345", write_meta=True)
|
|
|
1113
1067
|
|
|
1114
1068
|
```
|
|
1115
1069
|
<output_dir>/<file_name>/
|
|
1116
|
-
├── <file_name>.md # 正文 Markdown(YAML 头:title / author / publish_time / source_url)
|
|
1070
|
+
├── <file_name>.md # 正文 Markdown(YAML 头:title / author / publish_time / source / source_url)
|
|
1117
1071
|
├── images/ # 图片转存(NNN.ext,正文引用已本地化)
|
|
1118
1072
|
└── meta.json # 可选:结构化元信息(write_meta=True 时产出)
|
|
1119
1073
|
```
|
|
@@ -1122,10 +1076,65 @@ WxArticleKit.save(url, "./output", "12345", write_meta=True)
|
|
|
1122
1076
|
`ArticleInfo.publish_time` 与 `meta.json` 内仍为 ISO 8601(结构化数据)。
|
|
1123
1077
|
- 无登录态抓取:命中微信风控(环境异常 / 访问过于频繁)时返回 `ok=False` + 原因,不破解验证码;
|
|
1124
1078
|
抓取或图片下载失败不抛异常,单张图片失败时正文保留原远程 URL。
|
|
1079
|
+
- 模型与 `WebArticleKit` 共用 `kit.article_models`(`ArticleInfo` 含 `source` / `site_host`);
|
|
1080
|
+
失败时 `reason` 为类型化分类(微信常用 `risk_control` / `no_content` / `fetch_failed`)。
|
|
1125
1081
|
- 同名子目录**覆盖写**(不追加序号);`file_name` 带 `.md` 后缀会自动去除,为空则抛 `ValueError`。
|
|
1126
1082
|
|
|
1127
1083
|
---
|
|
1128
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
|
+
|
|
1129
1138
|
## AI / MCP 档案文件
|
|
1130
1139
|
|
|
1131
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, ...] = ()
|
|
@@ -142,6 +142,26 @@ class Settings(BaseSettings):
|
|
|
142
142
|
description="llama-server 可执行文件路径(None 时沿用系统 LLAMA_CPP_BINARY)",
|
|
143
143
|
)
|
|
144
144
|
|
|
145
|
+
# Laya 决策引擎配置(本地小模型,把任意文本变成 choice/score/noul 类型化决策)
|
|
146
|
+
LAYA_MODEL: str = Field(
|
|
147
|
+
default="multilingual",
|
|
148
|
+
description=(
|
|
149
|
+
"生效检查点:multilingual(多语言含中文)/ english / typed-decisions"
|
|
150
|
+
),
|
|
151
|
+
)
|
|
152
|
+
LAYA_DEVICE: str | None = Field(
|
|
153
|
+
default=None,
|
|
154
|
+
description="推理设备(如 cpu / cuda),None 表示交给 laya 自动选择",
|
|
155
|
+
)
|
|
156
|
+
LAYA_HF_ENDPOINT: str = Field(
|
|
157
|
+
default="https://hf-mirror.com",
|
|
158
|
+
description="检查点权重下载入口(默认国内镜像),置空则使用 HuggingFace 官方源",
|
|
159
|
+
)
|
|
160
|
+
LAYA_HF_DISABLE_XET: bool = Field(
|
|
161
|
+
default=True,
|
|
162
|
+
description="禁用 HF Xet 存储后端(国内镜像不支持,开启会导致权重下载挂起)",
|
|
163
|
+
)
|
|
164
|
+
|
|
145
165
|
class Config:
|
|
146
166
|
env_file = ".env"
|
|
147
167
|
env_file_encoding = "utf-8"
|