ТранскрибаторТранскрибатор
Практическая инструкция

Несколько CSV-расшифровок в одну таблицу через Python: как сохранить происхождение реплик

Объединение строк файлов с одинаковой структурой. Колонки source_file и source_record, контроль заголовков, ID внутри файла и итогового числа записей.

Транскрибатор
ЗАПИСЬПЛАН РАБОТЫОтберите согласованные CSVДобавьте поляпроисхожденияСверьте состав и числозаписейПроверить результатОТ СЛОВ К РЕЗУЛЬТАТУ
Работа с реальной записьюНаглядный учебный примерШаблон для копирования
1Отберите согласованныеCSV2Добавьте поляпроисхождения3Сверьте состав и числозаписей
Каждая итоговая запись сохраняет источник и порядковый номер записи в нём; результат находится вне входной папки.

Определите состав общего корпуса

Для поиска и сравнения нескольких интервью удобно иметь одну таблицу. До объединения составьте список нужных файлов и убедитесь, что они относятся к согласованным версиям. Не включайте одновременно исходную и исправленную копию одного интервью как два самостоятельных материала.

Положите только выбранные 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_filereplica_idКак трактовать
interview-a.csvr01Первая реплика источника A
interview-b.csvr01Другая реплика источника B
all-replicas.csvНе входной файлХранить вне inputs
СоставВыбранные версииПроверить списокПроисхождениеФайл и номер записиСохранить в каждой строкеИтогСумма записейПрочитать обратно
Происхождение сохраняет различие реплик, даже когда локальные ID совпадают.

Не считайте одинаковые 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-записи данных, а не физической строки.

Таймкоды становятся общей шкалой?

Нет. Они сохраняют связь со своим исходным интервью.