Создание и настройка разделов документа в Python для работы со страницами

Раздел: Автоматизация -> Документы Word

Работа со страницами документа Word с использованием python-docx

Библиотека python-docx позволяет программно управлять структурой документа, включая разбивку на страницы, разделы, поля и нумерацию. Основной механизм для настройки страниц - это разделы (sections). Каждый раздел может иметь собственные параметры: ориентацию, размеры, поля, колонтитулы и начальный номер страницы. Разрывы страниц (page breaks) используются для принудительного перехода на новую страницу внутри раздела.

Основное решение: работа с разделами и разрывами

Лучший способ управлять макетом страниц - создавать новые разделы с помощью document.add_section() и настраивать их свойства. Для принудительного разрыва страницы используется document.add_page_break() или добавление разрыва в ран.


from docx import Document
from docx.enum.section import WD_ORIENT
from docx.shared import Cm, Pt

doc = Document()

# Настройка первого раздела (по умолчанию)
section = doc.sections[0]
section.orientation = WD_ORIENT.PORTRAIT
section.page_width = Cm(21)
section.page_height = Cm(29.7)
section.margins.left = Cm(2.5)
section.margins.right = Cm(2.5)
section.margins.top = Cm(2)
section.margins.bottom = Cm(2)

# Добавление содержимого
doc.add_paragraph('Первый раздел (портретный)')

# Принудительный разрыв страницы
doc.add_page_break()
doc.add_paragraph('Эта строка на новой странице, но в том же разделе')

# Создание нового раздела с альбомной ориентацией
new_section = doc.add_section(WD_ORIENT.LANDSCAPE)
new_section.page_width = Cm(29.7)
new_section.page_height = Cm(21)
doc.add_paragraph('Второй раздел (альбомный)')

doc.save('sections_example.docx')

Python docx страница (работа со страницами в docx)

Это решение покрывает большинство задач по управлению страницами: разные ориентации, поля, размеры листов.

Как добавить разрыв страницы в произвольном месте?

Простой способ - вызов document.add_page_break() вставляет разрыв после текущего абзаца. Альтернативно можно использовать run.add_break(docx.enum.text.WD_BREAK.PAGE) для вставки разрыва внутри абзаца.


from docx import Document
from docx.enum.text import WD_BREAK

doc = Document()
p = doc.add_paragraph('Текст до разрыва')
run = p.add_run()
run.add_break(WD_BREAK.PAGE)
p.add_run('Текст после разрыва на новой странице')
doc.save('break_in_run.docx')

Типичная ошибка: забыть установить курсор в нужное место. При вызове add_page_break() разрыв вставляется после текущего текста. Для вставки перед содержимым следует сначала добавить пустой абзац или переместить курсор.

Как создать новый раздел с отличными от предыдущего параметрами?

Используйте document.add_section(start_type), где start_type определяет, с чего начинается раздел (новая страница, новый столбец и т.д.). По умолчанию - WD_SECTION_START.NEW_PAGE.


from docx import Document
from docx.enum.section import WD_ORIENT, WD_SECTION_START
from docx.shared import Cm

doc = Document()
doc.add_paragraph('Первый раздел по умолчанию')

new_section = doc.add_section(WD_SECTION_START.NEW_PAGE)
new_section.orientation = WD_ORIENT.LANDSCAPE
new_section.margins.top = Cm(1)
new_section.margins.bottom = Cm(1)
doc.add_paragraph('Второй раздел с альбомной ориентацией и узкими полями')
doc.save('new_section.docx')

Проблема: если не указать start_type, раздел может начаться не там, где ожидалось. Также важно помнить, что после создания раздела все его параметры сбрасываются к значениям по умолчанию, если их не переопределить.

Как настроить нумерацию страниц?

Python-docx не имеет встроенного метода для номера страницы, но можно вставить поле PAGE в колонтитул через низкоуровневый XML. Рекомендуется использовать библиотеку docx.oxml.


from docx import Document
from docx.oxml.ns import qn

doc = Document()
section = doc.sections[0]
header = section.header
paragraph = header.paragraphs[0]
run = paragraph.add_run()

