Как стать автором
Обновить

Автоматизируем печать документов с помощью 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 формы

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.