Python: уроки, справочник и задачи

Библиотека python-docx: работа с файлами Word

Раздел: Работа с файлами в Python · Обновлено 31.10.2026

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

Python-docx: библиотека для работы с документами Word в Python
Содержание статьи
  1. Установка и первый документ
  2. Как устроен документ: абзацы и фрагменты
    1. Заголовки, списки и разрывы страниц
  3. Форматирование текста и абзацев
    1. Свойства, которые есть у Run
  4. Таблицы и изображения
  5. Поля страницы, ориентация и колонтитулы
  6. Чтение и изменение существующего файла
    1. Замена текста без потери оформления
    2. Генерация документов по шаблону
  7. Ограничения библиотеки
  8. Частые вопросы
    1. Почему import docx выдает ошибку после pip install docx?
    2. Как задать шрифт сразу для всего документа?
    3. Можно ли открыть файл .doc?
    4. Как посчитать количество слов в документе?

Установка и первый документ

Пакет ставится через 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. Перед использованием стоит знать, чего она не умеет:

Полный список классов и свойств есть в официальной документации 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). Для точного результата добавьте текст из ячеек таблиц.