Автоматизируем печать документов с помощью Python

от автора

Печать Word и PDF, используя двухсторонний принтер и автоматическая систематизация страниц в PDF, c помощью Python

Обзор

1) Начало
2) Задача
3) Решение
4) Вывод

Начало

Меня зовут, Матвеев Дмитрий, и я работаю в Синергии.

Каждый день, я готовлю однообразные документы, в которых нужно печатать страницы — одинаково (однообразно):
1 (ую) и 2 (ую) страницы, двойной печатью по длинному краю;
3 (ью) и 4 (ую) по короткому краю (эти листы горизонтальные);
5 (ую) страницу отдельно (только 1 лист).

Каждый день, из раза в раз, нужно было настраивать диапазон для печатати. И в один момент (спустя 3 дня) мне это надоело и я решил написать программу, с помощью которой можно будет распечатать этот документ — одним нажатием мыши.

Спойлер — мне удалось. Но пришлось поискать информацию, а информации на русском не очень много, поэтому искал преимущественно в английских источниках.

Задача

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

Решение

Из Word в PDF

Для начала преобразуем Word документы в отдельные PDF файлы, для этого устанавливаем следящую библиотеку:

pip install docx2pdf

И пишем код для конвертации. Для этого собираем все «.docx» файлы в папке, преобразуем их в PDF и нам требуется условие на проверку файла.

Если «.docx» есть в названии файла, то конвертируем, иначе пропускаем файлы. Нужно для того, чтобы сами pdf файлы не конвертировались в pdf (вылезет ошибка).

from docx2pdf import convert import os  def word_convert(arr):   out = []   for file in arr:       if '.docx' in file:          out.append(file)    return out  indir = '\\input' #ваша директория, где находятся .docx файлы all_files = next(os.walk(indir))[2] # все файлы в вашей директории в массиве input_files = word_convert(all_files) # входные .dock файлы  #проходимся циклом по директории и конвертируем for file in input_files:   path_file = indir + file # путь до файла   convert(path_file)

Систематизация страниц

Далее устанавливаем библиотеку для редактирования страниц в pdf:

pip install pikepdf

Пишем функцию для чтения и систематизации документа на под файлы т.к используя эту библиотеку, мы будет удалять из нашего файла страницы, то пишем функцию для более удобного удаления:

def del_pages(pdf, arr, num_pages=16):   max = arr[1]   min = arr[0] - 1   del pdf.pages[max:num_pages]   del pdf.pages[0:min]  return pdf # -1 для того, чтобы вы писали с 1 страницы, а не с 0.   #В начале это не мешает, но если страниц 20, то в конце можно запутаться. 

Сохранение новых файлов

# Теперь пишем функцию для чтения и систематизации:  outdir = '\\output'  def doc_crushing(doc_path, arr, outpdf):   with pikepdf.open(doc_path) as pdf:     # Смотрим сколько страниц в pdf     num_pages = len(pdf.pages)     # Удаляем страницы из pdf     del_pages(pdf, arr, num_pages)     pdf.save(outdir + outpdf)  #Пример doc = 'Договор.pdf' # Название вашего файла (тут для примера без цикла) doc_path = indir + doc # Путь до файла  #doc_crushing(входной файл, масив с нужными срезом страниц, новое название) doc_crushing(doc_path, [1,2], 'Титульный лист.pdf') doc_crushing(doc_path, [3,4], 'Экзаменационный лист с фото.pdf') doc_crushing(doc_path, [5,6], 'Заявление на поступление.pdf') 

Т.е из документа «doc_path», мы оставляем промежуток страниц с 1 по 2 и сохраняем с именем ‘Титульный лист.pdf’ и так для каждого файла

Печать документов

Для работы с принтерами установим следующую библиотеку

pip install pywin32

Далее пишем функцию для печати т.к мы ее будем вызывать для каждого нужного файла

