构建 OpenAssistant 影视剧台词对话语料:tv_dialogue 数据集从抓取到统一格式化的完整工程实践
【免费下载链接】Open-AssistantOpenAssistant is a chat-based assistant that understands tasks, can interact with third-party systems, and retrieve information dynamically to do so.项目地址: https://gitcode.com/gh_mirrors/op/Open-Assistant
影视剧字幕与剧本中蕴含着海量自然、口语化、多角色轮转的对话文本,是训练对话式 AI 的重要语料来源。本文以 OpenAssistant 仓库中的 tv_dialogue 数据集为研究对象,完整讲解其设计动机、统一的数据格式规范、覆盖 9 类影视作品/电影的来源构成,以及从公开渠道下载、清洗、结构化、导出 Parquet 的全套可复现代码流程,读者学完后即可按同样方法构建属于自己的多来源对话数据集。
该数据集位于仓库 data/datasets/tv_dialogue,包含 README.md(数据卡片)、两个 Jupyter Notebook(public.ipynb 处理 Friends、The Office、Marvel、Doctor Who、Star Trek 五个"纯对话"来源;imsdb.ipynb 负责从 imsdb.com 抓取带详细场景描述的完整剧本)以及 requirements.txt 依赖清单。
一、数据集定位:为什么要为对话模型收集影视台词
对话数据集通常来自三大渠道:社交平台用户发言、机器生成对话、以及影视剧本/字幕。前两者或噪音较多,或缺乏多轮对话的自然结构。影视剧本则天然具备以下优势:
- 多角色轮转结构:每句台词带明确的说话人标注,天然形成
[说话人] 台词的交替结构,是训练多轮对话模型的理想输入; - 口语化表达密度高:剧本语言贴近真实交流,包含大量省略、停顿、语气词,有助于模型学习自然语言节奏;
- 场景上下文丰富:台词依附于具体场景,场景切换、画外音(v.o.)等标注为理解对话语境提供了辅助信息;
- 来源公开可获取:本数据集的全部剧本均来自公开的 GitHub 仓库、Kaggle 数据集和剧本网站,工程上可复现。
正因如此,tv_dialogue 将"影视剧对话/剧本"作为对话语料的一个独立来源,按照 OpenAssistant 的语料组织方式(每条样本即一集/一部电影)进行统一整理。
二、数据集规模与 YAML 数据卡片解读
数据卡片头部(dataset_info块)从 HuggingFace Datasets 的视角声明了数据集的 Schema,是理解整个数据集结构的第一份官方文档:
dataset_info: features: - name: TEXT dtype: string - name: METADATA dtype: string - name: SOURCE dtype: string splits: - name: train num_bytes: 211728118 num_examples: 2781 download_size: 125187885 dataset_size: 211728118 license: mit task_categories: - conversational - text2text-generation language: - en tags: - OpenAssistant - transcripts - subtitles - television pretty_name: TV and Movie dialogue and transcript corpus size_categories: - 1K<n<10K要点拆解:
| 字段 | 值 | 含义 |
|---|---|---|
features | TEXT / METADATA / SOURCE | 每条样本三个字段,均为字符串 |
splits.train | 2781 条 / 211,728,118 字节 | 单个 train 划分,数据集约 2.1 亿字节 |
download_size | 125,187,885 字节 | 原始文件压缩下载体积约 1.25 亿字节 |
license | mit | MIT 开源协议 |
task_categories | conversational, text2text-generation | 对话与文本生成两类任务均可使用 |
language | en | 当前仅收录英语语料 |
size_categories | 1K<n<10K | 样本量在 1000 到 10000 之间 |
三个特征列的分工明确:TEXT是经过统一格式化后的完整剧本正文,METADATA是该集/该片的结构化信息(JSON 字符串),SOURCE则记录原始出处(格式为来源组织/抓取者,如friends/emorynlp),保证每条语料可溯源。
三、统一数据格式:一集一行,[说话人] 台词
README 中给出了格式规范的官方示例:
[PERSON 1] Hello [PERSON 2] Hello Person 2! How's it going? (they are both talking) [PERSON 1] I like being an example on Huggingface! They are examples on Huggingface. CUT OUT TO ANOTHER SCENCE We are somewhere else [PERSON 1 (v.o)] I wonder where we are?从示例可以归纳出这条数据集的格式化约定:
- 角色标注:每句对白以
[说话人]前缀开始; - 多行台词:同一说话人的连续多行台词在下一个
[前持续累积(如[PERSON 2]的两行内容); - 场景信息以原文保留:如
(they are both talking)这样的动作提示、CUT OUT TO ANOTHER SCENCE这样的场景切换说明,作为场景上下文留在文本中,而非被粗暴删除; - 特殊说话人标记:
[PERSON 1 (v.o)]中的(v.o)表示画外音(voice-over),说明该台词属于非画面内的画外叙述; - 每行一条样本:数据集以"一集 / 一部电影"为一行(共 2781 行),而不是以单句对白为行——这样每条样本天然是一段完整、有开头结尾的多轮对话。
值得注意的是,这种"粗粒度一行"的设计与后续 OpenAssistant 的训练数据组织方式一致:先以完整剧本为单位组织语料,再在训练阶段由数据加载器按需要切分成对话片段。仓库中的相关加载逻辑可参考 data/datasets/init.py 等数据集入口文件。
四、语料来源全景:两类剧本、九大出处
README 将全部来源分成两类,并附上了原始出处链接,本文按仓库文档整理如下(原始链接见 README 原文,此处保留来源标识供追溯):
4.1 纯对话类:仅保留台词,附带少量场景信息
| 剧集 | 剧本数 | 抓取来源 | Source 标识 |
|---|---|---|---|
| Friends | 236 集 | character-mining 项目(emorynlp) | friends/emorynlp |
| The Office | 186 集 | Kaggle 数据集 nasirkhalid24 | office/nasirkhalid24 |
| Marvel Cinematic Universe | 18 部电影 | Kaggle 数据集 pdunton | marvel/pdunton |
| Doctor Who | 306 集 | Kaggle 数据集 jeanmidev | drwho/jeanmidev |
| Star Trek | 708 集 | chakoteya.net,基于 Star_Trek_Scripts 项目整理 | startrek/chakoteya |
这一类的特点是:原始数据本身已具备结构化字段(说话人、季、集、场景),加工重点是合并字段、过滤噪音、按集聚合。
4.2 完整剧本类:带详细场景描述
| 作品 | 剧本数 | 抓取来源 | Source 标识 |
|---|---|---|---|
| Top Movies | 919 部电影 | imsdb.com | imsdb |
| Top Movies | 171 部电影 | dailyscript.com | dailyscript |
| Stargate SG-1 | 18 集 | imsdb.com | imsdb |
| South Park | 129 集 | imsdb.com | imsdb |
| Knight Rider | 80 集 | knightriderarchives.com | knightriderarchives |
这一类的特点是:原始材料是非结构化的纯文本剧本(含大量场景描述、动作指示),加工重点是编写解析器从 HTML/DOM 中提取对白、识别说话人、清理脏文本。两类来源在产出阶段最终都会落入完全相同的TEXT / METADATA / SOURCE三列格式。
五、工程实现(一):public.ipynb——结构化数据集的下载与聚合
public.ipynb 面向"数据本身已结构化"的五个来源,核心流程为:下载 → 读取为 DataFrame → 逐集聚合 → 统一三列 → 导出 Parquet。
5.1 环境准备与依赖
仓库提供了 requirements.txt,内容如下:
beautifulsoup4 kaggle numpy pandas pyarrow requests tqdm在 Colab 中运行时,Notebook 顶部预留了设置脚本(git clone 仓库后进入本目录、pip install -r requirements.txt)。其中kaggle库用于下载 Kaggle 数据集,使用前需要先在账户页面获取kaggle.json凭据(Notebook 代码注释中已说明)。
5.2 Friends:TSV 读取 + 场景分隔 + 过滤噪音
Friends 数据来自 emorynlp/character-mining 项目的friends_transcripts.tsv(制表符分隔),读取后得到 67373 行、每行包含season_id / episode_id / scene_id / utterance_id / speaker / tokens / transcript的语料表,覆盖 S01~S10 全部 236 集。
关键加工步骤(对应 Notebook 代码):
friends["group"] = friends[["season_id", "episode_id"]].apply( lambda x: f"{x[0]}_{x[1]}", axis=1 )- 构造 group 键:把
season_id + episode_id拼成s01_e01形式的分组键,作为"一集"的粒度; - 季/集与标题映射:Notebook 内硬编码了全 10 季的剧名表(
episodes[season][episode] -> title),用于把结构化 ID 还原为可读剧名; - 逐集聚合与文本拼接:核心循环如下(行号对应 public.ipynb):
data = {"TEXT": [], "METADATA": [], "SOURCE": []} for name, group in tqdm(friends.groupby("group")): metadata = { "show": "Friends", "season": group["season_id"].values[0], "episode": group["episode_id"].values[0], "title": episodes[group["season_id"].values[0]][group["episode_id"].values[0]], } text, last_scene = f"Friends - {metadata['title']}\r\n\r\n", None group.sort_values(by=["scene_id", "utterance"], ascending=True, inplace=True) for index, row in group.iterrows(): if last_scene is None: last_scene = row["scene_id"] elif last_scene != row["scene_id"]: last_scene = row["scene_id"] text += "\r\n---------------------------------------\r\n\r\n" if row["speaker"] == "unknown" or row["tokens"] == "[]" or pd.isna(row["transcript"]): continue text += f"[{row['speaker'].strip()}] {row['transcript'].strip()}\r\n" data["TEXT"].append(text) data["METADATA"].append(json.dumps(metadata)) data["SOURCE"].append("friends/emorynlp") data = pd.DataFrame(data)这段代码体现了整个数据集格式规范的三个核心约定:
- 文本头:
Friends - {title}作为该集的标题行,接下来空两行开始正文; - 场景分隔符:检测到
scene_id变化时插入---------------------------------------分隔线,对应 README 示例中的"场景切换"语义; - 噪音过滤:说话人为
unknown、tokens 为空[]、台词缺失(NaN)三种情况直接跳过,保证产出文本的干净度。
最终样本形如:
Friends - The Pilot [Monica Geller] There's nothing to tell! He's just some guy I work with! [Joey Tribbiani] C'mon, you're going out with the guy! ...METADATA 为{"show": "Friends", "season": "s01", "episode": "e01", "title": "The Pilot"}的 JSON 字符串,SOURCE 为friends/emorynlp。
5.3 The Office / Marvel / Doctor Who:Kaggle 数据集下载与字段映射
这三个来源统一走 Kaggle API:
kaggle.api.dataset_download_files("nasirkhalid24/the-office-us-complete-dialoguetranscript", "office", unzip=True) kaggle.api.dataset_download_files("pdunton/marvel-cinematic-universe-dialogue", "marvel", unzip=True) kaggle.api.dataset_download_files("jeanmidev/doctor-who", "drwho", unzip=True)- The Office:读取
office/The-Office-Lines-V4.csv(54626 行,字段season / episode / title / scene / speaker / line),去掉多余的Unnamed: 6列,同样用season_episode拼 group 键,按s01/e01的零填充格式写入 METADATA(f"s{str(x).zfill(2)}"),最终 186 集; - Marvel:18 部电影的对话(台词行含
(Rhodes is telling the same story...)这类括号场景说明,保留在文本中),METADATA 含电影标题等信息; - Doctor Who:按
episodeid分组(306 集),METADATA 额外记录季、集、集名,且文本头会带上播出方式和博士代数的信息(Doctor Who ({diffusion}; {doctors}) - {title}),体现"元信息融入正文"的灵活性。
三者的共同收尾动作:
data.to_parquet("office.pq", row_group_size=100, engine="pyarrow", index=False)即以 100 行为一个 row group、使用 pyarrow 引擎导出 Parquet——这也是 README 数据卡片中download_size与dataset_size体积差异的来源之一(Parquet 的列式压缩特性)。
5.4 Star Trek:JSON 抓取与多剧集合
Star Trek 部分通过requests.get直接抓取all_scripts_raw.json(708 集,覆盖 DS9、ENT 等子系列),逐条解析为TEXT / METADATA / SOURCE三列(public.ipynb),METADATA 记录{"show": "Star Trek", "season": "DS9", "episode": ...}等字段,最后导出picard.pq。
六、工程实现(二):imsdb.ipynb——非结构化剧本的爬虫解析
imsdb.ipynb 面向 imsdb.com 等纯文本剧本网站,数据没有结构化字段,必须先写解析器。Notebook 定义了IMSDbCrawler类,核心方法如下:
| 方法 | 职责 |
|---|---|
_get(url, allow_unicode_errors) | 带容错地请求 HTML 页面 |
get_catalog() | 从 imsdb.com 抓取剧本目录(剧名 + 链接),导出 CSV 状态文件 |
download(url) | 下载单个剧本页面 |
_clean_dom(html) | 清洗 HTML DOM,去掉脚本、样式等无关节点 |
is_person(speaker) | 判断一行文本是否为说话人标注(用于区分台词与场景描述) |
parse(html) | 解析剧本正文,提取[说话人] 台词结构 |
save(url) | 组合 download + parse,成功则写入本地.txt文件 |
6.1 清洗与说话人识别
_clean_dom负责把网页还原为接近纯文本的剧本:剥离<script>、<style>等节点后,通过正则与启发式规则判断"哪些行是[说话人]开头"。is_person从源码结构看,是后续parse阶段区分说话人行与场景描述行的关键判定函数。
6.2 剧本解析与脏数据过滤
parse阶段的处理要点(对应 imsdb.ipynb):
- 对 HTML 实体做反转义(如
&→&); - 统一换行符:
\r\n与\n归一化,多个连续空行压缩为两个; - 质量门控:若解析结果中没有出现连续的
[说话人] 台词交替模式(正则\[.+?\] .+?\r\n\r\n\[.+?\] .+?\r\n\r\n),说明该页不是有效剧本,直接返回空串丢弃; - 画外音等特殊标注:文本内保留了
[Person (v.o)]等括号注记,作为场景信息的一部分; save方法额外以"解析后长度小于 128 字符"作为阈值,过滤掉过短的无效页面。
6.3 全量抓取与断点续爬
Notebook 主体展示了带断点续爬能力的批处理循环(imsdb.ipynb):
catalog = pd.read_csv(STATUS) crawled = catalog.copy() for index, row in catalog.iterrows(): if pd.isna(row["status"]): t = time.time() print(f"{row['alpha']} {row['title']}", end=" ") if ic.save(row["link"]): print("✔️", end=" ") crawled.at[index, "status"] = 1.0 else: print("❌", end=" ") crawled.at[index, "status"] = 0.0 print(f"- {(time.time() - t):.3f}s") crawled.to_csv(STATUS, index=False) if pd.notna(crawled["status"]).sum() % 25 == 0: print( f"▶▶▶ {pd.notna(crawled['status']).sum()} done " f"({int(crawled['status'].sum())} successful) out of {len(crawled)} ◀◀◀" ) print("Done.")设计要点:
- 状态落盘:每抓一部就写回 CSV 状态文件,中断后重跑时通过
pd.isna(row["status"])自动跳过已完成条目; - 进度反馈:逐条打印耗时(秒),每完成 25 条打印一次累计统计(成功数 / 总数);
- 结果保存:
save方法把剧本以{剧名}.txt写入FOLDER目录(文件名由 URL 清洗得到,非字母数字字符替换为-/_)。
这份脚本同时覆盖了 README 中"Top Movies (imsdb / dailyscript)、Stargate SG-1、South Park、Knight Rider"等来源的抓取基础设施。全部文本抓取完成后,再按与 public.ipynb 相同的三列格式聚合、导出,汇入最终数据集。
七、在 OpenAssistant 中的使用方式与复现路径
7.1 本地复现
# 进入数据集目录(仓库根目录相对路径) cd data/datasets/tv_dialogue pip install -r requirements.txt随后按需执行两个 Notebook:先运行 public.ipynb 处理五个结构化来源(需要 Kaggle API 凭据kaggle.json),再运行 imsdb.ipynb 抓取剧本网站(耗时长,依赖断点续爬机制)。
7.2 数据加载示例
数据集以 HuggingFace Datasets 的dataset_info格式描述,训练侧可参考仓库的 HuggingFace 数据集导出工具 backend/oasst_backend/utils/hugging_face.py 与 backend/export.py 了解 OpenAssistant 如何将这类对话语料转换为 HuggingFace 格式;模型训练侧的对话数据加载可参考 model/model_training/custom_datasets/oasst_dataset.py。
7.3 注意事项
- 来源授权:数据集以 MIT 协议发布,但各原始剧本来源的版权情况请以各来源网站为准,使用时注意区分"公开可抓取"与"可自由商用";
- 语言范围:当前仅英语(
language: en),如需多语言需自行扩展数据源; - 文本噪声:部分剧本源(尤其完整剧本类)仍可能残留格式噪声,训练前可结合数据清洗脚本进一步过滤。
八、总结
tv_dialogue 是 OpenAssistant 中一个典型的"多来源、单格式"数据集工程:5 个结构化来源 + 4 类非结构化剧本来源,最终统一为TEXT / METADATA / SOURCE三列的 2781 行语料。其工程价值不止于数据本身,更在于沉淀了一套可复用的方法论——结构化数据用 pandas 聚合、非结构化数据用爬虫 + 正则解析、全程以 Parquet 落地、元数据以 JSON 字符串随行存储、来源标识保证可溯源。这套"下载 → 清洗 → 统一格式化 → 导出"的流水线,可以直接迁移到任何其他影视对话语料的构建任务中。
如需继续深入,推荐阅读同一目录下的两个 Notebook 原文,以及 data/datasets 下其他数据集的 README,了解 OpenAssistant 对不同语料来源的差异化处理策略。
【免费下载链接】Open-AssistantOpenAssistant is a chat-based assistant that understands tasks, can interact with third-party systems, and retrieve information dynamically to do so.项目地址: https://gitcode.com/gh_mirrors/op/Open-Assistant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考