sdpy-kit 0.5.3__tar.gz → 0.6.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.5.3 → sdpy_kit-0.6.1}/PKG-INFO +166 -30
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/README.md +1240 -1104
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/__init__.py +1 -1
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/config/kit_config.py +26 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/crawl/__init__.py +13 -5
- sdpy_kit-0.6.1/kit/crawl/article_io.py +325 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/crawl/article_models.py +10 -0
- sdpy_kit-0.6.1/kit/crawl/deepseek_kit.py +673 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/crawl/web_article_kit.py +32 -178
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/crawl/wx_article_kit.py +39 -156
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/db/audit.py +92 -22
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/db/base_db_kit.py +315 -39
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/db/crud.py +3 -5
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/pyproject.toml +3 -3
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/.gitignore +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/ai/__init__.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/ai/base_service.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/ai/chat_service.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/ai/embedding_service.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/ai/exceptions.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/ai/failover.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/ai/image_service.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/ai/model_builder.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/ai/model_client.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/ai/pool.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/ai/profiles.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/ai/rerank_service.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/ai/runner.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/ai/types.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/common_kit.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/config/__init__.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/config/log_config.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/db/__init__.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/db/engine.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/db/exceptions.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/db/models/__init__.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/db/models/base.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/db/mysql_kit.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/db/pg_kit.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/db/query.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/db/session.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/db/sqlite_kit.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/db/transaction.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/docs/__init__.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/docs/anydoc_kit.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/docs/docx_kit.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/docs/marker_kit.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/docs/md2doc_kit.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/docs/mineru_kit.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/docs/rc_docparser_kit.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/file_kit.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/laya_kit.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/license_kit.py +0 -0
- {sdpy_kit-0.5.3 → sdpy_kit-0.6.1}/kit/redis_kit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sdpy-kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: SDPY Kit——统一数据库访问层(PG/MySQL/SQLite)与 AI/Redis/文件/文档/向量工具集
|
|
5
5
|
Author-email: Clark <changhongyuan@126.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -18,7 +18,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
19
|
Requires-Python: >=3.13
|
|
20
20
|
Requires-Dist: cryptography>=46.0.7
|
|
21
|
-
Requires-Dist: pydantic-settings>=2.
|
|
21
|
+
Requires-Dist: pydantic-settings>=2.16.1
|
|
22
22
|
Requires-Dist: pydantic>=2.14.1
|
|
23
23
|
Requires-Dist: sqlalchemy~=2.0.54
|
|
24
24
|
Requires-Dist: sqlmodel>=0.0.48
|
|
@@ -30,7 +30,7 @@ Requires-Dist: docxtpl>=0.20.2; extra == 'all'
|
|
|
30
30
|
Requires-Dist: firecrawl-anydoc>=0.2.4; extra == 'all'
|
|
31
31
|
Requires-Dist: httpx>=0.28.1; extra == 'all'
|
|
32
32
|
Requires-Dist: huggingface-hub<2,>=1.5; extra == 'all'
|
|
33
|
-
Requires-Dist: laya>=0.4.
|
|
33
|
+
Requires-Dist: laya>=0.4.12; extra == 'all'
|
|
34
34
|
Requires-Dist: markdown-it-py>=4.2.0; extra == 'all'
|
|
35
35
|
Requires-Dist: marker-pdf>=2.0.0; extra == 'all'
|
|
36
36
|
Requires-Dist: numpy>=2.5.4; extra == 'all'
|
|
@@ -53,7 +53,7 @@ Provides-Extra: docx-parse
|
|
|
53
53
|
Requires-Dist: rc-docparser>=0.2.2; extra == 'docx-parse'
|
|
54
54
|
Provides-Extra: laya
|
|
55
55
|
Requires-Dist: huggingface-hub<2,>=1.5; extra == 'laya'
|
|
56
|
-
Requires-Dist: laya>=0.4.
|
|
56
|
+
Requires-Dist: laya>=0.4.12; extra == 'laya'
|
|
57
57
|
Provides-Extra: marker
|
|
58
58
|
Requires-Dist: marker-pdf>=2.0.0; extra == 'marker'
|
|
59
59
|
Provides-Extra: md2doc
|
|
@@ -78,7 +78,7 @@ Description-Content-Type: text/markdown
|
|
|
78
78
|
|
|
79
79
|
SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
80
80
|
|
|
81
|
-
- **统一数据库访问层**:PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致,切换数据库只改一行 import
|
|
81
|
+
- **统一数据库访问层**:PostgreSQL / MySQL / SQLite,三个 Kit API 完全一致,切换数据库只改一行 import;内置审计字段自动填充(`created_*` / `updated_*` / `deleted`)与软删除查询过滤;
|
|
82
82
|
- **AI 多模型服务池**:chat / embedding / rerank / image 四类能力,多厂商候选链 + 故障自动转移;
|
|
83
83
|
- **工具集**:Redis / 文件 / 雪花 ID / 文档互转 / Word 模板填充 / MinerU 解析 / Marker 本地解析(PDF + 图片 → md + images/) / 离线 docx 解析(md + images/) / Laya 类型化决策 / 微信公众号文章抓取存储 / 通用网页文章抓取存储。
|
|
84
84
|
|
|
@@ -101,9 +101,11 @@ SDPY 统一工具集(Kit 体系),供 Python 项目开箱即用:
|
|
|
101
101
|
| `kit.docs.mineru_kit` | `MinerUKit` | MinerU 文档解析(精准 + Agent 轻量) | `mineru` |
|
|
102
102
|
| `kit.docs.marker_kit` | `MarkerKit` | 本地解析(PDF / 图片 → Markdown + Images) | `marker` |
|
|
103
103
|
| `kit.laya_kit` | `LayaKit` | 类型化决策:choice / score / noul,返回判断结果 + 置信度 | `laya` |
|
|
104
|
-
| `kit.crawl.article_models` | `ArticleInfo` / `ArticleResult` / `SavedOutput` 等 |
|
|
104
|
+
| `kit.crawl.article_models` | `ArticleInfo` / `ArticleResult` / `SavedOutput` 等 | 文章抓取通用模型(三个 `*ArticleKit` 共用,含 `reason` 分类与非致命 `warnings`) | 无 |
|
|
105
|
+
| `kit.crawl.article_io` | `resolve_stem` / `save_article` 等 | 落盘公共层(目录命名 / 图片本地化 / YAML 头 / `meta.json`),三个 `*ArticleKit` 共用 | 无 |
|
|
105
106
|
| `kit.crawl.wx_article_kit` | `WxArticleKit` | 微信公众号文章 → Markdown + 图片转存 | 无 |
|
|
106
107
|
| `kit.crawl.web_article_kit` | `WebArticleKit` | 通用网页文章 → Markdown + 图片转存(**不含微信**,微信请用 `WxArticleKit`;明确支持头条) | `web-article` |
|
|
108
|
+
| `kit.crawl.deepseek_kit` | `DeepSeekKit` | DeepSeek Search / Fetch API:网页搜索 + 单页抓取转 Markdown(`save` 与另两个 Kit 同契约) | 无(需 `DEEPSEEK_API_KEY`) |
|
|
107
109
|
|
|
108
110
|
## 安装
|
|
109
111
|
|
|
@@ -336,7 +338,7 @@ from kit.db import session_scope, transactional, DBError # 推荐
|
|
|
336
338
|
from kit import session_scope, transactional, DBError # 等价
|
|
337
339
|
```
|
|
338
340
|
|
|
339
|
-
### 1. 三个数据库 Kit(
|
|
341
|
+
### 1. 三个数据库 Kit(31 个共享方法 API 完全一致)
|
|
340
342
|
|
|
341
343
|
| 类 | 导入 | 数据库 | extra |
|
|
342
344
|
|----|------|--------|-------|
|
|
@@ -344,7 +346,7 @@ from kit import session_scope, transactional, DBError # 等价
|
|
|
344
346
|
| `PgKit` | `from kit.db.pg_kit import PgKit` | PostgreSQL | `pg` |
|
|
345
347
|
| `MySqlKit` | `from kit.db.mysql_kit import MySqlKit` | MySQL | `mysql` |
|
|
346
348
|
|
|
347
|
-
**共有方法(
|
|
349
|
+
**共有方法(31 个,全部为 classmethod;`raw_*` 内部自动提交,`build_raw_*` 只拼装不执行)**
|
|
348
350
|
|
|
349
351
|
| 分组 | 方法 | 说明 |
|
|
350
352
|
|------|------|------|
|
|
@@ -365,7 +367,11 @@ from kit import session_scope, transactional, DBError # 等价
|
|
|
365
367
|
| 原生 SQL | `execute(sql, params=None) -> int` | 原生写入,返回影响行数;`params` 传**字典列表**时批量执行(executemany,行键集须一致、行值须为标量、空列表返回 0) |
|
|
366
368
|
| 原生 SQL | `prepare_statement(sql, params=None) -> (语句, dict \| None)` | **模块级函数**(`from kit.db import prepare_statement`):构造可在**自有事务**内 `session.execute(stmt, bound)` 的语句,支持 `IN :ids`,调用方无须 import `sqlalchemy` |
|
|
367
369
|
| 原生表 | `create_raw_table(table_name, columns, if_not_exists=True) -> None` | 按「列名 → 列定义」建普通表;⚠ 列定义须按目标库方言书写,不做方言翻译 |
|
|
368
|
-
| 原生表 | `raw_insert(table_name, data) -> int` |
|
|
370
|
+
| 原生表 | `raw_insert(table_name, data, *, ignore_conflict=False) -> int` | 插入一行;`ignore_conflict=True` 幂等(PG/SQLite `ON CONFLICT DO NOTHING`、MySQL `INSERT IGNORE`) |
|
|
371
|
+
| 原生表 | `raw_update(table_name, data, where, params=None) -> int` | 按 `where`(不含 `WHERE`)更新;启用审计填充时自动补 `updated_*` |
|
|
372
|
+
| 原生表 | `raw_touch(table_name, where, params=None) -> int` | **0.6.0**:只刷新 `updated_*`(需先 `enable`;表无该列抛 `DBError`) |
|
|
373
|
+
| 原生表 | `raw_delete(table_name, where, params=None) -> int` | **0.6.0**:条件删除(`where` 必填) |
|
|
374
|
+
| 原生表 | `build_raw_insert / build_raw_update / build_raw_touch / build_raw_delete` | **0.6.0**:只拼装不执行,返回 `(语句, 参数)`,供**自有事务**内 `session.execute(*builder(...))` |
|
|
369
375
|
| 原生表 | `raw_get_by(table_name, column, value) -> dict \| None` | 按单列等值取首行 |
|
|
370
376
|
| 事务 | `transaction()` | 事务上下文管理器,`with PgKit.transaction() as session:` |
|
|
371
377
|
| 表 | `list_tables() -> list[str]` | 列出全部表名 |
|
|
@@ -596,7 +602,90 @@ finally:
|
|
|
596
602
|
> 相似度换算建议:SQLite 仅支持 L2,若向量入库前已**归一化**,则 `cos = 1 − L2²/2`;
|
|
597
603
|
> PG 可直接用 `distance_type="COSINE"`。
|
|
598
604
|
|
|
599
|
-
### 3.
|
|
605
|
+
### 3. 审计字段自动填充与软删除过滤(默认关闭)
|
|
606
|
+
|
|
607
|
+
`AuditConfig.enable(...)` 后,kit 的写入口自动补齐审计字段;`set_soft_delete_filter(True)`
|
|
608
|
+
后查询自动只看未删除记录。**两项默认都关闭**,不调用时行为与旧版完全一致。
|
|
609
|
+
|
|
610
|
+
```python
|
|
611
|
+
from kit.db import AuditConfig, PgKit
|
|
612
|
+
|
|
613
|
+
AuditConfig.enable(user_resolver=get_current_userid) # 启动时调用一次
|
|
614
|
+
AuditConfig.set_soft_delete_filter(True) # 可选:查询过滤软删除
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
填充字段(默认列名 + 内置别名):
|
|
618
|
+
|
|
619
|
+
| 槽位 | 默认列名 | 内置别名 | 说明 |
|
|
620
|
+
|------|----------|----------|------|
|
|
621
|
+
| `created_at` | `created_at` | `create_time` | 创建时间 |
|
|
622
|
+
| `created_by` | `created_by` | — | 创建人 |
|
|
623
|
+
| `updated_at` | `updated_at` | `update_time` | 更新时间 |
|
|
624
|
+
| `updated_by` | `updated_by` | — | 更新人 |
|
|
625
|
+
| `deleted` | `deleted` | — | `0` 正常 / `1` 删除 |
|
|
626
|
+
|
|
627
|
+
**内置别名无需配置**:表里的时间列若叫 `create_time` / `update_time`,同样会被识别;
|
|
628
|
+
两套列同时存在时两列都填(同值)。
|
|
629
|
+
|
|
630
|
+
判定只有两条(同时满足才填):**表中存在该列** + **本次写入数据中该字段为空**
|
|
631
|
+
(`None` / 空串 / 纯空白;`0` 与 `False` 不算空)。显式非空值一律保留,
|
|
632
|
+
**不存在覆盖语义**;「当前值」指本次写入的数据,不是数据库已存值。
|
|
633
|
+
|
|
634
|
+
| 动作 | `created_at` / `create_time` | `updated_at` / `update_time` | `deleted` |
|
|
635
|
+
|------|------------------------------|------------------------------|-----------|
|
|
636
|
+
| INSERT | 空才填 | 空才填(与 created 同值) | 空才填 `0`(字符串列填 `"0"`) |
|
|
637
|
+
| UPDATE | **不参与** | 空才填 | **不参与** |
|
|
638
|
+
|
|
639
|
+
落点:ORM flush(`before_flush`)、`CRUDBase.update_multi` / `upsert`、
|
|
640
|
+
`raw_insert` / `raw_update`。**`execute()` 手写 SQL 与批量 executemany 不填充**。
|
|
641
|
+
|
|
642
|
+
```python
|
|
643
|
+
PgKit.raw_insert("chat_session", {"id": 1, "title": "t"})
|
|
644
|
+
# → 自动补 created_at/created_by/updated_at/updated_by 与 deleted=0
|
|
645
|
+
|
|
646
|
+
PgKit.raw_update("chat_session", {"title": "t2"}, "id = :id", {"id": 1})
|
|
647
|
+
# → 只刷 updated_at / updated_by;where 的值全部走绑定参数
|
|
648
|
+
|
|
649
|
+
PgKit.raw_get_by("chat_session", "id", 1) # 过滤开启时自动排除已删除行
|
|
650
|
+
|
|
651
|
+
with AuditConfig.include_deleted(): # 需要读已删除记录时临时绕过
|
|
652
|
+
PgKit.get(Model, 1)
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
自定义字段名(列名与约定完全不同时):
|
|
656
|
+
|
|
657
|
+
```python
|
|
658
|
+
AuditConfig.set_fields({"created_at": "create_on", "deleted": "del_flag"})
|
|
659
|
+
AuditConfig.fields() # 查看当前「槽位 → 实际列名」
|
|
660
|
+
AuditConfig.set_fields(None) # 恢复默认
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
约定与局限:
|
|
664
|
+
|
|
665
|
+
- `set_fields` 的列名必须是合法标识符(字母 / 数字 / 下划线,不以数字开头),
|
|
666
|
+
否则立即 `ValueError`——列名会被拼进 SQL 的标识符位置。
|
|
667
|
+
- `deleted` 支持整数列(填 `0`)与字符串列(填 `"0"`,`VARCHAR` / `CHAR` / `TEXT`);
|
|
668
|
+
**boolean 列不支持**(会报类型错误)。
|
|
669
|
+
- 查询过滤严格等值 `deleted = 0`(字符串列 `"0"`);`NULL` **不**视为未删除,历史数据需自行回填。
|
|
670
|
+
- 写方法与 `query()` 手写 SQL 不追加过滤条件;`upsert` 的冲突回查跳过过滤。
|
|
671
|
+
- `user_resolver` 返回 `None` / 空串或抛异常时跳过 `*_by` 填充,不影响写入。
|
|
672
|
+
|
|
673
|
+
**原生写工具(0.6.0)**:`raw_insert` / `raw_update` / `raw_touch` / `raw_delete` 自带事务;
|
|
674
|
+
`build_raw_*` 只拼装(返回 `(语句, 参数)`),供 `with Kit.transaction() as session:` 内使用:
|
|
675
|
+
|
|
676
|
+
```python
|
|
677
|
+
with PgKit.transaction() as session:
|
|
678
|
+
session.execute(*PgKit.build_raw_insert("docs_tag", {"id": 1}, ignore_conflict=True))
|
|
679
|
+
session.execute(*PgKit.build_raw_touch("docs_item", "id = :id", {"id": 1}))
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
- `ignore_conflict=True`:PG/SQLite `ON CONFLICT DO NOTHING`、MySQL `INSERT IGNORE`
|
|
683
|
+
(⚠ MySQL 忽略范围更宽,含数据截断告警);冲突被忽略时返回 `0`。
|
|
684
|
+
- `raw_touch`:只刷新 `updated_*`(含别名 `update_time`),需先 `enable`;表无该列抛 `DBError`。
|
|
685
|
+
- 写入值:`dict` 自动编码为 JSON 文本;`list` 仅在 JSON 列编码,其它列抛 `ValueError`
|
|
686
|
+
(避免被当作 `IN` 展开破坏 SQL;数组 / 向量请用字符串或 `execute()`)。
|
|
687
|
+
|
|
688
|
+
### 4. CRUDBase(配合 session_scope,不自动提交)
|
|
600
689
|
|
|
601
690
|
```python
|
|
602
691
|
from kit.db import CRUDBase, session_scope
|
|
@@ -628,7 +717,7 @@ with session_scope() as session: # 正常退出提交、异常回滚
|
|
|
628
717
|
|
|
629
718
|
> 约定:`SQLiteKit / PgKit / MySqlKit` 方法自动提交;`CRUDBase` 方法**不提交**,事务边界由外层 `session_scope` 或 `@transactional` 决定。
|
|
630
719
|
|
|
631
|
-
###
|
|
720
|
+
### 5. 引擎 / 会话 / 事务 / 异常
|
|
632
721
|
|
|
633
722
|
| 符号 | 签名 | 说明 |
|
|
634
723
|
|------|------|------|
|
|
@@ -655,7 +744,7 @@ def transfer(session: Session, from_id: int, to_id: int, amount: float) -> None:
|
|
|
655
744
|
...
|
|
656
745
|
```
|
|
657
746
|
|
|
658
|
-
###
|
|
747
|
+
### 6. 模型混入类与查询构建器
|
|
659
748
|
|
|
660
749
|
```python
|
|
661
750
|
from sqlalchemy import BigInteger
|
|
@@ -1064,35 +1153,41 @@ result = WxArticleKit.fetch("https://mp.weixin.qq.com/s/xxxx")
|
|
|
1064
1153
|
if result.ok:
|
|
1065
1154
|
print(result.info.title, result.info.author, result.info.publish_time)
|
|
1066
1155
|
|
|
1067
|
-
# 抓取并存储:<output_dir>/<
|
|
1156
|
+
# 抓取并存储:<output_dir>/<stem>/ 下 <stem>.md + images/ + meta.json
|
|
1068
1157
|
saved = WxArticleKit.save(url, output_dir="./output", file_name="12345")
|
|
1069
1158
|
print(saved.md_path, saved.images_saved)
|
|
1070
1159
|
|
|
1071
|
-
#
|
|
1072
|
-
WxArticleKit.save(url, "./output"
|
|
1160
|
+
# 不传 file_name:目录按本地时间 yymmddhhmmss 自动命名
|
|
1161
|
+
WxArticleKit.save(url, "./output")
|
|
1162
|
+
|
|
1163
|
+
# 不需要 meta.json 时显式关闭
|
|
1164
|
+
WxArticleKit.save(url, "./output", "12345", write_meta=False)
|
|
1073
1165
|
```
|
|
1074
1166
|
|
|
1075
1167
|
| 方法 | 说明 |
|
|
1076
1168
|
|------|------|
|
|
1077
1169
|
| `fetch(url, timeout=20.0) -> ArticleResult` | 抓取并解析正文(不落盘);结果对象字段 `ok` / `error` / `info`(`ArticleInfo`)/ `markdown` |
|
|
1078
|
-
| `save(url, output_dir, file_name, write_meta=
|
|
1170
|
+
| `save(url, output_dir, file_name=None, write_meta=True, timeout=20.0) -> SavedOutput` | 抓取并存储到 `<output_dir>/<stem>/`;结果对象字段 `ok` / `error` / `output_dir` / `md_path` / `images_dir` / `title` / `images_saved` / `reason` / `warnings` |
|
|
1079
1171
|
|
|
1080
|
-
|
|
1172
|
+
落盘结构(三个 `*ArticleKit` 统一由 `kit.crawl.article_io` 完成):
|
|
1081
1173
|
|
|
1082
1174
|
```
|
|
1083
|
-
<output_dir>/<
|
|
1084
|
-
├── <
|
|
1085
|
-
├── images/
|
|
1086
|
-
└── meta.json
|
|
1175
|
+
<output_dir>/<stem>/
|
|
1176
|
+
├── <stem>.md # 正文 Markdown(YAML 头:title / author / publish_time / source / source_url)
|
|
1177
|
+
├── images/ # 图片转存(NNN.ext,正文引用已本地化)
|
|
1178
|
+
└── meta.json # 结构化元信息(默认产出;write_meta=False 关闭)
|
|
1087
1179
|
```
|
|
1088
1180
|
|
|
1089
|
-
-
|
|
1181
|
+
- `stem` 不传时按本地时间 `%y%m%d%H%M%S` 自动命名,同秒碰撞追加 `-2`…`-99`;
|
|
1182
|
+
显式传 `file_name` 时同名目录**覆盖写**;
|
|
1183
|
+
- 元信息随 Markdown 的 YAML 头落盘(头部 `publish_time` 为中文日期,如 `2026年8月4日`);
|
|
1090
1184
|
`ArticleInfo.publish_time` 与 `meta.json` 内仍为 ISO 8601(结构化数据)。
|
|
1091
1185
|
- 无登录态抓取:命中微信风控(环境异常 / 访问过于频繁)时返回 `ok=False` + 原因,不破解验证码;
|
|
1092
1186
|
抓取或图片下载失败不抛异常,单张图片失败时正文保留原远程 URL。
|
|
1093
1187
|
- 模型与 `WebArticleKit` 共用 `kit.crawl.article_models`(`ArticleInfo` 含 `source` / `site_host`);
|
|
1094
1188
|
失败时 `reason` 为类型化分类(微信常用 `risk_control` / `no_content` / `fetch_failed`)。
|
|
1095
|
-
-
|
|
1189
|
+
- 显式 `file_name` 时同名子目录**覆盖写**(不追加序号);省略时按时间自动命名,同秒追加 `-2`…;
|
|
1190
|
+
`file_name` 带 `.md` 后缀会自动去除,显式传空串则抛 `ValueError`。
|
|
1096
1191
|
|
|
1097
1192
|
---
|
|
1098
1193
|
|
|
@@ -1108,21 +1203,21 @@ result = WebArticleKit.fetch("https://blog.csdn.net/xxx/article/details/123")
|
|
|
1108
1203
|
if result.ok:
|
|
1109
1204
|
print(result.info.source, result.info.title, result.info.publish_time)
|
|
1110
1205
|
|
|
1111
|
-
# 抓取并存储:<output_dir>/<
|
|
1206
|
+
# 抓取并存储:<output_dir>/<stem>/ 下 <stem>.md + images/ + meta.json
|
|
1112
1207
|
saved = WebArticleKit.save(url, output_dir="./output", file_name="12345")
|
|
1113
1208
|
print(saved.md_path, saved.images_saved)
|
|
1114
1209
|
|
|
1115
|
-
#
|
|
1116
|
-
WebArticleKit.save(url, "./output"
|
|
1210
|
+
# 不传 file_name:目录按本地时间 yymmddhhmmss 自动命名
|
|
1211
|
+
WebArticleKit.save(url, "./output")
|
|
1117
1212
|
```
|
|
1118
1213
|
|
|
1119
1214
|
| 方法 | 说明 |
|
|
1120
1215
|
|------|------|
|
|
1121
1216
|
| `fetch(url, timeout=20.0) -> ArticleResult` | 抓取并解析正文(不落盘);结果对象字段 `ok` / `error` / `info`(`ArticleInfo`)/ `markdown` |
|
|
1122
|
-
| `save(url, output_dir, file_name, write_meta=
|
|
1217
|
+
| `save(url, output_dir, file_name=None, write_meta=True, timeout=20.0) -> SavedOutput` | 抓取并存储到 `<output_dir>/<stem>/`;字段与 `WxArticleKit.save` 一致 |
|
|
1123
1218
|
|
|
1124
|
-
落盘结构与 `WxArticleKit` 完全一致(`<
|
|
1125
|
-
`<
|
|
1219
|
+
落盘结构与 `WxArticleKit` 完全一致(`<stem>.md` + `images/` + `meta.json`):
|
|
1220
|
+
`<stem>.md` 的 YAML 头为 `title / author / publish_time / source / source_url`(`publish_time` 渲染为中文日期)。
|
|
1126
1221
|
|
|
1127
1222
|
**站点覆盖范围**(规则表只允许「转交」与「接口适配」两类条目,不做逐站 DOM 补丁):
|
|
1128
1223
|
|
|
@@ -1142,11 +1237,52 @@ WebArticleKit.save(url, "./output", "12345", write_meta=True)
|
|
|
1142
1237
|
|
|
1143
1238
|
| 字段 | 取值 / 含义 |
|
|
1144
1239
|
|---|---|
|
|
1145
|
-
| `reason` | 失败时的类型化分类:`unsupported_source` / `fetch_blocked` / `fetch_failed` / `no_content` / `risk_control`(成功为空串) |
|
|
1240
|
+
| `reason` | 失败时的类型化分类:`unsupported_source` / `fetch_blocked` / `fetch_failed` / `no_content` / `risk_control` / `auth_failed` / `rate_limited` / `config_missing`(成功为空串) |
|
|
1146
1241
|
| `warnings` | **非致命**抽取自检提示(`tuple[str, ...]`):正文疑似过短、页面含图片但产物无图;同时写入 warning 日志 |
|
|
1147
1242
|
|
|
1148
1243
|
标题统一去除站点后缀(如 CSDN 的 `标题-CSDN博客`);刻意不做「与页面 h1 择优」(h1 可能是导航文案)。
|
|
1149
1244
|
|
|
1245
|
+
## kit.crawl.deepseek_kit —— DeepSeek Search / Fetch
|
|
1246
|
+
|
|
1247
|
+
零第三方依赖,基础安装即可用(按请求次数计费,0.005 元/次量级)。需配置 API Key:
|
|
1248
|
+
|
|
1249
|
+
```
|
|
1250
|
+
DEEPSEEK_API_KEY=sk-xxx # .env 或环境变量
|
|
1251
|
+
DEEPSEEK_BASE_URL=https://api.deepseek.com
|
|
1252
|
+
DEEPSEEK_TIMEOUT=60
|
|
1253
|
+
DEEPSEEK_MAX_RETRIES=2 # 仅 408/429/5xx 重试
|
|
1254
|
+
```
|
|
1255
|
+
|
|
1256
|
+
```python
|
|
1257
|
+
from kit.crawl.deepseek_kit import DeepSeekKit
|
|
1258
|
+
|
|
1259
|
+
# 1. 网页搜索(不落盘)
|
|
1260
|
+
found = DeepSeekKit.search("DeepSeek V4 发布 site:github.com", count=5, need_content=False)
|
|
1261
|
+
for item in found.items:
|
|
1262
|
+
print(item.title, item.url, item.rerank_score, item.authority)
|
|
1263
|
+
|
|
1264
|
+
# 2. 单页抓取(不落盘)
|
|
1265
|
+
page = DeepSeekKit.fetch("https://api-docs.deepseek.com/zh-cn/guides/search")
|
|
1266
|
+
print(page.info.title, page.markdown)
|
|
1267
|
+
|
|
1268
|
+
# 3. 抓取并存储:<output_dir>/<stem>/ 下 <stem>.md + images/ + meta.json
|
|
1269
|
+
saved = DeepSeekKit.save(url, output_dir="./output", file_name="ds-guide")
|
|
1270
|
+
```
|
|
1271
|
+
|
|
1272
|
+
| 方法 | 说明 |
|
|
1273
|
+
|------|------|
|
|
1274
|
+
| `search(query, count=10, need_content=False, ori_question=None, localization=None, timeout=None, retries=None) -> SearchResult` | 返回 `ok` / `error` / `reason` / `items`(`SearchItem` 元组)/ `warnings`;`SearchItem.to_dict()` 可直接 JSON 化 |
|
|
1275
|
+
| `fetch(url, timeout=None, retries=None) -> ArticleResult` | 与 `WebArticleKit.fetch` 同构(`info` / `markdown` / `reason` / `warnings`) |
|
|
1276
|
+
| `save(url, output_dir, file_name=None, write_meta=True, timeout=None, retries=None) -> SavedOutput` | 落盘结构与两个 `*ArticleKit` 完全一致 |
|
|
1277
|
+
|
|
1278
|
+
失败 `reason`:`auth_failed`(401/402)/ `fetch_blocked`(403)/ `no_content`(404)/
|
|
1279
|
+
`rate_limited`(429)/ `fetch_failed`(408/5xx 重试后仍失败、请求不合法)。
|
|
1280
|
+
未配置 `DEEPSEEK_API_KEY` 抛 `DeepSeekKitError`。实现要求:
|
|
1281
|
+
|
|
1282
|
+
- 服务端返回的正文是**重度反斜杠转义的伪 Markdown**,kit 会统一反转义后再落盘;
|
|
1283
|
+
- 该接口**不返回代码块围栏**,代码会被压成普通段落;图片也常缺失。
|
|
1284
|
+
需要高保真正文(代码块、图片)请改用 `WebArticleKit`。
|
|
1285
|
+
|
|
1150
1286
|
---
|
|
1151
1287
|
|
|
1152
1288
|
## AI 档案文件
|