💡 Полный рабочий пример доступен на GitHub:
sanitize-office-document-pii-python
Данные, которые никто не проверяет перед отправкой
Квартальный отчёт совета директоров отправляется внешнему аудитору. Текст безупречен; три цикла проверки это гарантируют. Сам файл — другая история. Его свойства всё ещё содержат имя аналитика, который его подготовил, менеджера, который его доработал, дочернюю компанию, владеющую шаблоном, метку времени LastPrinted с ночи перед дедлайном и идентификатор одобряющего в SharePoint из внутреннего процесса согласования. Никакая из этих сведений не отображается на страницах. Всё это перемещается вместе с файлом.
Удаление персональных данных — это рабочий процесс GroupDocs.Metadata для Python через .NET, который программно удаляет такие свойства, содержащие идентифицирующую информацию, из файлов Word, Excel и PowerPoint. В этой статье сравниваются три подхода, предлагаемые API: удаление по тегам для полей идентификации, удаление по шаблону имени для семейств свойств, таких как комментарии и версии, и однократный вызов sanitize(), который очищает всё. Вы также увидите шаг, который пропускают большинство скриптов санитизации, — проверочный скан, подтверждающий, что очистка действительно произошла.
Почему PII в метаданных заслуживает отдельного конвейера
Инструменты проверки контента проверяют то, что читают люди. Они не проверяют то, что хранит файловая система, и именно в этом разрыве возникают инциденты с соблюдением требований. Запрос GDPR охватывает персональные данные в полях Author и Manager так же, как и данные в тексте. Юридическое раскрытие читает счётчики версий и суммарное время редактирования, чтобы восстановить, как долго обсуждался позиционный документ. Рецензенты тендеров могут построить структуру вашей организации по свойствам рабочего процесса SharePoint, а поля комментариев пресс‑релиза сохраняют имена рецензентов вместе с замечаниями черновика. Каждый из этих пунктов — находка. Ни один из них не виден в теле документа.
Предварительные требования
Перед началом убедитесь, что у вас есть:
- Python 3 с pip
- GroupDocs.Metadata для Python через .NET, зафиксированный в репозитории‑примере на версии 26.5
- Office‑файл с реальными свойствами для практики
Установка
pip install groupdocs-metadata-net==26.5
companion repository содержит образец DOCX и выполняет каждый фрагмент кода ниже как проверенный конвейер.
Метод 1: Удаление идентификации по тегам
Четыре самых чувствительных поля — Author, LastSavedBy, Manager и Company — имеют разные внутренние имена в разных форматах Office. Система тегов решает эту проблему: вместо указания конкретных свойств предикат запрашивает всё, помеченное как человек или компания.
# Match identity properties by meaning, not by format-specific name
with Metadata("board-report.docx") as metadata:
removed = metadata.remove_properties(lambda p:
Tags.person.creator in list(p.tags) # Author, LastSavedBy
or Tags.person.editor in list(p.tags)
or Tags.person.manager in list(p.tags)
or Tags.corporate.company in list(p.tags))
metadata.save("board-report-clean.docx")
print(f"{removed} identity properties removed")
Ключевые моменты:
- Независимость от формата: один и тот же lambda‑выражение очищает DOCX, XLSX и PPTX, потому что теги классифицируют по роли.
- Подсчёт результата:
remove_propertiesвозвращает количество совпавших свойств, что следует фиксировать в журнале аудита. - Копирующая семантика: сохранение в новый путь оставляет оригинал для ваших записей.
💡 Совет: этот проход сохраняет Title, Subject и другие описательные поля, поэтому файл остаётся удобным для поиска и индексации DMS.
Метод 2: Удаление по шаблону имени для семейств свойств
Теги охватывают классифицированные концепции. Целые семейства «протекающих» полей находятся за пределами этой классификации: свойства комментариев, счётчики версий, метки рабочего процесса SharePoint. Для них сопоставляют имя свойства напрямую.
# Comment fields often live in custom properties the tag system
# does not classify, so match them by name substring
with Metadata("board-report.docx") as metadata:
removed = metadata.remove_properties(lambda p:
p.name is not None and (
"Comment" in p.name
or "Reviewer" in p.name
or "Reviewed" in p.name))
metadata.save("board-report-no-comments.docx")
Та же структура обрабатывает остальные два семейства; меняется лишь список подстрок:
| Семейство | Подстроки для поиска |
|---|---|
| История правок | Revision, TrackedChange, LastPrinted, TotalEditingTime, EditTime |
| Сервер / рабочий процесс | Server, Workflow, Approver, ContentType, Template |
Это компромисс между точностью и охватом: "Comment" также захватывает Comments и CommentCount, что обычно требуется при санитизации. Широкие подстроки могут совпасть с безвредными полями шаблона, поэтому сравнивайте полученный счётчик с ожидаемыми значениями.
💡 Совет: запускайте каждое семейство отдельным проходом, если журнал аудита требует подсчётов по категориям; объединяйте подстроки в один предикат, если это не требуется.
Метод 3: Однократный полный sanitize()
Когда файл покидает организацию и никакие метаданные не должны сохраняться, откажитесь от написания предикатов.
# One call, every detected metadata package
with Metadata("board-report.docx") as metadata:
removed = metadata.sanitize()
metadata.save("board-report-final.docx")
print(f"sanitize() removed {removed} properties")
sanitize() очищает каждый пакет метаданных, обнаруженный библиотекой: поля идентификации в документ‑инфо, комментарии, историю правок, авторов отслеживаемых изменений и пользовательские части OOXML. Поведение описано на странице Clean metadata. Его сила же является и стоимостью. Title и Subject исчезают вместе с PII, поэтому этот метод лучше использовать на этапе экспорта, а не в середине рабочего процесса совместной работы.
Нужно ли выполнять все четыре целевых прохода?
Нет. Каждый проход существует потому, что за риск отвечает разная команда. Поля идентификации беспокоят специалистов по конфиденциальности, следы комментариев — юридический отдел, счётчики правок — переговорщиков, а серверные поля — службу безопасности. Запускайте те проходы, которые соответствуют вашим проверяющим, в любом порядке, поскольку каждый пишет собственную копию вывода. Когда ни одно поле не должно сохраняться, сразу переходите к sanitize() и проверяйте результат.
Сравнение трёх подходов
| Метод | Лучшее применение | Ключевые преимущества | Ограничения |
|---|---|---|---|
| Удаление по тегам | Рабочие копии, мультиформатные конвейеры | Независимость от формата, сохраняет описательные поля | Охватывает только концепции, классифицированные тегами |
| Удаление по шаблону имени | Комментарии, правки, серверные поля | Достигает пользовательских свойств, которые пропускают теги | Требуется настройка подстрок под конкретную среду |
Полный sanitize() |
Финальный экспорт за пределы организации | Не пропустит забытое свойство | Удаляет безвредные поля вместе с PII |
Подходы естественно комбинируются: целевые проходы, пока документ «жив», и sanitize() при отправке.
Проверьте перед тем как доверять
Возврат количества удалённых свойств не является доказательством чистоты файла. Репозиторий завершает каждый запуск повторным открытием очищенного вывода и сканированием его функцией find_properties, используя предикат, объединяющий правила тегов и имён из всех вышеописанных проходов.
def is_pii(p):
if p.name is None:
return False
return (
Tags.person.creator in list(p.tags)
or Tags.person.editor in list(p.tags)
or Tags.person.manager in list(p.tags)
or Tags.corporate.company in list(p.tags)
or any(n in p.name for n in (
"Comment", "Reviewer", "Revision", "TrackedChange",
"Classification", "Department", "Server", "Workflow")))
with Metadata("board-report-final.docx") as metadata:
for p in metadata.find_properties(is_pii):
value = (str(p.interpreted_value) if p.interpreted_value is not None
else (str(p.value) if p.value is not None else ""))
if value and value not in ("0", "0.0"):
print(f"LEAK {p.name}={value}")
Полная версия в репозитории сортирует оставшиеся свойства в две группы, и это различие имеет значение. Утечки метаданных должны быть нулевыми. Оставшиеся в теле документа следы — комментарии Word и отслеживаемые изменения внутри word/document.xml — относятся к контенту, который API метаданных не может достать; их удаление требует библиотеки редактирования контента, такой как Aspose.Words. Честный отчёт перечисляет обе группы, а не объявляет победу после первого сканирования. При первом запуске этого сканирования на «чистом» файле он обнаружил поле Department, которое корпоративный шаблон тихо добавлял в течение нескольких месяцев.
Лучшие практики и советы
- Санитизируйте копии, а не оригиналы: каждый фрагмент кода пишет в новый путь, оставляя источник для ваших записей и правил хранения.
- Записывайте счётчики: возвращаемые значения
remove_propertiesиsanitize()— ваш журнал аудита. Сохраняйте их по файлу и по проходу. - Внедрите проверку в CI: проверка на утечки, прерывающая сборку, ловит регрессии шаблонов в тот же день, когда они появляются, а не когда их замечает клиент.
- Помните о границе метаданных/контента: никогда не объявляйте файл чистым, пока в теле остаются комментарии; фиксируйте их как отдельную находку.
- Лицензирование: режим оценки воспроизводит всё, что описано в статье; используйте лицензию в продакшене, чтобы метки оценки не попали в исходящие файлы.
Заключение
Три подхода, одно правило выбора. Сопоставляйте по тегу, когда концепция классифицирована и файл должен оставаться полезным. Сопоставляйте по имени, когда семейство живёт в пользовательских свойствах. Вызывайте sanitize(), когда файл пересекает границу доверия, и проверяйте результат сканированием чтения независимо от выбранного пути.
Готовы углубиться? Следующие шаги:
- Изучите поверхность предиката на странице документации Remove metadata properties
- Следуйте step-by-step use case guide , построенному на том же коде
- Клонируйте sample repository и запустите проверенный конвейер на своих файлах
Дополнительные ресурсы
- GroupDocs.Metadata Documentation
- API Reference
- Sample Projects on GitHub
- GroupDocs.Metadata Blog Category
Есть вопросы или хотите поделиться своей реализацией? Обращайтесь на support forum.