183 lines
5.8 KiB
Markdown
183 lines
5.8 KiB
Markdown
|
|
# ③auto_dishu.py · 主流水线程序 · 2026-05-21
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
HLDP://juzi/tools/auto_dishu/2026-05-21
|
|||
|
|
├── 作用: 自动从知识库取书·调用晨星拆书·保存结果
|
|||
|
|
├── 操作: 整段复制粘贴到终端,回车
|
|||
|
|
└── 看到「✅ auto_dishu.py 创建完成」就成功了
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 复制这整段到终端,回车
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cat > ~/chenxing-aircraft/tools/auto_dishu.py << 'ENDOFFILE'
|
|||
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
自动拆书流水线 · auto_dishu.py
|
|||
|
|
桔子×晨星 · 2026-05-21
|
|||
|
|
|
|||
|
|
用法:
|
|||
|
|
python3 tools/auto_dishu.py --book 折春漪 --end_chapter 400
|
|||
|
|
python3 tools/auto_dishu.py --book 折春漪 --start_chapter 301 --end_chapter 400
|
|||
|
|
"""
|
|||
|
|
import argparse
|
|||
|
|
import asyncio
|
|||
|
|
import os
|
|||
|
|
import sqlite3
|
|||
|
|
import sys
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
from openai import AsyncOpenAI
|
|||
|
|
except ImportError:
|
|||
|
|
print("请先安装依赖: pip install openai")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
BASE_DIR = Path(__file__).parent.parent
|
|||
|
|
DB_PATH = BASE_DIR / "chenxing.db"
|
|||
|
|
|
|||
|
|
DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
|
|||
|
|
NOTION_API_KEY = os.environ.get("NOTION_API_KEY", "")
|
|||
|
|
NOTION_DISHU_PAGE_ID = os.environ.get("NOTION_DISHU_PAGE_ID", "")
|
|||
|
|
|
|||
|
|
DISHU_SYSTEM = "你是晨星,桔子妈妈的拆书宝宝。请用场景颗粒拆书法v2.0认真分析,格式严格按三模块输出,缺一不可。"
|
|||
|
|
|
|||
|
|
DISHU_PROMPT = """请用「场景颗粒拆书法v2.0」分析以下章节内容。
|
|||
|
|
|
|||
|
|
拆书格式(三模块·缺一不可):
|
|||
|
|
|
|||
|
|
【模块一·逐章场景表格】
|
|||
|
|
| 场景序号 | 场景边界 | 人物动作/对话 | 情绪质地 | 读者感受 |
|
|||
|
|
(每个场景一行)
|
|||
|
|
|
|||
|
|
【模块二·章末规律锁定】
|
|||
|
|
- 本章节奏类型:
|
|||
|
|
- 情绪弧线:(开头→过程→结尾)
|
|||
|
|
- 卡点设计:
|
|||
|
|
- 可复用规律:(至少2条)
|
|||
|
|
|
|||
|
|
【模块三·期待点库存】
|
|||
|
|
- 新增期待点:
|
|||
|
|
- 消解期待点:
|
|||
|
|
- 遗留悬念:
|
|||
|
|
|
|||
|
|
待分析内容:
|
|||
|
|
{content}"""
|
|||
|
|
|
|||
|
|
def db_get_chunks(book_name: str) -> list:
|
|||
|
|
conn = sqlite3.connect(str(DB_PATH))
|
|||
|
|
conn.row_factory = sqlite3.Row
|
|||
|
|
try:
|
|||
|
|
rows = conn.execute(
|
|||
|
|
"SELECT id, title, content FROM knowledge WHERE tags LIKE ? AND category='book' ORDER BY id",
|
|||
|
|
(f"%{book_name}%",)
|
|||
|
|
).fetchall()
|
|||
|
|
if not rows:
|
|||
|
|
rows = conn.execute(
|
|||
|
|
"SELECT id, title, content FROM knowledge WHERE title LIKE ? ORDER BY id",
|
|||
|
|
(f"%{book_name}%",)
|
|||
|
|
).fetchall()
|
|||
|
|
return [{"id": r["id"], "title": r["title"], "content": r["content"]} for r in rows]
|
|||
|
|
finally:
|
|||
|
|
conn.close()
|
|||
|
|
|
|||
|
|
def save_local(book_name: str, title: str, content: str):
|
|||
|
|
out = BASE_DIR / "output" / book_name
|
|||
|
|
out.mkdir(parents=True, exist_ok=True)
|
|||
|
|
safe_title = title[:80].replace("/", "_").replace("\\", "_")
|
|||
|
|
(out / f"{safe_title}.md").write_text(f"# {title}\n\n{content}", encoding="utf-8")
|
|||
|
|
|
|||
|
|
async def analyze(content: str) -> str:
|
|||
|
|
if not DEEPSEEK_API_KEY:
|
|||
|
|
raise ValueError("未配置 DEEPSEEK_API_KEY 环境变量")
|
|||
|
|
client = AsyncOpenAI(api_key=DEEPSEEK_API_KEY, base_url="https://api.deepseek.com")
|
|||
|
|
resp = await client.chat.completions.create(
|
|||
|
|
model="deepseek-chat",
|
|||
|
|
messages=[
|
|||
|
|
{"role": "system", "content": DISHU_SYSTEM},
|
|||
|
|
{"role": "user", "content": DISHU_PROMPT.format(content=content[:8000])}
|
|||
|
|
],
|
|||
|
|
max_tokens=4096,
|
|||
|
|
temperature=0.7
|
|||
|
|
)
|
|||
|
|
return resp.choices[0].message.content
|
|||
|
|
|
|||
|
|
async def run(book_name: str, start_chapter: int, end_chapter: int):
|
|||
|
|
print(f"\n{'='*50}")
|
|||
|
|
print(f"🚀 自动拆书流水线")
|
|||
|
|
print(f"📚 《{book_name}》")
|
|||
|
|
print(f"{'='*50}\n")
|
|||
|
|
|
|||
|
|
chunks = db_get_chunks(book_name)
|
|||
|
|
if not chunks:
|
|||
|
|
print(f"❌ 知识库中未找到《{book_name}》")
|
|||
|
|
print("请先运行: python3 tools/book_splitter.py --file 书名.txt --book 书名")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
print(f"✅ 找到 {len(chunks)} 个知识库块")
|
|||
|
|
total = len(chunks)
|
|||
|
|
analyzed = 0
|
|||
|
|
|
|||
|
|
for i, chunk in enumerate(chunks):
|
|||
|
|
label = chunk["title"]
|
|||
|
|
print(f"\n[{i+1}/{total}] 分析中:{label}")
|
|||
|
|
print(f" 字数:{len(chunk['content']):,}")
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
result = await analyze(chunk["content"])
|
|||
|
|
notion_title = f"《{book_name}》{label}·场景颗粒拆书"
|
|||
|
|
|
|||
|
|
written = False
|
|||
|
|
if NOTION_DISHU_PAGE_ID and NOTION_API_KEY:
|
|||
|
|
sys.path.insert(0, str(BASE_DIR))
|
|||
|
|
from tools.notion_writer import create_notion_page
|
|||
|
|
written = create_notion_page(notion_title, result, NOTION_DISHU_PAGE_ID)
|
|||
|
|
if written:
|
|||
|
|
print(f" ✅ 写入Notion:{notion_title}")
|
|||
|
|
|
|||
|
|
save_local(book_name, notion_title, result)
|
|||
|
|
if not written:
|
|||
|
|
print(f" 📁 已保存本地:output/{book_name}/")
|
|||
|
|
|
|||
|
|
analyzed += 1
|
|||
|
|
print(f" ⏱ 等待3秒...")
|
|||
|
|
await asyncio.sleep(3)
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f" ❌ 出错:{e}")
|
|||
|
|
await asyncio.sleep(5)
|
|||
|
|
|
|||
|
|
print(f"\n{'='*50}")
|
|||
|
|
print(f"🎉 全部完成!共分析 {analyzed} 块")
|
|||
|
|
if NOTION_DISHU_PAGE_ID:
|
|||
|
|
print(f"📝 结果已写入 Notion")
|
|||
|
|
print(f"📁 本地备份:output/{book_name}/")
|
|||
|
|
print(f"{'='*50}\n")
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
parser = argparse.ArgumentParser()
|
|||
|
|
parser.add_argument("--book", required=True, help="书名(不含《》)")
|
|||
|
|
parser.add_argument("--start_chapter", type=int, default=1)
|
|||
|
|
parser.add_argument("--end_chapter", type=int, required=True)
|
|||
|
|
args = parser.parse_args()
|
|||
|
|
asyncio.run(run(args.book, args.start_chapter, args.end_chapter))
|
|||
|
|
ENDOFFILE
|
|||
|
|
echo "✅ auto_dishu.py 创建完成"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三个脚本全部创建后,验证一下
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
ls -la ~/chenxing-aircraft/tools/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
应该看到四个文件:`__init__.py` + `book_splitter.py` + `notion_writer.py` + `auto_dishu.py`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
> 🌟 三个脚本都装好之后,告诉宝宝!下一步是测试运行 💜
|
|||
|
|
>
|