本地运行COC跑团随机素材生成器:从零搭建稻玉狯岳自动售货机

COC跑团随机素材生成器本地部署
于 2026-08-31 03:58:47 修改
·本内容遵循CC 4.0 BY-SA版权协议

这次我们来看一个很有意思的本地化跑团小工具:“稻玉狯岳自动售货机”。听起来像是个整活项目,本质上是给 COC 跑团(克苏鲁的呼唤 TRPG)用的“随机内容自动售货机”,主题定在雷兄弟角色桌,角色对象是稻玉狯岳。也就是说,你不用每次开团前手动翻设定、找台词、编事件,而是在本地启动一个服务/页面,投一个“硬币”,自动掉出角色台词、事件分支、判定提示、道具线索这些跑团素材。

这类工具最大的特点是:硬件门槛极低,不需要 GPU,不需要独立显卡,普通笔记本就能跑。如果你是自己搭静态页面,甚至连 Python 都不用装,浏览器直接打开就能用。如果你要做接口 API、批量预生成、接入跑团机器人,再加一个轻量后端就够了。下面我会把它当成一个本地随机素材生成器来拆解:核心能力、部署方式、功能测试、接口调用、资源占用和排查方法,全程给可复制的命令和代码。

1. 核心能力速览

“自动售货机”在这里不是卖饮料,而是一种随机出料机制。常见实现就是:前端页面/命令行工具接收一次请求(点击按钮或调用接口),程序从配置好的 JSON 素材库中按权重抽取词条,组合成一条跑团可用素材并返回。

能力项 说明
项目类型 COC 跑团辅助 / 随机内容生成器
主题背景 雷兄弟角色桌,稻玉狯岳角色素材
核心功能 随机生成角色台词、NPC 反应、事件分支、判定结果提示、道具/线索
硬件需求 极低,纯前端方案不需要 GPU,不需要高性能 CPU
显存占用 无,浏览器页面或轻量 Python 服务,不涉及大模型推理
支持平台 Windows / macOS / Linux,浏览器访问
启动方式 静态页面直接打开,或 Python/Node 启动本地服务
是否支持 API 可以,Flask/Express 加 /api 路由即可
是否支持批量任务 可以,批量预生成本,或通过循环调用接口
适合场景 跑团主持人在线备团、现场随机事件、角色互动素材生成、跑团机器人的本地后端

从材料角度看,这个项目并不规定你必须用什么框架。核心是把“稻玉狯岳”和“跑团规则提示”组织成结构化素材,再用随机机制输出。下面给的部署方案覆盖“零依赖静态页”和“带 API 的轻量服务”两种路线,你可以按自己机器情况选。

2. 适用场景与使用边界

这工具最适合三类人:

  • COC 跑团主持人(KP):需要大量现场文案,但不想手动编每个 NPC 的台词。自动售货机可以在几秒内给出一条带语气、动作、判定提示的素材。
  • 雷兄弟角色桌玩家:角色是稻玉狯岳,需要贴合角色性格的互动文本,比如傲气台词、战斗时的雷之呼吸判词、对队友的嘲讽。
  • 跑团工具开发者:想给机器人或网页导入“随机素材流”,这个项目可以当作一个轻量 API 后端来用。

能解决的问题也很明确:开团时素材重复、角色脱离人设、事件编写耗时。

但也有明显边界,需要提前说清:

  • 它不生成真正的 AI 大模型内容。如果没有接 LLM API,它只是“素材排列组合器”,句子是预置词条拼接的,不是真正意义上的文风生成。
  • 素材库质量决定输出质量。如果词条写得粗糙,随机出来的文本一样会粗糙。
  • 版权合规问题。稻玉狯岳来自《鬼灭之刃》,角色名、台词、设定属于版权方。个人跑团、非商业二次创作一般属于同人使用范围,但如果你要做商业化工具、公开售卖模板,必须确认授权边界。
  • 涉及跑团私密内容时,不要把带角色隐私的素材传到公网,建议本地部署、本地使用。

从工程实践来看,我建议把“角色素材”和“跑团规则模板”分开存放,避免版权敏感内容落到无法删除的第三方服务里。

3. 环境准备与前置条件

根据你选的部署路线不同,环境要求差距很大。

3.1 纯静态页面路线

只需要一个现代浏览器。Chrome、Edge、Firefox 都行,不需要安装任何依赖。素材 JSON 和 HTML、JS 文件放同一个目录,双击 index.html 就能用。

这种方案适合“我自己开团,现场点两下抽个素材”。

