Библиотека python-docx: работа с файлами Word
Библиотека python-docx позволяет создавать, читать и изменять документы Word в формате .docx прямо из кода на Python. Ниже разобраны установка, объектная модель документа, работа с текстом, таблицами, картинками, колонтитулами и полями страницы, а также заполнение готового шаблона и типичные ошибки.

Содержание статьи
Установка и первый документ
Пакет ставится через pip под именем python-docx, а импортируется как docx. Эта разница в названиях часто сбивает с толку: команда pip install docx поставит совсем другой, давно заброшенный пакет, который не работает в Python 3. Если вы еще не знакомы с установкой библиотек, загляните в руководство по менеджеру пакетов pip.
# в терминале: pip install python-docx
from docx import Document
doc = Document() # пустой документ на базе встроенного шаблона
doc.add_heading('Отчет за октябрь', level=0)
doc.add_paragraph('Первый абзац документа.')
doc.save('report.docx')Вызов Document() без аргументов создает новый файл со стилями по умолчанию. Если передать путь, например Document('contract.docx'), библиотека откроет существующий документ. Метод save() принимает имя файла или файловый объект, открытый в двоичном режиме, поэтому документ можно сохранить в память через io.BytesIO и, например, отдать пользователю из веб-приложения без записи на диск.
Как устроен документ: абзацы и фрагменты
Документ Word в python-docx представлен как последовательность блоков: абзацев (Paragraph) и таблиц (Table). Каждый абзац, в свою очередь, состоит из фрагментов Run. Фрагмент - это кусок текста с одинаковым оформлением. Если в предложении одно слово выделено жирным, абзац будет содержать минимум три фрагмента: текст до слова, само слово и текст после него.
p = doc.add_paragraph('Срок оплаты: ')
run = p.add_run('10 дней')
run.bold = True
p.add_run(' с момента подписания.')
print(len(p.runs)) # 3Понимание этой модели избавляет от главной путаницы новичков. Оформление (шрифт, цвет, жирность) задается на уровне фрагмента, а выравнивание, отступы и интервалы - на уровне абзаца через paragraph_format.
Заголовки, списки и разрывы страниц
Метод add_heading(text, level) принимает уровень от 0 до 9. Уровень 0 соответствует стилю Title, уровни 1-9 стилям Heading 1-9. Списки создаются обычными абзацами со стилем маркированного или нумерованного списка, отдельного объекта "список" в библиотеке нет.
doc.add_heading('План работ', level=1)
for task in ['Собрать данные', 'Построить графики', 'Отправить отчет']:
doc.add_paragraph(task, style='List Number')
doc.add_paragraph('Без номера', style='List Bullet')
doc.add_page_break()Нумерацию писать вручную ("1. Собрать данные") не нужно: стиль List Number сам проставит номера, а Word пересчитает их при редактировании.
Форматирование текста и абзацев
Размеры в python-docx задаются через вспомогательные классы из docx.shared: Pt для пунктов, Cm и Mm для метрических единиц, Inches для дюймов. Цвет задается через RGBColor, выравнивание через перечисление WD_ALIGN_PARAGRAPH.
from docx.shared import Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
p = doc.add_paragraph()
run = p.add_run('Важное уведомление')
run.font.name = 'Times New Roman'
run.font.size = Pt(14)
run.font.color.rgb = RGBColor(0xC0, 0x00, 0x00)
run.bold = True
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
fmt = p.paragraph_format
fmt.first_line_indent = Cm(1.25)
fmt.line_spacing = 1.5 # множитель, а Pt(18) задаст точное значение
fmt.space_after = Pt(6)Чтобы не повторять одни и те же настройки для каждого фрагмента, удобнее поменять сам стиль. Например, doc.styles['Normal'].font.size = Pt(12) изменит размер шрифта всего основного текста документа сразу.
Свойства, которые есть у Run
Ниже основные атрибуты фрагмента, которые чаще всего нужны при генерации отчетов.
| Атрибут | Что задает | Пример значения |
|---|---|---|
| bold, italic, underline | Жирный, курсив, подчеркивание | True, False или None (наследовать от стиля) |
| font.name | Название шрифта | 'Arial' |
| font.size | Кегль | Pt(11) |
| font.color.rgb | Цвет текста | RGBColor(0, 0x66, 0xCC) |
| font.highlight_color | Цвет выделения маркером | WD_COLOR_INDEX.YELLOW |
| font.strike, font.superscript | Зачеркивание, верхний индекс | True |
Таблицы и изображения
Таблица создается методом add_table(rows, cols). Без указания стиля она получится без рамок, поэтому обычно сразу назначают встроенный стиль Table Grid. Строки удобно добавлять динамически через add_row(), когда количество записей заранее неизвестно.
sales = [('Январь', 120), ('Февраль', 95), ('Март', 143)]
table = doc.add_table(rows=1, cols=2)
table.style = 'Table Grid'
head = table.rows[0].cells
head[0].text, head[1].text = 'Месяц', 'Продажи'
for month, value in sales:
cells = table.add_row().cells
cells[0].text = month
cells[1].text = str(value)Значение ячейки всегда строка, поэтому числа нужно приводить через str() или f-строку. Объединить ячейки можно методом merge(): table.cell(0, 0).merge(table.cell(0, 1)). Если данные лежат в Excel, их удобно сначала прочитать через openpyxl, а затем перенести в таблицу Word.
Картинка вставляется методом add_picture(). Достаточно указать только ширину: высота пересчитается пропорционально, и изображение не исказится.
from docx.shared import Cm
doc.add_picture('chart.png', width=Cm(15))
doc.paragraphs[-1].alignment = WD_ALIGN_PARAGRAPH.CENTERПоля страницы, ориентация и колонтитулы
Параметры страницы хранятся в разделах (doc.sections). В новом документе раздел один. Через него меняются поля, ориентация и содержимое колонтитулов.
from docx.enum.section import WD_ORIENT
section = doc.sections[0]
section.left_margin = Cm(3)
section.right_margin = Cm(1.5)
# альбомная ориентация: размеры листа нужно поменять местами вручную
section.orientation = WD_ORIENT.LANDSCAPE
section.page_width, section.page_height = section.page_height, section.page_width
section.header.paragraphs[0].text = 'ООО "Ромашка". Внутренний документ'
section.footer.paragraphs[0].text = 'Конфиденциально'Частая ошибка - поменять только orientation. Word тогда покажет альбомный значок, но лист останется книжным, потому что ширина и высота не изменились.
Чтение и изменение существующего файла
Для чтения документ открывают по пути и перебирают абзацы и таблицы. Свойство text возвращает текст абзаца или ячейки целиком, а style.name помогает отличить заголовки от обычного текста.
doc = Document('contract.docx')
for p in doc.paragraphs:
if p.style.name.startswith('Heading'):
print('Раздел:', p.text)
for table in doc.tables:
for row in table.rows:
print([cell.text for cell in row.cells])Учтите, что doc.paragraphs не включает текст из таблиц, колонтитулов и надписей: их нужно обходить отдельно. Для массовой обработки папки с документами пригодится модуль glob, который соберет все файлы .docx по маске.
Замена текста без потери оформления
Присваивание paragraph.text = '...' удаляет все фрагменты абзаца и создает один новый, поэтому жирный шрифт и цвет пропадут. Безопаснее менять текст внутри фрагментов.
def replace_in_doc(doc, old, new):
for p in doc.paragraphs:
for run in p.runs:
if old in run.text:
run.text = run.text.replace(old, new)
replace_in_doc(doc, '{client}', 'Иванов И. И.')
doc.save('contract_ivanov.docx')Подвох в том, что Word иногда разбивает метку вроде {client} на несколько фрагментов (например, после проверки орфографии). Тогда функция ее не найдет. Решение - набрать метку в шаблоне заново одним движением или использовать библиотеку docxtpl, которая работает с метками в синтаксисе Jinja2 и сама склеивает такие фрагменты.
Генерация документов по шаблону
Шаблоном для python-docx служит обычный .docx, в котором заранее настроены стили, поля, логотип в колонтитуле и метки для подстановки. Код открывает этот файл, заполняет данными и сохраняет под новым именем, а исходный шаблон остается нетронутым. Копировать содержимое шаблона поэлементно в другой документ не нужно.
clients = ['Иванов И. И.', 'Петров П. П.']
for name in clients:
doc = Document('template.docx') # каждый раз читаем шаблон заново
replace_in_doc(doc, '{client}', name)
doc.save(f'contract_{name.split()[0]}.docx')Открывать шаблон нужно внутри цикла. Если открыть его один раз и сохранять в разные файлы, после первой замены метки уже не останется, и все остальные договоры получат имя первого клиента.
Ограничения библиотеки
python-docx работает только с форматом Office Open XML. Перед использованием стоит знать, чего она не умеет:
- не открывает старые файлы .doc: их сначала конвертируют в .docx, например через LibreOffice в режиме командной строки;
- не сохраняет в PDF, для этого применяют LibreOffice или пакет docx2pdf (ему нужен установленный Word); для работы с готовыми PDF есть PyPDF2;
- не пересчитывает поля и оглавление: Word обновит их при открытии файла;
- не имеет готового метода для вставки гиперссылок и сносок, их добавляют через низкоуровневый XML.
Полный список классов и свойств есть в официальной документации python-docx.
Частые вопросы
Ответы на вопросы, которые чаще всего возникают при первой работе с библиотекой.
Почему import docx выдает ошибку после pip install docx?
Пакет docx и python-docx - разные проекты. Удалите первый командой pip uninstall docx и установите правильный: pip install python-docx. Импорт при этом остается import docx.
Как задать шрифт сразу для всего документа?
Измените стиль Normal: doc.styles['Normal'].font.name = 'Arial' и doc.styles['Normal'].font.size = Pt(12). Все абзацы без собственного форматирования унаследуют эти значения.
Можно ли открыть файл .doc?
Нет, библиотека поддерживает только .docx. Старый формат нужно предварительно сконвертировать, например командой soffice --headless --convert-to docx file.doc.
Как посчитать количество слов в документе?
Соберите текст всех абзацев и разбейте его по пробелам: sum(len(p.text.split()) for p in doc.paragraphs). Для точного результата добавьте текст из ячеек таблиц.