import win32print import win32api #print_pdf (входной pdf, режим печати, какой принтер) #режим печати: 1 - односторонняя, 2 двойная по длинному краю, 3 - по короткому def print_pdf(input_pdf, mode=2, printer=1):   # тут мои принтеры, для своихузнаем имя дефолтного принтера через метод win32print.GetDefaultPrinter()   if int(printer) == 2:     name = "\\\\buh\\BUH DCP-L5500DN series (копия 1)" #win32print.GetDefaultPrinter()   elif int(printer) == 1:     name = "Brother DCP-L2540DN series Printer" #win32print.GetDefaultPrinter()   try:     # Устанавливаем дефолтный принтер     win32print.SetDefaultPrinterW(name)     win32print.SetDefaultPrinter(name)   finally:     # Если не получилось или получилось -> устанавливаем этот принтер стандартом     name = win32print.GetDefaultPrinter()  # оставляем без изменений   ## тут нужные права на использование принтеров   printdefaults = {"DesiredAccess": win32print.PRINTER_ALL_ACCESS}   ## начинаем работу с принтером ("открываем" его)   handle = win32print.OpenPrinter(name, printdefaults)   ## Если изменить level на другое число, то не сработает   level = 2   ## Получаем значения принтера   attributes = win32print.GetPrinter(handle, level)   ## Настройка двухсторонней печати   attributes['pDevMode'].Duplex = mode   #flip over  3 - это короткий 2 - это длинный край    ## Передаем нужные значения в принтер   win32print.SetPrinter(handle, level, attributes, 0)   win32print.GetPrinter(handle, level)['pDevMode'].Duplex   ## Предупреждаем принтер о старте печати   win32print.StartDocPrinter(handle, 1, [input_pdf, None, "raw"])   ## 2 в начале для открытия pdf и его сворачивания, для открытия без сворачивания поменяйте на 1   win32api.ShellExecute(2,'print', input_pdf,'.','/manualstoprint',0)   ## "Закрываем" принтер   win32print.ClosePrinter(handle)    ## Меняем стандартный принтер на часто используемый     win32print.SetDefaultPrinterW("Brother DCP-L2540DN series Printer")   win32print.SetDefaultPrinter("Brother DCP-L2540DN series Printer")  # Пример  inputs_print = next(os.walk(outdir))[2] # Берем все файлы в папке outdir # Печатаем документы for input_print in inputs_print: # Путь до файла, который нужно расспечатать   path_print = outdir + input_print   # Если в названии файла есть 'Экзаменационный', то печатаем по короткому краю     if 'Экзаменационный' in input_print:       print_pdf(path_print, 3, num)     else:       print_pdf(path_print, 2, num)
Полный код c переделкой на работу с php сервером:
from docx2pdf import convert import win32print import win32api  import pikepdf import shutil  import time import sys import os  #Мой код запускается сразу с определными значениями if sys.argv[7]:   lastname = sys.argv[1]   name = sys.argv[2]   fname = sys.argv[3]   name1 = sys.argv[4]   today = sys.argv[5]   num = sys.argv[6]   level_stydy = sys.argv[7] else:   lastname = sys.argv[1]   name = sys.argv[2]   fname = sys.argv[3]   today = sys.argv[4]   num = sys.argv[5]   level_stydy = sys.argv[6]  #Если значения передавались, то изменяем директорию if lastname: dir = os.path.abspath(os.curdir) + "\\py\\" + f"{lastname} {name} {fname}" else:   dir = os.path.abspath(os.curdir) indir = dir + "\\input\\" outdir =  dir + "\\output\\" #Получаем все файлы в дикетории -> выдаем docx файлы def tackword(arr):   out = []   for file in arr:       if '.docx' in file:          out.append(file)  return out  #Функция для копирования сгенерированых файлов в мою директорию def copy_in_input(indir, name1): dir = os.path.abspath(os.curdir) + '\\iles\\' input_files = tackword(next(os.walk('.\\files'))[2]) for file in input_files:     if name1 in file and today in file:         src = dir + file         ind = indir + file         shutil.copyfile(src, ind)  def del_pages(pdf, arr, num_pages=16):   max = arr[1]   min = arr[0] - 1   del pdf.pages[max:num_pages]   del pdf.pages[0:min]  return pdf  def doc_crushing(doc_path, arr, outpdf):   with pikepdf.open(doc_path) as pdf:     num_pages = len(pdf.pages)     #Удаляем страницы из pdf     del_pages(pdf, arr, num_pages)      pdf.save(outdir+outpdf)  def print_pdf(input_pdf, mode=2, printer=1):   if int(printer) == 2:     name = "\\buh\BUH DCP-L5500DN series (копия 1)" #win32print.GetDefaultPrinter()   elif int(printer) == 1:     name = "Brother DCP-L2540DN series Printer" #win32print.GetDefaultPrinter()   win32print.SetDefaultPrinterW(name)   win32print.SetDefaultPrinter(name)    name = win32print.GetDefaultPrinter()   print(name)   print("<bk>")   drivers = win32print.EnumPrinterDrivers(None, None, 2)   for drive in drivers:       print(drive['Name'])    printdefaults = {"DesiredAccess": win32print.PRINTER_ALL_ACCESS}    handle = win32print.OpenPrinter(name, printdefaults)   level = 2   attributes = win32print.GetPrinter(handle, level)    attributes['pDevMode'].Duplex = mode   #flip over  3 - это короткий 2 - это длинный край    win32print.SetPrinter(handle, level, attributes, 0)   win32print.GetPrinter(handle, level)['pDevMode'].Duplex    r = win32print.StartDocPrinter(handle, 1, [input_pdf, None, "raw"])   print(r)   win32api.ShellExecute(2,'print', input_pdf,'.','/manualstoprint',0)    # win32print.ClosePrinter(handle)    # win32print.SetDefaultPrinterW("Brother DCP-L2540DN series Printer")   # win32print.SetDefaultPrinter("Brother DCP-L2540DN series Printer")  def main():   # проверяем есть ли директория, если нету, то делаем   if not os.path.exists(dir):     os.mkdir(dir)     os.mkdir(dir + '\\input')     os.mkdir(dir + '\\output')   # копируем из файлы имеющие имя в файле   copy_in_input(indir, name1)   # берем docx скопированные файлы   input_files = tackword(next(os.walk(indir))[2])   for file in input_files:       convert(indir+file)    input_pds = next(os.walk(indir))[2]    doc = ''   rec = ''   outs = []      # Если я хочу изменять pdf файл, то записываю его в переменную   for file in input_pds:       if '.pdf' in file:           if 'Договор' in file:               doc = file           elif 'Реквизиты' in file:               rec = file           else:               outs.append(file)    # Открываем договор pdf, если он есть   if doc != '':       doc_path = indir + doc        # 1 - БАК; 2 - МАГ; 3 - СПО       if level_stydy == 1:            doc_crushing(doc_path, [1,2], 'Титульный лист.pdf')           doc_crushing(doc_path, [3,4], 'Экзаменационный лист с фото.pdf')           doc_crushing(doc_path, [5,6], 'Заявление на поступление.pdf')           doc_crushing(doc_path, [7,7], 'Согласие на зачисление.pdf')           doc_crushing(doc_path, [8,9], 'Согласие на обработку персональных данных.pdf')           doc_crushing(doc_path, [10,16], 'Договор.pdf')        elif level_stydy == 2:           doc_crushing(doc_path, [1,2], 'Титульный лист.pdf')           doc_crushing(doc_path, [3,4], 'Экзаменационный лист с фото.pdf')           doc_crushing(doc_path, [5,6], 'Заявление на поступление.pdf')           doc_crushing(doc_path, [7,7], 'Согласие на зачисление.pdf')           doc_crushing(doc_path, [8,9], 'Согласие на обработку персональных данных.pdf')           doc_crushing(doc_path, [10,14], 'Договор.pdf')        elif level_stydy == 3:           doc_crushing(doc_path, [1,2], 'Титульный лист.pdf')           doc_crushing(doc_path, [3,4], 'Заявление на поступление.pdf')           doc_crushing(doc_path, [5,5], 'Согласие на зачисление.pdf')           doc_crushing(doc_path, [6,7], 'Согласие на обработку персональных данных.pdf')           doc_crushing(doc_path, [8,12], 'Договор.pdf')    if rec != '':       rec_path = indir + rec        with pikepdf.open(rec_path) as pdf:           num_pages = len(pdf.pages)           if num_pages != 1:               del_pages(pdf, [1,1], num_pages)            pdf.save(outdir+'Реквизиты на оплату.pdf')    if outs:       for out in outs:           with pikepdf.open(indir+out) as pdf:               pdf.save(outdir+out)    files_to_print = []   inputs_print = next(os.walk(outdir))[2]    #Печатаем документы   for input_print in inputs_print:        path_print = outdir + input_print        if 'Экзаменационный' in input_print:           print_pdf(path_print, 3, num)       else:           print_pdf(path_print, 2, num)    # #Удаляем файлы   # time.sleep(100)    # for file in os.scandir(indir):    #     if file.name.endswith(".pdf") or file.name.endswith(".docx"):   #         os.unlink(file.path)    # for file in os.scandir(outdir):    #     if file.name.endswith(".pdf") or file.name.endswith(".docx"):   #         os.unlink(file.path)  if name == 'main': main()

Вывод

Сразу про минусы:
принтеры не всегда меняются (т.е печатает, только, 1 принтер), разбираюсь почему.

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

Прощу, раскритиковать мой говнокод и поделится своим мнением о автоматизации печати (как вы это делали) или была ли моя статья полезно для вас

Это моя первая статья, поэтому давайте пожестче

P.S Документы генерирую на php форме с php сервера, на котором запускаю Python скрипт, с помощью php формы


ссылка на оригинал статьи https://habr.com/ru/articles/935200/