前置条件:

  • 一个文本编辑器,推荐 VS Code,但记事本也行。
  • 基本 JSON 语法知识。
  • 目录里至少有一个 index.html 和一个 data.json。

3.2 本地 API 服务路线

如果你想接机器人、接第三方工具,或者跑批量预生成,需要一个轻量后端。推荐 Python 3.9+,也可以用 Node.js 16+。

检查本机环境:

BASH
python --version
pip --version
TEXT
 
```bash
node --version
npm --version

两个都有就更好,没有也无所谓,下面两种方案都能独立跑。

3.3 磁盘空间与端口

  • 纯静态方案:整个项目 1MB 以内。
  • API 方案:加上 Python 和依赖,占用不超过 200MB。
  • 端口建议:优先用 8000、5000、3000。如果本机已经占用,启动参数里改掉即可。

4. 安装部署与启动方式

这部分分三步走:先建素材库,再写核心生成逻辑,最后启动服务。

4.1 素材库结构设计

自动售货机是否好用,一半取决于素材库结构。推荐把数据拆成五个部分:

JSON
{
"character": {
"name": "稻玉狯岳",
"titles": ["雷之呼吸传人", "雷兄弟角色桌成员", "狂傲剑士"]
},
"dialogs": [
"听不懂你在说什么。",
"这种程度的敌人,不需要你出手。",
"雷之呼吸,不是用来表演的。"
],
"events": [
"调查员在街角闻到一股焦糊味。",
"狯岳突然停下脚步,握住了刀柄。",
"雷声过后,现场只剩下一道焦痕。"
],
"checks": [
"侦查检定:成功则发现刀柄上的雷纹。",
"聆听检定:失败则听到的只是普通雷声。"
],
"items": [
"雷之呼吸秘传书(破损)",
"沾满尘土的同人立绘",
"七度灶护身符"
]
}

这里说一个常见问题:直接用整段话当词条,随机出来的东西会非常僵硬。更稳的方式是“槽位组合”。比如:

JSON
{
"action_templates": [
"{character} {action},{weather}从中传来{result}。"
],
"actions": ["压低身形", "拔刀指向远处", "缓缓闭上眼睛"],
"weather": ["雷云", "暴雨", "破风声"],
"results": ["一声暴喝", "微弱电流声", "金属摩擦声"]
}

运行时先把模板里的 {character}{action} 替换成随机词条,比直接抽整段话自然很多。

4.2 纯前端实现

下面是一个最简的浏览器版本。新建一个 index.html

HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>稻玉狯岳自动售货机</title>
<style>
body { font-family: sans-serif; max-width: 720px; margin: 40px auto; }
#output { border: 1px solid #ccc; padding: 16px; min-height: 80px; }
button { padding: 8px 16px; margin-top: 12px; }
</style>
</head>
<body>
<h1>稻玉狯岳自动售货机</h1>
<button id="btn">投币抽取</button>
<div id="output"></div>
 
<script>
fetch('./data.json')
.then(response => response.json())
.then(data => {
const btn = document.getElementById('btn');
const output = document.getElementById('output');
btn.addEventListener('click', () => {
const text = sample(data.character) + ':' + sample(data.dialogs) + ' / ' + sample(data.events);
output.innerText = text;
});
});
 
function sample(arr) {
if (!arr || arr.length === 0) return '(素材缺失)';
const idx = Math.floor(Math.random() * arr.length);
return arr[idx];
}
</script>
</body>
</html>

注意:受浏览器安全限制,直接双击这个 HTML 去 fetch 本地的 data.json 会被拦截。你需要用本地静态服务器打开,或者把 data.json 内容直接嵌到 JS 变量里。

建议走本地服务器,一条命令解决:

BASH
cd your_project_dir
python -m http.server 8000

然后浏览器打开 http://127.0.0.1:8000

4.3 Python Flask 接口版

如果你想要 API 接口,用 Flask 最省事。先建虚拟环境并安装依赖:

BASH
mkdir vending-machine && cd vending-machine
python -m venv venv
 
# Windows
venv\Scripts\activate
 
# Linux / macOS
source venv/bin/activate
 
pip install flask

项目结构建议:

TEXT
vending-machine/
├── app.py
├── data.json
├── templates/
│ └── index.html
└── static/
└── style.css

app.py 的核心代码:

PYTHON
import json
import random
from flask import Flask, jsonify, request, render_template
 
app = Flask(__name__)
 
with open("data.json", "r", encoding="utf-8") as f:
DATA = json.load(f)
 
def random_item(key):
items = DATA.get(key, [])
if not items:
return "(素材缺失)"
return random.choice(items)
 
@app.route("/")
def index():
return render_template("index.html")
 
@app.route("/api/vending", methods=["GET", "POST"])
def vending():
source = request.args.get("source") or "dialogs"
if request.method == "POST":
body = request.get_json(force=True)
source = body.get("source", source)
 
if source not in DATA:
return jsonify({"error": "unknown source", "available": list(DATA.keys())}), 400
 
result = {
"source": source,
"character": random_item("character_title") if "character_title" in DATA else "稻玉狯岳",
"text": random_item(source)
}
return jsonify(result)
 
@app.route("/api/vending/batch", methods=["POST"])
def vending_batch():
body = request.get_json(force=True)
count = int(body.get("count", 5))
source = body.get("source", "events")
 
if source not in DATA:
return jsonify({"error": "unknown source"}), 400
 
results = [random_item(source) for _ in range(count)]
return jsonify({"source": source, "count": count, "results": results})
 
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8000, debug=False)

启动:

BASH
python app.py

浏览器打开 http://127.0.0.1:8000。如果端口 8000 被占用,把 port=8000 改成其它端口。

5. 功能测试与效果验证

部署完就要验证功能。下面是一套通用验证流程,不需要写自动化测试,跟着点就行。

5.1 测试:基础抽取

测试目的:确认默认素材能正常返回。

操作步骤:

  1. 启动服务。
  2. 浏览器访问首页。
  3. 点击“投币抽取”按钮。

预期结果:页面输出一条“角色名 + 台词 + 事件”拼接文本。

判断标准:

  • 文本完整,没有缺失。
  • 页面控制台无报错。
  • 中文正常显示,没有乱码。

常见失败原因:

  • 静态页面 fetch data.json 被浏览器拦截,改用 python -m http.server 后正常。
  • 文件编码不是 UTF-8,导致中文乱码。所有 JSON、HTML、Python 文件统一保存为 UTF-8。

5.2 测试:关键词过滤

如果你的素材库给每个词条加了标签,比如“战斗”“嘲讽”“解谜”,可以加一个 tag 过滤参数。

在 Flask 版本里扩展一下:

PYTHON
def random_item_by_tag(key, tag=None):
items = DATA.get(key, [])
if tag:
filtered = [i for i in items if tag in i.get("tags", [])]
if filtered:
items = filtered
return random.choice(items)

测试方式:

BASH
curl "http://127.0.0.1:8000/api/vending?source=dialogs&tag=battle"

预期结果:返回的台词都带战斗标签。

5.3 测试:连抽去重

随机工具最容易翻车的点是连续抽到相同素材。我的建议是加上一个“最近 N 条不重复”的去重逻辑,至少是做“同一轮里不重复”。

PYTHON
class VendingMachine:
def __init__(self, data, history_size=5):
self.data = data
self.history = []
self.history_size = history_size
 
def draw(self, key):
candidates = self.data.get(key, [])
if not candidates:
return "(素材缺失)"
available = [c for c in candidates if c not in self.history]
if not available:
available = candidates
self.history.clear()
item = random.choice(available)
self.history.append(item)
if len(self.history) > self.history_size:
self.history.pop(0)
return item

测试时连点 10 次,观察前面 5 条是否出现重复。

5.4 测试:批量预生成

批量场景是为了备团。你可以一次性生成 20 条事件,覆盖一整个桌。在 API 版里调用:

BASH
curl -X POST "http://127.0.0.1:8000/api/vending/batch" \
-H "Content-Type: application/json" \
-d '{"source": "events", "count": 20}'

预期结果:返回包含 20 条结果的 JSON 数组。如果 count 过大,建议在服务端加个上限,比如 100,防止有人恶意刷接口导致内存占用过高。

6. 接口 API 与批量任务

如果你的核心诉求是“把自动售货机接到自己的跑团工具/机器人上”,那 API 设计就很重要。

6.1 接口说明

接口地址:

TEXT
GET /api/vending?source=dialogs
POST /api/vending
POST /api/vending/batch

请求参数:

参数 类型 必填 含义
source string 抽取的素材分类,默认 dialogs
tag string 按标签过滤,需要素材库支持
count int 批量数量,默认 5,上限 100

响应示例:

JSON
{
"source": "events",
"character": "稻玉狯岳",
"text": "狯岳突然停下脚步,握住了刀柄。"
}

批量响应示例:

JSON
{
"source": "dialogs",
"count": 2,
"results": [
"听不懂你在说什么。",
"这种程度的敌人,不需要你出手。"
]
}

6.2 Python 客户端调用

PYTHON
import requests
 
BASE_URL = "http://127.0.0.1:8000"
 
# 单次抽取
resp = requests.get(f"{BASE_URL}/api/vending", params={"source": "dialogs"}, timeout=10)
data = resp.json()
print(data["character"], data["text"])
 
# 批量抽取
batch = requests.post(
f"{BASE_URL}/api/vending/batch",
json={"source": "events", "count": 10},
timeout=10
)
for item in batch.json()["results"]:
print(item)

6.3 批量任务队列设计

如果只是本地备团,直接循环调用批量接口就行。但如果你想做“每天自动跑一批”,就需要一个简单的队列脚本:

PYTHON
import time
import json
import requests
 
with open("tasks.json", "r", encoding="utf-8") as f:
tasks = json.load(f)
 
for task in tasks:
resp = requests.post(
"http://127.0.0.1:8000/api/vending/batch",
json={"source": task["source"], "count": task["count"]},
timeout=10
)
output = resp.json()
# 写到以 source 命名的输出文件
with open(f"output/{task['source']}.txt", "a", encoding="utf-8") as f:
for item in output["results"]:
f.write(item + "\n")
time.sleep(1) # 轻量防抖

注意:本地服务并发很低,不建议开太多并发线程。Flask 开发服务器本身不适合承载高并发,只适合本地工具或社团小范围使用。若要部署到服务器,建议换 gunicorn + gevent,或者直接用 Node/Express 重写。

6.4 接入跑团机器人

如果你的跑团群有一个 Webhook 机器人,可以写一个简单的转发脚本:收到 /draw dialogs 指令后,请求本地接口,再把返回结果发给群里。这个场景下,接口调用失败重试是必须的:

PYTHON
for attempt in range(3):
try:
resp = requests.get(url, timeout=10)
resp.raise_for_status()
break
except requests.RequestException:
time.sleep(2 ** attempt)
else:
print("接口三次重试仍失败")

7. 资源占用与性能观察

这个工具不涉及大模型推理,资源占用重点是“文件体积、内存占用、随机算法公平性”,不是显存。

7.1 网页端资源占用

  • 纯静态页面:HTML + CSS + JS + JSON 素材库,一般在 100KB 到 1MB 之间。
  • 浏览器打开后,内存占用增加一般不超过 20MB。
  • 不需要 GPU,不需要独立显卡,集显设备完全没问题。
  • 页面刷新后重新加载素材,连续调用接口不会积累太多内存。

7.2 Python 服务资源占用

Flask 开发服务在空闲时 CPU 占用几乎为 0。批量生成 100 条素材,耗时远小于 1 秒。这里主要观察两个点:

  • 进程是否残留:Windows 下 Ctrl+C 没杀干净的话,可以用 tasklist | findstr python 检查。
  • 端口是否释放:再次启动报 Address already in use 时,说明旧进程还在,换端口或杀掉旧进程。

7.3 随机算法公平性

如果你直接使用 Math.random()random.choice(),在数据量小的情况下,分布会有偏差但不影响使用。如果你的自动售货机要做“重复概率展示”或“稀有素材保底”,建议使用加密级随机源:

浏览器端:

JAVASCRIPT
function cryptoRandomInt(max) {
const array = new Uint32Array(1);
crypto.getRandomValues(array);
return array[0] % max;
}

Python 端使用 secrets 模块:

PYTHON
import secrets
 
def secure_random_choice(arr):
return arr[secrets.randbelow(len(arr))]

这样才能保证抽到每条素材的概率在统计学上更均匀。

7.4 大数据集下的性能

如果你的素材库膨胀到几万条,不要用每次遍历过滤的方式,建议改为“索引 + 标签缓存”:

PYTHON
# 构建索引
tag_index = {}
for i, item in enumerate(all_items):
for tag in item.get("tags", []):
tag_index.setdefault(tag, []).append(i)

查询时直接按索引位置随机抽取,耗时从 O(n) 降到 O(1)。

8. 常见问题与排查方法

问题现象 可能原因 排查方式 解决方案
双击 HTML 后素材不显示 浏览器拦截本地 fetch 请求 打开浏览器 F12 控制台看报错 改用 python -m http.server 8000 访问
中文乱码 文件编码不是 UTF-8 用文本编辑器查看右下角编码 全部文件另存为 UTF-8
端口被占用 之前启动的 python 进程未退出 检查日志 / netstat -ano 查看端口 换端口或结束旧进程
抽取结果大量重复 素材库词条太少 统计 data.json 各分类数量 扩充素材库或加最近 N 条去重
API 返回 400 source 参数不存在 查看响应体 error 字段 根据 available 字段传合法分类
批量请求卡住 并发过高或 count 过大 查看服务端日志、CPU 占用 限制 count,加 sleep 防抖
随机结果拼接不自然 整段词条直接拼接 检查输出文本 改用“槽位模板 + 词条替换”
机器人调用本地接口失败 机器人运行环境无法访问 127.0.0.1 检查网络、端口监听地址 监听 0.0.0.0 并注意防火墙访问限制
素材库与角色人设不符 没有给词条打标签 检查数据分类 增加 tags 字段,按标签过滤

9. 最佳实践与使用建议

从工程角度看,这类“自动售货机”虽然简单,但想长期好用,还是要遵守几个习惯。

9.1 素材库与代码分离

不要把所有词条硬编码到 JS 或 Python 文件里。数据文件单独放在 data/ 目录,方便维护、备份、导出。推荐结构:

TEXT
data/
├── dialogs.json
├── events.json
├── items.json
└── templates.json

9.2 给词条打标签

每一条素材带 tags 字段,可以按“战斗”“日常”“解谜”“感情戏”筛选。这是后续扩展关键词过滤、场景模式、智能推荐的基础。

JSON
{
"text": "狯岳缓缓闭上眼睛,雷声在远处炸响。",
"tags": ["战斗", "蓄力", "氛围"]
}

9.3 第一次先小参数测试

本地部署完成后,不要直接生成 100 条。先跑 5 条小批次,检查文本质量。如果拼接结果太僵硬,优先调整模板,而不是大规模扩充词条。

9.4 保底机制

自动售货机要有“保底”——连续多次没有抽到稀有素材时,强制给一条。这在跑团里非常实用,因为 KP 需要关键线索推进剧情,不能完全交给随机。实现时给每条素材加一个 rarity 字段:

PYTHON
weights = {"common": 80, "rare": 15, "unique": 5}
selected = random.choices(items, weights=[rarity_map[item["rarity"]] for item in items], k=1)[0]

9.5 合规与授权

重点提醒一下:稻玉狯岳、雷之呼吸、鬼灭之刃世界观,这些材料属于原作版权方。个人跑团、家庭桌、同人交流场景下使用通常没问题;公开发布模板、打包售卖、接入商业平台之前,务必确认授权范围。不要把角色原画、版权台词直接用于盈利。对外发布截图时,也尽量避免暴露跑团群内隐私内容。

9.6 版本管理

即使是个人工具,也建议用 git 管理。素材库被改坏时可以回滚。简单做法是项目目录初始化:

BASH
git init
git add .
git commit -m "初始版本"

每次改词条后提交一次,养成习惯。

10. 总结与下一步

“稻玉狯岳自动售货机”这类项目,最值得尝试的点是它把角色扮演和随机素材生成变成了一个极轻量的本地服务。你不需要显卡,不需要装模型,不需要联网,文件准备好后,一条命令就能跑。最先应该验证的是基础抽取和标签过滤——这两个功能一旦通了,后续接入机器人、批量备团、场景化模板都只是加数据的问题。

最容易踩的坑有三个:一是本地静态页面直接 fetch JSON 被浏览器拦截;二是素材库词条太少导致重复率高;三是拼接模板写得太粗,输出文本生硬。前两个在部署阶段就能避开,第三个需要你在跑团过程中持续迭代词条。

后续扩展方向可以考虑:

  • 接入 LLM API,把随机素材当成 prompt 前缀,让模型基于素材扩写角色对白,生成更自然的内容。
  • 增加场景模式,比如“战斗开场”“调查员对话”“追击环节”,一键切换到对应素材池。
  • 做一个简单的 WebUI 管理后台,方便非技术成员添加词条,不必直接改 JSON。
  • 给自动售货机加一个“历史记录面板”,跑团过程中回顾每轮抽取结果,方便复盘。

如果你只是想备团时少翻几次设定,先把 4.2 节的纯前端页面跑起来就够了。要是你想把它接到群机器人或自动化流程里,直接跳转到 4.3 节 Flask 接口版。素材积累越早开始,后面这台北货机的口味就越像你桌上的那位稻玉狯岳。