为质性编码准备访谈转录稿
在你自己的电脑上转写访谈和焦点小组录音,对文本去标识化,并在编码和导出到 NVivo 之前为每位参与者设置一个案例。
访谈和焦点小组录音要经过五个步骤才能变成已编码的摘录,依次是:带说话人标签和时间戳的转写、去标识化、在整份转录稿与摘录之间做出选择、按每位参与者一个案例载入数据,以及编码和导出。Potato 用 WhisperX 或 faster-whisper 在你自己的电脑上转写录音,在 QDA 模式中借助备忘录和案例为文本编码,并把已编码的项目以 REFI-QDA .qdpx 文件导出到 NVivo、ATLAS.ti 或 MAXQDA。Potato 不会对转录稿去标识化,所以你的团队需要在载入文本之前替换姓名和其他标识信息。
转写把录下的语音变成文字,转录稿记录到多细的程度是一项分析上的决定。Oliver、Serovich 和 Mason(2005)区分了两种惯例(参考文献 2)。自然化转写尽可能详细地记录每一句话语。去自然化转写会修正语法,删去口吃和停顿,并把非标准口音规范化。说话人分离标出谁在什么时候说话,去标识化则删除或替换可能识别出参与者的细节。在 Potato 中,案例把属于同一位参与者的条目归为一组,备忘录是附在一份转录稿或一段高亮文字上的分析笔记。REFI-QDA 是 NVivo、ATLAS.ti 和 MAXQDA 都能读写的交换标准。
本指南介绍编码前后的准备工作。两名编码者编码访谈转录稿并报告 alpha 介绍两名编码者之间的信度,如何标注 Whisper 转写文本介绍如何对照音频校正语音识别的输出。为质性研究编制码本介绍如何撰写代码,质性数据分析软件对比介绍如何选择工具。
先确定转写惯例
McLellan、MacQueen 和 Neidig(2003)指出,没有哪一种转写格式适合所有类型的质性数据、场景或理论取向,准备不当的转录稿可能拖延分析,甚至损害分析(参考文献 1)。一份成文的惯例让每位转写者和每一轮校正都遵循相同的规则,你的团队要在转写第一份录音之前商定这份惯例。惯例涵盖三项决定:
- 细节。 惯例规定是否标注填充词、停顿、重叠话语和笑声,以及用什么记号标注。
- 说话人标签。 每位说话人用一个角色或参与者 ID 表示,例如
Interviewer或P07,绝不用真实姓名。 - 时间戳。 每个话轮开头的时间让编码者能在录音中找到这段话。
Potato 的本地转写写出的是 Whisper 模型生成的文字,Potato 也没有任何设置能让模型标出停顿、重叠或填充词。因此,自然化转录稿需要对照录音再做一轮校正。如何标注 Whisper 转写文本介绍如何在 Potato 中设置这轮校正。
在你自己的电脑上转写录音
知情同意书和伦理审批常常限定谁可以听访谈录音,以及音频可以传到哪里。potato transcripts --transcribe 用 sherpa-onnx 做说话人分离,语音识别在 WhisperX 已安装时用 WhisperX,否则用 faster-whisper。transcribe 附加依赖会安装 faster-whisper 和 sherpa-onnx,WhisperX 需要单独安装(pip install whisperx)。这三个工具都在你自己的电脑上运行,不经过任何云服务:
pip install 'potato-annotation[transcribe]'
potato transcripts ./recordings --transcribe --asr-model small.en --diarize --num-speakers 2 -o data/transcripts.json该命令为每份录音写出一个条目,其中包含每个话轮的说话人、开始时间、结束时间和文字。Whisper 模型从 tiny 到 large-v3 不等,默认是 base。模型越大,运行越慢,转写也越准确。Potato 的帮助文本提到,用 --num-speakers 给出说话人数量后,两人访谈的说话人分离效果会明显更好。在一段 21 秒、有两个声音的合成访谈上测试时,命令输出了下面的摘要:
Scanned 1 file(s):
int07.wav local ASR + diarization 5 turns 21.4s 2 speaker(s): SPEAKER_00, SPEAKER_01Potato 2.14.0 会把引擎名写在 local ASR 的位置,例如 ASR (whisperx) 或 ASR (faster-whisper)。
说话人分离把说话人命名为 SPEAKER_00、SPEAKER_01 等,顺序不固定。在测试中,SPEAKER_01 是访谈者,下文的准备脚本会把每个标签映射为一个角色或参与者 ID。Potato 自己的源代码注明,基于聚类的说话人分离在多人同时说话时表现很差。编码开始前,请对照录音检查每场焦点小组的说话人标签。
模型权重在首次使用时下载。两个说话人分离模型文件合计约 47 MB,Whisper 的下载量随模型大小增加。权重放到机器上之后,转写可以在气隙环境中运行,详见气隙与离线部署。对于离线机器,请把一个 CTranslate2 格式的 Whisper 模型目录复制过去,并把该目录的路径传给 --asr-model,再把 POTATO_MODEL_CACHE 设为存放说话人分离模型的文件夹。
Potato 会把每份原始转录稿以 <name>.whisper.json 缓存在录音旁边,缓存中保留了所有姓名。请把缓存文件和录音放在一起,或者传入 --asr-cache,把缓存文件写到一个同样受保护的文件夹。转写之前会先运行语音活动检测,防止 Whisper 在静音片段上凭空生成文字。这项检测也可能在没有任何警告的情况下丢掉轻声或耳语。如果转录稿比录音短,请加上 --asr-no-vad 重新运行命令。WhisperX 始终运行语音活动检测,所以安装了 WhisperX 时,要让 --asr-no-vad 生效,请再加上 --asr-engine faster-whisper。
编码前对转录稿去标识化
Potato 没有能在转录稿中查找或替换姓名、地名或其他标识信息的工具。因此,去标识化必须在 Potato 载入文本之前完成,因为每个编码者界面、每条备忘录、每份引文报告和每个 .qdpx 导出都会按载入时的原样呈现转录稿。Potato 唯一的文本清理功能在项目结束时运行。python -m potato.publish --scrub-pii 会在发布的数据集中把电子邮件地址和电话号码替换为 [EMAIL] 和 [PHONE],这个选项不会处理姓名。
英国数据服务(UK Data Service)建议用化名或描述性标签(例如“[colleague 1]”)替换识别性细节,而不是删除这些内容。同一份指南要求在所有转录稿中一致地应用每一处替换,并为改动留下日志。该指南还要求有所克制,因为过度匿名化会删去情境细节,降低数据的诠释价值(参考文献 4)。Saunders、Kitzinger 和 Kitzinger(2015)在对植物状态和最低意识状态患者家属的访谈中描述了这种取舍(参考文献 3)。几位作者把医院名称替换为“Hospital 1”这样的标签,并报告说某些保护措施让分析失去了数据的一部分丰富性。
下一节的准备脚本会把替换表 pseudonyms.csv 应用到每份转录稿上,这张表同时也是匿名化日志。由于这张表能把化名映射回真实姓名,请把这张表存放在 Potato 所服务的项目文件夹之外。替换表只能捕捉表中列出的拼写。因此,替换之后仍要有人通读每份转录稿,并判断工作单位或罕见职业这类间接标识信息。在测试转录稿中,替换表把参与者姐妹的名字替换为 Ana,把一个街道名替换为“[street name]”,而这位姐妹工作的面包店则留给读者判断。载入之后,用 Potato 的全文搜索在语料中逐一搜索日志里的每个姓名。每次搜索都不应返回任何转录稿。
整份转录稿还是摘录
编码者看到的单位是数据文件中的一个条目。把整份转录稿作为一个条目,能让编码者看到访谈的完整脉络,.qdpx 导出也会为每场访谈写出一个来源。项目也可以把每份转录稿拆成摘录,例如每个参与者回答一个条目,这时导出会为每条摘录写出一个来源。摘录更适合以下两种情况:
- 编码者间信度。 一致性是在两名编码者都作答的单元上计算的,固定的摘录让两名编码者面对同一个单元。双编码者教程正是出于这个原因把转录稿拆成一个个回答。
- 焦点小组。 Potato 把每个条目最多归入一个案例,因此作为一个条目载入的小组转录稿只能属于一个案例。每个参与者话轮一个条目,才能让每位参与者的话轮组成该参与者自己的案例。
把转录稿展平为带说话人标签的文本
potato transcripts 的输出把每份转录稿保存为一个嵌套的话轮列表,而 .qdpx 导出会跳过文本不是纯字符串的条目。因此,要导入 NVivo、ATLAS.ti 或 MAXQDA 的项目需要先把每份转录稿展平为带说话人标签的文本。脚本所依据的决定保存在三个小型 CSV 文件中:
speakers.csv transcript,speaker,name
int07,SPEAKER_01,Interviewer
int07,SPEAKER_00,P07
participants.csv participant_id,site,age_group
P07,rural,35-49
pseudonyms.csv original,replacement
Maria,Ana
Main Street,[street name]脚本把同一说话人的连续片段合并为一个话轮,在每个话轮开头写入时间戳,应用替换表,并把参与者的属性复制到条目上。加上 --excerpts 后,脚本改为每个参与者话轮写出一个条目:
"""Turn `potato transcripts` output into speaker-labeled text for coding.
Usage:
python prepare_transcripts.py data/transcripts.json speakers.csv participants.csv \
pseudonyms.csv > data/interviews.jsonl
python prepare_transcripts.py ... --excerpts > data/excerpts.jsonl
"""
import csv
import json
import re
import sys
transcripts, speakers_csv, participants_csv, pseudonyms_csv = sys.argv[1:5]
excerpts = "--excerpts" in sys.argv
names = {(r["transcript"], r["speaker"]): r["name"] for r in csv.DictReader(open(speakers_csv))}
people = {r["participant_id"]: r for r in csv.DictReader(open(participants_csv))}
swaps = [(re.compile(rf"\b{re.escape(r['original'])}\b"), r["replacement"])
for r in csv.DictReader(open(pseudonyms_csv))]
def clock(seconds):
m, s = divmod(int(seconds), 60)
return f"{m // 60:02d}:{m % 60:02d}:{s:02d}"
def pseudonymize(text):
for pattern, replacement in swaps:
text = pattern.sub(replacement, text)
return text
for item in json.load(open(transcripts, encoding="utf-8")):
tid = item["id"]
turns = [] # merge consecutive segments from the same speaker into one turn
for t in item["conversation"]["turns"]:
name = names.get((tid, t.get("speaker")), t.get("speaker") or "Unknown")
if turns and turns[-1]["name"] == name:
turns[-1]["text"] += " " + t["text"]
else:
turns.append({"name": name, "start": t.get("start") or 0, "text": t["text"]})
for t in turns:
t["text"] = pseudonymize(t["text"])
if excerpts: # one item per participant turn, for focus groups
for n, t in enumerate(turns):
if t["name"] in people:
print(json.dumps({"id": f"{tid}-{n:03d}", "transcript": tid,
**people[t["name"]],
"text": f"[{clock(t['start'])}] {t['name']}: {t['text']}"}))
continue
participant = next(t["name"] for t in turns if t["name"] in people)
text = "\n\n".join(f"[{clock(t['start'])}] {t['name']}: {t['text']}" for t in turns)
print(json.dumps({"id": tid, **people[participant], "text": text}))在测试录音上,脚本写出了一个条目,下面展示时缩短了文本:
{"id": "int07", "participant_id": "P07", "site": "rural", "age_group": "35-49", "text": "[00:00:00] Interviewer: Thanks for joining, can you tell me how you get to the clinic?\n\n[00:00:04] P07: I take two buses, and it takes most of the morning, my sister Ana drives me when she can, but she works at the bakery on [street name].\n\n..."}已经以文件形式存在的转录稿也走同一个脚本。不加 --transcribe 时,potato transcripts 可读取 21 种转写和字幕格式,包括 WhisperX JSON、SubRip、WebVTT、Praat TextGrid 和 ELAN EAF,完整列表见转写格式。在其他 QDA 程序中编码过的项目可以通过 potato import 迁移过来,详见导入项目。
在 QDA 模式中编码
下面的配置为整份转录稿设置开放式编码,每位参与者一个案例。该配置在 Potato 2.13.1 上通过了 python -m potato.validate_cli config.yaml --strict:
annotation_task_name: Clinic access interviews
task_dir: .
output_annotation_dir: annotation_output/
data_files:
- data/interviews.jsonl
item_properties:
id_key: id
text_key: text
user_config:
allow_all_users: true
users: []
# Open coding by one analyst: a codebook that grows while reading, memos, cases, search
qda_mode:
enabled: true
# One case per participant, with the participant's site and age group on the case
cases:
enabled: true
key: participant_id
attributes: [site, age_group]
annotation_ui:
memos: true
search:
enabled: true
annotator_claim: true
annotation_schemes:
- annotation_type: span
name: codes
description: Highlight a passage and apply a code, or press i to create a code from the highlighted words.
codebook: true
labels: [travel to the clinic, cost of care, family support, delaying care]QDA 模式把码本设为 open。分析者在阅读时添加代码,这四个标签只是码本的初始代码。选中一段文字后按 i,会用所选的词语创建一个原位代码。之后重命名或合并某个代码时,此前的编码仍留在旧名称下,详见质性编码的共享码本。search 块开启全文搜索,也就是上文检查姓名时用到的功能。用 potato start config.yaml -p 8000 启动服务器。
cases 块按 participant_id 把条目归组,并在每个案例上保存 site 和 age_group。Potato 2.13.1 只根据条目字段建立案例,没有任何手动把条目分配给案例的方法。因此,每个条目都需要带有参与者 ID。测试项目的服务器日志报告了 Cases auto-detect: 1 case(s), 1 instance(s) assigned。编码进行期间,管理员 API 端点 /admin/api/code_crosstab?attribute=site 可按案例属性统计代码,详见质性编码中的案例与案例属性。
备忘录在每份转录稿的 Notes 面板中打开。新建的备忘录默认是私有的。编码者如果希望团队其他成员阅读某条备忘录,就在该备忘录的可见性菜单中选择 Shared,详见质性编码中的分析备忘录。QDA 模式假定由一位分析者处理整个语料。如果两名编码者编码同一批摘录,请参照双编码者教程和质性研究中的编码者间信度。如果要让语言模型担任第二编码者,请参阅大语言模型辅助的质性编码。
导出已编码的转录稿
下面三种导出涵盖了常见的交付物:
python -m potato.export -c config.yaml -f qdpx -o export/
python -m potato.export -c config.yaml -f quotation_report --option include_memos=true -o export/
python -m potato.export -c config.yaml -f csv -o export/第一条命令写出一个 .qdpx 文件,其中每份转录稿是一个文本来源,代码是没有层级的扁平列表,每条编码都附有其编码者。该导出不包含备忘录和案例。由于配置中显式设置了 cases.enabled 和 annotation_ui.memos,导出会打印一条警告,点名备忘录和案例。第二条命令写出 quotations.csv,每个高亮段落一行,给出代码、引文、字符偏移量、转录稿 ID 和编码者,之后每条备忘录一行。第三条命令把每位编码者的标注写成一张表格,供 R 或 Python 使用。案例属性保留在 participants.csv 中,数据文件的 id 和 participant_id 字段可以把这些属性与引文报告连接起来。
常见问题
访谈转录稿应该逐字转写,还是做清理后的逐字转写?
根据分析需要选择细节程度。Oliver、Serovich 和 Mason(2005)指出,自然化的完全逐字转写在会话分析中最为常见。研究参与者就某个话题说了什么的研究,通常可以使用去自然化的清理版转录稿。Potato 的本地转写写出的是 Whisper 模型生成的文字。因此,逐字转写的惯例需要对照录音再做一轮校正。
访谈转录稿应该在什么时候去标识化?
在编码开始之前对转录稿去标识化,因为 Potato 会完全按载入时的样子显示和导出文本。Potato 没有去标识化工具。请在载入之前应用一张化名前后一致的替换表,通读每份转录稿查找间接标识信息,并在载入后的语料中搜索每个原始姓名。
faster-whisper 能否在不上传音频的情况下转写访谈?
能,faster-whisper 在你自己的电脑上运行 Whisper,potato transcripts --transcribe 在未安装 WhisperX 时用 faster-whisper 把一个文件夹的录音转成一个 Potato 数据文件。安装了 WhisperX 时,该命令改在你自己的电脑上运行 WhisperX,--asr-engine faster-whisper 则保留 faster-whisper。模型权重在首次使用时下载,首次下载之后,转写不再需要网络。
延伸阅读
- 两名编码者编码访谈转录稿并报告 alpha:两名编码者之间的信度与裁定
- 如何标注 Whisper 转写文本:对照音频校正机器转写
- 转写格式:Potato 可读取的每种转写格式及本地转写选项
- QDA 模式、共享码本、分析备忘录、案例与案例属性和全文搜索
- 项目快照:冻结已编码的项目,作为审计轨迹
- 为质性研究编制码本
- 质性研究中的编码者间信度
- 气隙与离线部署
- Potato 中的质性研究:一处汇总所有质性研究功能
参考文献
- McLellan, E., MacQueen, K. M., & Neidig, J. L. (2003). Beyond the Qualitative Interview: Data Preparation and Transcription. Field Methods, 15(1), 63-84. https://doi.org/10.1177/1525822X02239573
- Oliver, D. G., Serovich, J. M., & Mason, T. L. (2005). Constraints and Opportunities with Interview Transcription: Towards Reflection in Qualitative Research. Social Forces, 84(2), 1273-1289. https://doi.org/10.1353/sof.2006.0023
- Saunders, B., Kitzinger, J., & Kitzinger, C. (2015). Anonymising interview data: challenges and compromise in practice. Qualitative Research, 15(5), 616-632. https://doi.org/10.1177/1468794114550439
- UK Data Service. Anonymisation for text data. Retrieved November 2, 2026, from https://ukdataservice.ac.uk/learning-hub/data-producer-support/preparing-data-for-sharing-and-reuse/anonymising-data-and-protecting-participants/anonymisationfortextdata/