Несколько CSV-расшифровок в одну таблицу через Python: как сохранить происхождение реплик
Объединение строк файлов с одинаковой структурой. Колонки source_file и source_record, контроль заголовков, ID внутри файла и итогового числа записей.
Определите состав общего корпуса
Для поиска и сравнения нескольких интервью удобно иметь одну таблицу. До объединения составьте список нужных файлов и убедитесь, что они относятся к согласованным версиям. Не включайте одновременно исходную и исправленную копию одного интервью как два самостоятельных материала.
Положите только выбранные CSV в папку inputs. В учебном формате каждый файл имеет колонки replica_id, speaker, timecode, text. Другие форматы сначала приведите к согласованному контракту отдельным проверенным процессом, а не подгоняйте заголовки во время склейки.
Текстовую основу можно получить через Транскрибатор. CSV-корпус в этом примере создаётся отдельно; наличие показанных полей и общего экспорта не приписывается сервису.
Различайте добавление строк и соединение по ключу
При конкатенации все записи добавляются в один список. Это не JOIN, который присоединяет поля другой таблицы к найденной записи. Не используйте replica_id как глобальный ключ корпуса, если разные интервью имеют собственную нумерацию.
Для привязки замечаний к одной реплике существует другая задача — соединение комментариев по ID. Здесь одинаковый r01 из двух файлов сохраняется дважды с различным source_file.
source_record обозначает номер записи данных в конкретном CSV, считая с единицы после заголовка. Он не равен физической строке файла: многострочная реплика может занимать несколько строк. Не используйте его как таймкод аудио.
Проверьте файлы и выполните сборку
Скрипт ниже использует стандартную библиотеку Python. Он получает список inputs/*.csv в порядке имён. Результат all-replicas.csv создаётся в рабочей папке, а не в inputs, чтобы следующий запуск не прочитал его обратно как новый источник.
Сначала все данные проверяются в памяти. Заголовки должны совпадать по названиям и порядку. Лишнее или недостающее поле останавливает работу. Пустые и повторные ID внутри одного файла также вызывают ошибку, но одинаковые ID между разными файлами допустимы.
Для чтения используется utf-8-sig, для записи — UTF-8; newline="" нужен для корректной обработки переносов внутри полей. Режим x предотвращает перезапись существующего результата. Если файл уже есть, создайте отдельное имя версии после проверки состава.
Сверьте число и происхождение записей
После выполнения сохраните напечатанный список имён и количеств. Прочитайте общий CSV обратно и проверьте, что число записей равно сумме по входным файлам, а заголовок встречается только один раз. Сверьте первые и последние записи каждого источника.
Учебный тест объединил два файла с одинаковым локальным ID, сохранив обе реплики с разным source_file. Запятая и перенос внутри текста сохранились в одном поле. Файл с неподходящим заголовком вызвал остановку до создания результата.
Проверьте редкие имена и фрагменты с кавычками через контроль CSV-колонок. Успешная склейка не подтверждает одинаковую кодировку смысла меток: «А» в одном интервью и «А» в другом не обязательно один человек.
Сохраните версии и ограничения корпуса
Имя файла удобно как метка происхождения только в зафиксированном комплекте. После переименования источника связь изменится. Сохраните входные файлы, список состава и версию результата; для долгоживущего корпуса добавьте согласованный interview_id.
Скрипт не объединяет временные шкалы интервью в одну запись и не пересчитывает таймкоды. Время остаётся относительным к своему источнику. Не сортируйте весь корпус по timecode как по общей последовательности разговора.
Пример рассчитан на умеренный объём: список всех реплик занимает память. Для больших наборов нужен отдельный процесс с контролем ресурсов и промежуточной проверкой. Не удаляйте входные файлы после успешной сборки: они нужны для проверки происхождения и дальнейшей редактуры.
Учебный пример локальных ID в корпусе
Один и тот же ID используется в двух независимых интервью.
| source_file | replica_id | Как трактовать |
|---|---|---|
| interview-a.csv | r01 | Первая реплика источника A |
| interview-b.csv | r01 | Другая реплика источника B |
| all-replicas.csv | Не входной файл | Хранить вне inputs |
Не считайте одинаковые ID или метки говорящих между интервью доказательством совпадения участников.
Сборка согласованных CSV из папки inputs
import csv
from pathlib import Path
fields = ["replica_id", "speaker", "timecode", "text"]
paths = sorted(Path("inputs").glob("*.csv"), key=lambda p: p.name)
if not paths:
raise ValueError("Нет входных CSV")
rows, manifest = [], []
for path in paths:
count, seen = 0, set()
with path.open(encoding="utf-8-sig", newline="") as f:
reader = csv.DictReader(f)
if reader.fieldnames != fields:
raise ValueError("Заголовки: " + path.name)
for record, row in enumerate(reader, 1):
if None in row or any(v is None for v in row.values()):
raise ValueError("Число полей: " + path.name)
key = row["replica_id"]
if not key or key in seen:
raise ValueError("ID: " + path.name)
seen.add(key)
rows.append([path.name, record] + [row[k] for k in fields])
count += 1
manifest.append((path.name, count))
with open("all-replicas.csv", "x", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["source_file", "source_record"] + fields)
writer.writerows(rows)
for name, count in manifest:
print(name, count)
print("Итого:", len(rows))Частые вопросы
Это JOIN таблиц?
Нет. Строки добавляются друг под другом с полями происхождения.
Одинаковые ID в разных файлах удаляются?
Нет. Это могут быть разные реплики разных интервью.
source_record — номер строки файла?
Это номер CSV-записи данных, а не физической строки.
Таймкоды становятся общей шкалой?
Нет. Они сохраняют связь со своим исходным интервью.