Pull to refresh

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

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.