# Создание элемента fldChar для поля PAGE
fld_char_begin = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'begin'})
run._r.append(fld_char_begin)

instr = run._r.makeelement(qn('w:instrText'), {})
instr.text = ' PAGE '
run._r.append(instr)

fld_char_separate = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'separate'})
run._r.append(fld_char_separate)

fld_char_end = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'end'})
run._r.append(fld_char_end)

# Теперь также можно добавить номер раздела и т.д.
doc.save('page_number.docx')

Возможные ошибки: пропуск пространства имен (w:) или неправильный порядок элементов fldChar. При открытии в Word поле может не обновиться автоматически; нажмите Ctrl+A, затем F9.

Как сделать разные колонтитулы для первого листа или для разных разделов?

Используйте свойство section.different_first_page_header_footer для отдельного верхнего/нижнего колонтитула на первой странице раздела, а также section.header.is_linked_to_previous для отключения связи с предыдущим разделом.


from docx import Document

doc = Document()
section = doc.sections[0]
section.different_first_page_header_footer = True

# Заголовок для первой страницы раздела
first_header = section.first_page_header
first_header.paragraphs[0].text = 'Первый лист'

# Заголовок для остальных страниц
header = section.header
header.paragraphs[0].text = 'Последующие страницы'

# Отключаем связь с предыдущим разделом (если он есть)
section.header.is_linked_to_previous = False

doc.save('headers.docx')

Ошибка: если не сбросить is_linked_to_previous, изменения в колонтитуле раздела будут игнорироваться. Также не забывайте, что колонтитулы хранятся отдельно для каждого раздела.

Расширенные примеры работы со страницами в python-docx

Пример 1. Создание документа с несколькими разделами: разная ориентация и поля

В этом примере создаётся документ из трёх разделов. Первый - портретный с обычными полями, второй - портретный с широкими полями, третий - альбомный с узкими полями. Для каждого раздела добавляется содержимое.

Пример

from docx import Document
from docx.enum.section import WD_ORIENT, WD_SECTION_START
from docx.shared import Cm

doc = Document()

# Раздел 1: портретный, поля 2.5 см
sec1 = doc.sections[0]
sec1.orientation = WD_ORIENT.PORTRAIT
sec1.page_width = Cm(21)
sec1.page_height = Cm(29.7)
sec1.margins.left = Cm(2.5)
sec1.margins.right = Cm(2.5)
sec1.margins.top = Cm(2.5)
sec1.margins.bottom = Cm(2.5)
doc.add_paragraph('Первый раздел (портрет, поля 2.5 см)')

# Добавляем разрыв страницы внутри раздела
doc.add_page_break()
doc.add_paragraph('Ещё один абзац в первом разделе')

# Раздел 2: портретный, но поля 4 см
sec2 = doc.add_section(WD_SECTION_START.NEW_PAGE)
sec2.orientation = WD_ORIENT.PORTRAIT
sec2.page_width = Cm(21)
sec2.page_height = Cm(29.7)
sec2.margins.left = Cm(4)
sec2.margins.right = Cm(4)
sec2.margins.top = Cm(2)
sec2.margins.bottom = Cm(2)
doc.add_paragraph('Второй раздел (портрет, увеличенные боковые поля)')

# Раздел 3: альбомный, узкие поля
sec3 = doc.add_section(WD_SECTION_START.NEW_PAGE)
sec3.orientation = WD_ORIENT.LANDSCAPE
sec3.page_width = Cm(29.7)
sec3.page_height = Cm(21)
sec3.margins.left = Cm(1)
sec3.margins.right = Cm(1)
sec3.margins.top = Cm(1)
sec3.margins.bottom = Cm(1)
doc.add_paragraph('Третий раздел (альбом, поля 1 см)')

doc.save('multi_sections.docx')

Результат: файл будет содержать три раздела с заданными параметрами. При открытии в Word можно увидеть разные ориентации и поля на каждой странице-разделе.

Пример 2. Вставка номеров страниц в нижний колонтитул с разным форматом для разных разделов

