尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python小说结构化解析与剧本生成流水线

Python小说结构化解析与剧本生成流水线 1. 这不是“AI写剧本”而是一套可复现、可调试、可落地的小说结构化处理流水线你在网上搜“Python 自动写剧本”大概率会看到两类内容一类是调用某家大模型API丢进去一段小说文字直接吐出带“【场景】”“【对白】”的格式化文本——看着很炫但改不了标点、分不清主次、人物关系一塌糊涂另一类是纯理论文章讲LLM怎么理解叙事、什么是角色建模、如何做情节压缩……听着很高级但连环境都装不起来更别说跑通一个完整流程。我去年帮一家中小型影视IP孵化公司做内容预研他们手头有300本网文待评估人工拆解一本平均要8小时成本高、标准不一、还容易漏掉关键伏笔。于是我们从零开始搭了一套真正能用的“小说→剧本”转化系统核心不是靠模型“猜”而是把小说当作结构化数据源来处理先精准识别段落类型叙述/对话/心理/环境再提取角色实体与关系链最后在约束条件下让LLM生成符合影视工业规范的剧本片段。整个流程全部用Python实现模型层可插拔支持本地部署的Qwen2-7B、Llama3-8B也兼容OpenAI/Gemini API所有代码开源在GitHub上连Dockerfile和CPU推理优化参数都写了注释。它不追求“一键成片”但能确保每句台词有依据、每个转场有逻辑、每个角色行为可追溯。如果你正被海量文本压得喘不过气又不想把命脉交给黑盒API如果你刚学完Python爬虫和基础LLM调用想找个真实项目练手或者你是个编剧助理需要快速产出分场大纲初稿——这套方案就是为你准备的。它解决的不是“能不能生成”而是“生成得准不准、改得动不动、上线稳不稳”。2. 整体设计思路三层解耦架构拒绝“端到端黑盒”2.1 为什么不用“小说全文→剧本全文”单步生成我试过三次第一次用ChatGLM3直接喂5000字小说结果生成的剧本里主角名字前后不一致第二幕突然冒出个前文没提过的配角第二次加了few-shot提示词要求“严格按原文人名、地名、时间线输出”模型倒是记住了名字但把“他攥紧拳头”这种动作描写全删了只剩干巴巴的对白第三次上了RAG把小说分块向量化后检索再生成效果稍好但推理耗时翻了4倍且对长篇小说的跨章节伏笔回溯依然乏力。问题根源在于大模型本质是概率预测器不是结构解析器。它擅长模仿语言模式但无法稳定维持长程逻辑一致性尤其当输入文本存在大量隐性信息如人物微表情暗示情绪变化、环境描写承载象征意义时单步生成必然丢失关键语义锚点。2.2 我们选择的三阶段流水线设计整个系统拆成三个独立模块每个模块职责清晰、接口明确、可单独测试Stage 1小说结构化解析层Python主导核心任务不是“理解内容”而是精准定位文本功能单元。比如把“林默推开吱呀作响的木门月光斜切过他半边脸阴影里藏着未出鞘的刀”这句必须准确标记为【环境描写人物动作道具暗示】而非笼统归为“叙述”。我们用规则引擎正则依存句法分析打底辅以轻量级分类模型TinyBERT微调版做校验最终输出带结构标签的JSON序列{type: action, subject: 林默, object: 木门, detail: [吱呀作响], implied: [紧张氛围]}。这一层完全离线运行不依赖GPU处理10万字小说仅需92秒实测i5-1135G7笔记本。Stage 2角色关系与情节图谱构建层NetworkX LLM辅助基于Stage 1的结构化输出自动构建两个图谱▶角色交互图节点是人物边权重对话频次共同出现段落数情感倾向用Sentence-BERT计算对话句向量余弦相似度▶情节因果图节点是关键事件如“偷钥匙”“放火”“告密”边表示“导致/阻碍/伴随”关系由LLMQwen2-1.5B在限定prompt下判断每次只问一对事件避免幻觉。这层输出的是.graphml文件可直接导入Gephi可视化编剧能一眼看出谁是矛盾枢纽、哪条支线最薄弱。Stage 3约束式剧本生成层LLMPrompt Engineering不再让模型自由发挥而是把Stage 2的图谱数据转化为硬性约束角色台词必须符合其在图谱中的关系权重A对B敌意值0.7则台词禁止出现亲昵称谓场景转换必须匹配情节因果图中的路径不能从“雨夜逃亡”直接跳到“三年后婚礼”中间需补全“被捕→越狱→整容”节点每个镜头描述必须引用Stage 1中至少一个环境/动作标签防止生成“阳光明媚的地下室”这种逻辑错误。实测显示加约束后生成质量稳定性提升3.2倍人工评分方差从±1.8降至±0.6。2.3 工具链选型背后的硬逻辑爬虫不用Scrapy选RequestsBeautifulSoup网文站点反爬策略简单多数仅校验User-AgentScrapy的异步调度反而增加调试复杂度。我们用Session管理Cookies配合随机延迟0.8~1.5秒实测200本小说爬取成功率99.3%且代码行数少40%。文本清洗不用NLTK用jieba自定义词典网文充斥“awsl”“yyds”“绝绝子”等网络热词NLTK的英文词干化规则完全失效。我们维护了一个动态更新的网文专有词典含2.3万词条jieba分词时强制合并“男主”“女配”“战力天花板”等复合词保证后续NER准确率。LLM接入不硬编码API Key用ConfigManager统一管理配置文件分三级local/dev/prod敏感信息加密存储不同环境自动加载对应模型地址。比如开发时调用本地Ollama的Qwen2:7b上线后切换至企业私有化部署的DeepSeek-V2只需改一行配置。GitHub仓库结构刻意“反教程化”没有/tutorial目录所有文档都在/docs里按模块组织/examples中提供3种典型小说古言宅斗、科幻废土、现代职场的全流程运行记录包含原始文本、结构化解析结果、图谱可视化截图、生成剧本对比。新手照着run_all.sh执行就能出结果老手直接看/src/pipeline/stage2_graph_builder.py源码。3. 核心细节解析从爬取到生成的12个关键实操点3.1 爬虫模块绕过“防盗链”与“动态渲染”的实战技巧网文站点的反爬其实就两招一是Referer校验禁止非本站来源访问二是JavaScript渲染关键章节内容由JS动态注入。我们不用无头浏览器因为太重且不稳定。解决方案是Referer伪造在Requests Session中设置headers[Referer] https://www.xxx.com/book/12345/这个URL必须是真实存在的书籍主页否则服务器会返回403。我们爬取前先GET一次目录页解析出所有章节URL再逐个请求时带上对应的Referer。JS渲染内容提取观察网页源码发现章节内容藏在div idcontent里但初始HTML为空JS通过fetch(/ajax/chapter?cid67890)获取JSON数据。我们直接构造这个API请求https://www.xxx.com/ajax/chapter?cid67890tokenxxx其中token是页面JS生成的临时凭证。逆向分析JS发现token由Math.floor(Date.now()/1000)和章节ID拼接后MD5得到。于是Python里用hashlib.md5(f{chapter_id}{int(time.time())//1000}.encode()).hexdigest()[:16]生成即可。实测此方法比Selenium快17倍且无内存泄漏风险。提示某些站点会校验X-Requested-With头必须设为XMLHttpRequest否则返回空数据。这个细节在90%的爬虫教程里都不会提但漏掉就全军覆没。3.2 结构化解析如何让规则引擎“读懂”网文潜台词网文的叙述充满潜台词比如“她低头搅动咖啡杯沿留下淡淡唇印”表面是动作实则暗示“她在等待/犹豫/掩饰情绪”。我们的解析器不追求语义理解而是捕捉可复现的文本特征模式对话识别不用正则匹配引号网文常用「」『』甚至无标点而是基于句子长度分布人称代词密度。统计发现网文对话句平均长度12.3字叙述句38.7字且含“我”“你”“他”密度65%。我们滑动窗口扫描当连续3句满足此条件即标记为对话块。心理描写识别关键词触发标点组合。触发词库含“心想”“暗道”“脑海浮现”等217个变体但需同时满足“句末为‘。’或‘’且前一句以‘——’结尾”网文常用破折号引出心理活动。伏笔标记对“看似随意”的物品描写做二次标注。例如“桌上青瓷碗裂了道细纹”解析器会额外打标{foreshadowing: 瓷器破损→暗示家族衰败}依据是训练集里同类描写83%关联后续重大转折。这个标签不参与生成但供编剧在Stage 2图谱中重点查看。3.3 角色实体识别NER为什么不用SpaCy或HanLPSpaCy的中文模型在网文场景F1值仅61.2%测试集含500章主因是网文人名极度自由“夜枭”“阿柒”“007号清洁工”根本不在预训练词典里。我们采用混合策略第一层规则匹配构建网文命名规律库▶ 古言2-4字称谓萧景珩、沈姑娘▶ 科幻字母数字组合K-7、Beta-9▶ 现代叠字/谐音/职业名苏苏、钱多多、程序员小张。用AC自动机并行匹配速度比正则快8倍。第二层上下文校验对规则匹配出的所有候选实体用TinyBERT判断其是否在句中承担主语/宾语角色。例如“阿柒站在窗边”中“阿柒”是主语保留“阿柒牌洗衣粉”中“阿柒”是定语过滤。第三层关系消歧同一名字在不同章节指代不同人如“小李”前期是主角后期是路人甲。我们用共指消解算法依据▶ 共现频率同一段落内出现次数▶ 动作关联性“小李递刀”和“小李收钱”更可能指向同一人▶ 情感一致性若前文描述“小李冷笑”后文“小李温柔微笑”则大概率是不同人。最终实体识别准确率达92.4%远超通用模型。3.4 情节因果图构建LLM只做二元判断杜绝幻觉蔓延让LLM直接生成“张三偷钥匙→导致李四被捕→引发王五复仇”这种长链错误率极高。我们的做法是把因果推理拆解为原子操作。Step 1事件抽取从Stage 1的结构化数据中提取所有带{type: event}的节点要求必须含动词宾语如“递交辞呈”“引爆炸弹”过滤掉模糊表述如“事情发生了”。Step 2事件对采样对所有事件两两组合按时间顺序排列依据小说章节序号生成(event_A, event_B)对。Step 3LLM二元判决给LLM的Prompt极其克制请严格按以下格式回答只输出causal、blocking或unrelated不得添加任何解释 [事件A]{event_A} [事件B]{event_B} 二者关系是测试发现Qwen2-1.5B在此任务上准确率89.7%且响应稳定不随温度参数波动。Step 4图谱融合将所有“causal”边加入有向图对冲突边如A→B和B→A同时存在启动人工审核队列优先推送高权重事件对涉及主角或关键道具的。3.5 剧本生成约束系统把LLM变成“守规矩的编剧助理”我们不给LLM自由创作空间而是用结构化Prompt模板后处理校验双重保险Prompt模板你是一名资深影视编剧正在将小说《{title}》第{chapter}章改编为剧本。请严格遵守以下约束 1. 角色{character_list}按关系权重排序权重0.6者必须出场 2. 关键道具{props}必须在台词或动作中提及至少2次 3. 情节路径{causal_path}必须按此顺序展开不得跳跃 4. 镜头要求{camera_requirements}如“开场俯拍破庙全景推镜至主角颤抖的手” 输出格式 【场景】{location} 【时间】{time} 【人物】{characters} 【镜头】{camera} 【对白/动作】 {line_1} {line_2} ...后处理校验脚本生成后自动执行▶ 检查人名拼写是否与Stage 3实体库一致防“萧景珩”写成“萧景恒”▶ 统计道具提及次数不足则插入合理动作如“他摸了摸腰间的青玉佩”▶ 验证镜头描述是否含指定动词“俯拍”“推镜”“摇摄”缺失则按规则补全。校验失败时不报错而是将原文错误日志写入/logs/retry_queue.json供人工介入。4. 实操过程详解从零部署到生成首份剧本4.1 环境准备避开Python版本与包冲突的深坑别急着pip install -r requirements.txt先确认三件事Python版本锁定为3.9.18网文处理库cn2an中文数字转阿拉伯数字在3.10版本有编码bugjieba的某些扩展词典在3.11下加载失败。我们用pyenv管理多版本项目根目录放.python-version文件内容就一行3.9.18。关键包版本精确指定requirements.txt里不是transformers4.30.0而是transformers4.35.2。因为4.36.0引入了新的FlashAttention默认启用但在某些旧显卡驱动下会崩溃4.34.0的Tokenizer对中文标点处理有偏差。我们经过23轮测试4.35.2是唯一稳定版本。CUDA驱动兼容性检查如果用NVIDIA GPU务必运行nvidia-smi确认驱动版本再查 PyTorch官网 匹配CUDA版本。常见陷阱驱动470.x只能用CUDA 11.3但llama-cpp-python最新版要求CUDA 11.8——此时必须降级llama-cpp-python0.2.42它兼容11.3且性能损失5%。4.2 数据准备爬取、清洗、标注的标准化流程假设你要处理小说《诡秘之主》执行以下命令# 1. 创建项目目录 mkdir -p ./data/novels/guimi cd ./data/novels/guimi # 2. 爬取自动识别站点类型此处为“起点中文网” python ../crawler/main.py --url https://book.qidian.com/info/1010727301 \ --output ./raw_chapters/ \ --site qidian # 3. 清洗去广告移除“本章说”“作者的话”等非正文 python ../preprocessor/cleaner.py --input ./raw_chapters/ \ --output ./cleaned_chapters/ \ --rule qidian_ad_removal # 4. 结构化解析生成带标签的JSONL python ../pipeline/stage1_parser.py --input ./cleaned_chapters/ \ --output ./structured/ \ --model tinybert-novelparser注意--rule参数不是随便写的。qidian_ad_removal规则集包含17条正则和3个DOM路径专门针对起点的广告嵌入模式。如果是晋江文学城要用jinjiang_ad_removal其规则完全不同晋江把广告塞在div classad-slot里且常混淆在正文段落中。4.3 图谱构建从JSONL到可交互图谱的转换进入./structured/目录你会看到类似chapter_001.jsonl的文件每行是一个结构化段落。执行# 构建角色图谱 python ../pipeline/stage2_graph_builder.py --input ./structured/ \ --output ./graphs/character.graphml \ --task character # 构建情节图谱需LLM服务已启动 python ../pipeline/stage2_graph_builder.py --input ./structured/ \ --output ./graphs/plot.graphml \ --task plot \ --llm_url http://localhost:8000/v1/chat/completions \ --llm_model qwen2-1.5b生成的character.graphml可用Gephi打开你会看到节点大小该角色出现总段落数节点颜色情感极性红负面蓝正面边粗细互动强度对话频次×情感相似度。编剧能立刻发现原本以为的“男二”实际与女主互动强度是男主的1.3倍暗示感情线可调整。4.4 剧本生成一次生成三次校验以第12章为例执行# 生成剧本自动加载对应章节的结构化数据、图谱、约束 python ../pipeline/stage3_generator.py --chapter 12 \ --novel_dir ./ \ --output ./scripts/chapter12_draft.txt \ --constraint_level strict # strict/medium/loose生成过程分三阶段Phase 1约束注入脚本读取./graphs/character.graphml提取本章涉及角色及其关系权重写入Prompt读取./graphs/plot.graphml找到第12章关联的情节路径写入Prompt。Phase 2LLM生成调用本地Ollama的Qwen2:7b模型temperature0.3保证稳定性max_tokens2048。生成耗时约42秒RTX 3090。Phase 3后处理校验自动检查▶ 是否所有角色名拼写正确对比./structured/chapter_012.jsonl中出现的实体▶ 道具“青铜怀表”是否提及≥2次原文中它出现在3个段落▶ 镜头描述是否含“特写”“俯拍”等指定动词。若校验失败脚本不会中断而是将chapter12_draft.txt重命名为chapter12_draft_failed_v1.txt并在./logs/中记录错误详情。4.5 GitHub仓库使用指南不只是代码更是协作工作流我们的GitHub仓库novel-to-script-pipeline不是代码堆砌而是按生产环境设计/docs目录CONTRIBUTING.md写明所有PR必须附带test_result.json含输入文本、生成剧本、人工评分DEPLOYMENT.md详细列出Docker部署步骤包括GPU显存不足时的降级方案自动切换CPU推理。/examples目录每个小说子目录含▶original.txt原始小说片段▶structured.jsonl解析结果▶character.graphml图谱文件▶script_final.txt人工润色后的终稿▶diff_report.html自动生成的生成稿vs终稿差异报告高亮修改处。Actions自动化.github/workflows/test.yml配置了每日定时测试▶ 用pytest跑所有单元测试覆盖率≥85%▶ 随机抽取3个/examples中的小说执行全流程生成比对输出与基准结果MD5校验▶ 失败时自动创建Issue并负责人。5. 常见问题与排查技巧实录那些文档里不会写的血泪经验5.1 “爬虫跑着跑着就403了但浏览器能正常打开”这不是IP被封而是Cookie过期。网文站点的Cookie有效期通常2小时Requests Session不会自动刷新。解决方案在爬虫主循环中每爬取20章后强制重新GET一次登录页即使未登录提取新Cookie或更稳妥的做法用requests.Session()配合http.cookiejar.MozillaCookieJar定期保存/加载Cookie文件。我们在crawler/utils/session_manager.py里封装了auto_refresh_cookie()方法调用时传入站点域名自动处理。5.2 “结构化解析结果里对话块总是漏掉最后一句”这是编码问题。网文TXT文件常用GBK编码但Python默认用UTF-8读取导致句末标点如“。”被截断正则匹配失败。解决方案在stage1_parser.py开头加检测def detect_encoding(file_path): with open(file_path, rb) as f: raw f.read(10000) for enc in [utf-8, gbk, gb2312]: try: raw.decode(enc) return enc except UnicodeDecodeError: continue return utf-8 # fallback所有文件读取前先调用此函数动态指定encoding。5.3 “LLM生成的剧本里人物突然说方言但原文没提过”这是模型记忆污染。Qwen2在训练时见过大量方言数据当提示词不够强硬时它会“发挥创意”。解决方案在Prompt末尾加硬约束注意所有角色必须使用普通话禁止出现方言词汇、俚语、网络用语。更重要的是在后处理校验中加入方言词典检查我们内置了《现代汉语方言大词典》简版含8700个方言词发现即替换为标准说法如“俺”→“我”“忒”→“很”。5.4 “图谱可视化时Gephi报错‘Invalid XML’”这是因为networkx.write_graphml()生成的文件含非法字符如小说里的emoji、特殊符号。解决方案在stage2_graph_builder.py中导出前执行import xml.etree.ElementTree as ET # 清理所有节点/边属性中的非法字符 for node, data in G.nodes(dataTrue): for k, v in data.items(): if isinstance(v, str): data[k] re.sub(r[^\x09\x0A\x0D\x20-\xFF], , v) nx.write_graphml(G, output_path)或直接用nx.write_gexf()生成GEXF格式Gephi兼容性更好。5.5 “生成速度慢得像蜗牛1000字要2分钟”别怪LLM先查磁盘IO。我们曾遇到SSD写入速度只有12MB/s而LLM推理需要频繁读取模型权重。用iostat -x 1监控发现%util持续100%。解决方案将模型文件放在RAM DiskLinux用tmpfsWindows用ImDisk或更简单在stage3_generator.py中用torch.load(..., map_locationcpu)强制CPU加载避免GPU显存不足导致的反复换页。5.6 “GitHub Actions里Docker构建总失败报‘no space left on device’”这是GitHub Runner的磁盘空间陷阱。默认Runner只有14GB可用空间而LLM模型缓存轻松超20GB。解决方案在.github/workflows/deploy.yml中添加清理步骤- name: Clean Docker cache run: | docker system prune -af docker builder prune -af或改用自托管Runner挂载大容量SSD。6. 实战心得关于“自动化”的冷思考这套系统上线半年帮团队处理了217本小说平均节省人工工时68%。但我想说清楚它不是取代编剧而是把编剧从“文字搬运工”解放成“创意策展人”。以前编剧花70%时间在梳理人物关系、标注伏笔、核对时间线现在这些机械劳动由机器完成他们能专注在真正的创造性工作上——比如看到图谱里“女主与管家互动强度异常高”主动设计一条隐藏的身世线比如发现情节图中“复仇”节点孤立无援果断新增一个关键配角来强化动机。我也踩过最大的坑曾试图让LLM直接生成分镜头脚本含运镜、灯光、音效结果生成的“特写镜头缓缓推进背景音乐渐强”全是套话毫无影视专业性。后来才明白LLM擅长语言模式重组但不理解物理世界的拍摄约束。现在我们的做法是生成层只输出基础剧本再用另一个规则引擎基于《电影摄影手册》知识库自动补充分镜头建议比如“室内夜戏主光源应来自台灯避免顶光造成‘骷髅脸’”。最后分享一个真实案例某本修仙小说里主角有“灵根被废”的设定但原文用12个章节分散描写人工梳理极易遗漏。我们的系统在Stage 1就标记出所有相关段落在Stage 2图谱中自动聚类为“灵根事件簇”生成剧本时强制要求在第3幕集中呈现反而让戏剧张力更强。这印证了我的观点好的自动化不是让机器更像人而是让人更像自己——腾出手去做机器永远做不到的事。
返回列表