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