Используя XML, вставляем поле PAGE в нижний колонтитул первого раздела, а во втором разделе добавляем ещё и номер раздела.

Пример

from docx import Document
from docx.oxml.ns import qn
from copy import deepcopy

def add_page_number_to_footer(section):
    footer = section.footer
    paragraph = footer.paragraphs[0]
    run = paragraph.add_run()
    
    # Создаём поле PAGE
    fld_char_begin = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'begin'})
    run._r.append(fld_char_begin)
    instr = run._r.makeelement(qn('w:instrText'), {})
    instr.text = ' PAGE '
    run._r.append(instr)
    fld_char_separate = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'separate'})
    run._r.append(fld_char_separate)
    # Можно добавить текст-заполнитель
    run = paragraph.add_run(' ')
    fld_char_end = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'end'})
    run._r.append(fld_char_end)
    
def add_section_number_to_footer(section):
    footer = section.footer
    paragraph = footer.paragraphs[0]
    run = paragraph.add_run()
    # Поле SECTION
    fld_char_begin = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'begin'})
    run._r.append(fld_char_begin)
    instr = run._r.makeelement(qn('w:instrText'), {})
    instr.text = ' SECTION '
    run._r.append(instr)
    fld_char_separate = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'separate'})
    run._r.append(fld_char_separate)
    run = paragraph.add_run(' ')
    fld_char_end = run._r.makeelement(qn('w:fldChar'), {qn('w:fldCharType'): 'end'})
    run._r.append(fld_char_end)

doc = Document()
# Добавляем два раздела
sec1 = doc.sections[0]
add_page_number_to_footer(sec1)
doc.add_paragraph('Первый раздел. Номер страницы внизу.')

sec2 = doc.add_section()
sec2.header.is_linked_to_previous = False  # отключаем связь
add_page_number_to_footer(sec2)
add_section_number_to_footer(sec2)  # добавим ещё и номер раздела
doc.add_paragraph('Второй раздел. Внизу номер страницы и номер раздела.')

doc.save('page_and_section_numbers.docx')

Результат: при открытии документа в Word в нижнем колонтитуле первого раздела отобразится номер страницы, во втором - номер страницы и номер раздела. Поля обновятся при загрузке или по нажатию Ctrl+F9.

Пример 3. Добавление разрыва страницы при создании списка элементов

Предположим, нужно сгенерировать отчёт, где каждый новый элемент начинается с новой страницы. Можно использовать цикл с doc.add_page_break().

Пример

from docx import Document
from docx.shared import Pt

doc = Document()

elements = [
    ('Отчёт 1', 'Текст первого отчёта'),
    ('Отчёт 2', 'Текст второго отчёта'),
    ('Отчёт 3', 'Текст третьего отчёта'),
]

for i, (title, text) in enumerate(elements):
    if i > 0:
        doc.add_page_break()  # новая страница для каждого, кроме первого
    doc.add_heading(title, level=1)
    doc.add_paragraph(text)

doc.save('reports.docx')

Результат: документ с тремя страницами, на каждой - заголовок и абзац текста.

Пример 4. Изменение начального номера страницы в разделе

Иногда требуется начать нумерацию страниц с определённого числа. Для этого используется свойство section.page_numbering_start.

Пример

from docx import Document

doc = Document()

# Первый раздел: номера не показываем (не вставляли поле), но можно задать старт
sec1 = doc.sections[0]
sec1.page_numbering_start = 5  # начать нумерацию с 5

# Второй раздел: сбросить нумерацию
sec2 = doc.add_section()
sec2.page_numbering_start = 1  # начать заново с 1

doc.add_paragraph('Первый раздел (номера с 5)')
doc.add_paragraph('Текст')
doc.add_page_break()
doc.add_paragraph('Второй раздел (номера с 1)')

doc.save('page_start.docx')

Результат: Если вставить поля PAGE в колонтитулы, то в первом разделе страницы будут нумероваться с 5, во втором - с 1.

Работа со страницами в docx - comments

En
Python docx страница (python)