суббота, 16 апреля 2016 г.

Python, как автоматизация труда секретарши. Работа с html и простой GUI.

И так, в обычный день был обнаружен человек с простой задачей:
нужно было через каждый "экран текста" в html-файле вставить специальный тег. Задача простая и не особо под автоматизацию, пока не видишь файл примерно на 400к символов (среднестатистическая страница А4 печатного текста ~1800 символов => 200 страниц.

С такими объемами уже руками сражаться сложно. Приступим.

И так, я вооружился python3 и BeautifulSoup (ранее уже писал про него).
Перед тем как работать, стоит обратить внимание на html. В моём случае это оказался текст "Сохраненный как" html из Microsoft Office. Если кто не знает, у офиса это получается весьма своеобразным путём. Так как уже существует готовое решение, я не стал замарачиваться и писать свой велосипед. Очень рекомендую программу Doc2html. Для сравнения: исходный файл полученный мной весил 1,3 МБ - после конвертирования вес составил 698 КБ. Ощутимо.
Так же, обнаружим, что html из офиса в кодировке CP1251, а после конвертера UTF8.

Сам скрипт

Точность не требовалась по ТЗ. Логика скрипта простая:
Делаем итерацию по всем параграфам (<p></p>)  и измеряем длину каждого в символах. В том же цикле, мы прочли больше символов, чем заданное Х - вставляем наш тег.

def pagin(htmlin,htmlout,psize,pstart,charset):
    from bs4 import BeautifulSoup
    itercounter = 0
    page = pstart
    with open(htmlin, 'r',encoding=charset) as file: #
        bs = BeautifulSoup(file.read(), 'html.parser')
        pps =  bs.findAll('p')
    
        for p in pps: #reversed
            itercounter += len(p.text)
            sum += len(p.text)
            if itercounter >= psize:

                sp = str(page)
                tag = bs.new_tag('hr', **{'class':'system','title':'Страница %s' % (sp) })
                p.insert(0,tag)
                itercounter = 0
                page +=1

        with open(htmlout,'w') as fo:
            fo.write(str(bs.html))

 

Конечно, скрипт получился не идеальным - с не маленькой погрешностью, но нас устраивает.
Вставляемый тег: 
<hr class="system" title="страница n" />
Он может быть любой. Из кода вполне понятно, как сделать аналогичный объект.

GUI для скрипта

Скрипт изначально рассчитывался на "блондинку". Ранее, в скриптах для себя я использовал модуль argparse и управление скриптом было из командной строки. Но как только я начал описывать параметры - я понял что для "блондинки" и тем более из под Windows будет сложно его использовать. А если автоматизацию сложнее использовать, чем исходный процесс - она не работает.
Я задумался об GUI. В GUI был выбран TK так как он интегрирован в python что бы завести скрипт на другой машине - нужно меньше телодвижений (об этом позже).
Это мой второй интерфейс. Первый я реализовал на PyQT4 и был не доволен количеством телодвижений для простого интерфейса. В этом плане TK оказался легким и простым.
Да, сразу скажу, что я не стал обрабатывать всевозможные исключения и интерфейс не идеален. Но по трудочасам - минимален, что и требовалось.
Для написания интерфейса мне хватило трёх страничек в интернете и времени на него ушло даже меньше, чем на основную функцию.

from tkinter import *
from tkinter.filedialog import *

def pagin(htmlin,htmlout,psize,pstart,charset):
    ...

def button_clicked():
    htmlin = askopenfilename()
    htmlout = asksaveasfilename()
    if listboxKind.curselection()[0] == 0:
        charset = 'utf8'
    elif listboxKind.curselection()[0] == 1:
        charset = 'cp1251'
    psize = SizeEnt.get()
    pstart = StartEnt.get()
    pagin(htmlin,htmlout,int(psize),int(pstart),charset)
    print(htmlin,htmlout,psize,pstart,charset)
root=Tk()

listboxKind=Listbox(root,height=2,width=15,selectmode=SINGLE)
listKind = ['utf8 (doc2html)','cp1251 (MSOffice)']
for i in listKind:
    listboxKind.insert(END,i)



Label(root,text="Тип html").pack()
listboxKind.pack()
Label(root,text="Период в символах").pack()
SizeEnt= Entry(root,width=10)
SizeEnt.pack()
Label(root,text="Начало отсчета").pack()
StartEnt = Entry(root,width=10)
StartEnt.pack()
button1 = Button(root,text='Открыть файл',command=button_clicked)
button1.pack()
root.mainloop()

Python+TK
Что было примечательно для меня, что в TK есть готовые файловые askopenfilename, asksaveasfilename. Что очень приятно упрощает жизнь.
диалоги:
Просто, но работает. Теперь точно не запутаться в параметрах и аргументах командной строки.

Переносимость скрипта

Написал - молодец, но надо ещё отдать этот скрипт "блондинке".
Существует, на мой взгяд, два пути:
  1. Отдать скрипт (1,8 КБ)
    А в месте с ним иструкцию, как установить python3, beautifulSoup научить этим пользоваться. Благо TK идет в комплекте, и не надо дополнительно ничего качать, собственно по этому он и был выбран.
    Но, оставим этот вариант на черный день.
  2. Скомпилировать скрипт в полноценный exe (27 МБ)
    Тогда есть чётко exe файл который надо запускать, и минимум объяснений, что радует. Что не радует, так это размер. Ну что уж, 21 век, "8мб хватит всем" =)
Существуют различные пути компиляции Python  в exe, и один из них py2exe. Проект документирован  и есть простая статья на Хабре как в это ввязаться. Установить можно из pip репозитория.
Данная приблуда, вместе со скриптом кладёт сам интерпретатор Python и все библиотеки, которые используются в скрипте. py2exe достаточно самостоятелен, но иногда требуется указать ему что надо собрать с собой, к примеру дополнительные библиотеки или статические файлы.
Для сборки необходимо положить рядом со скриптом специальный файл setup.py. В моём случае, всё собралось с первого раза:

#!/usr/bin/python3
from distutils.core import setup
import py2exe
     
setup(
        windows=[{"script":"paginator_03.py"}],
        options={"py2exe": {"includes":[]}}
)


И выполняем команду уже под windows :
python3 setup.py py2exe

Готово. 27 метров скрипта на 2 кб.


Открытые вкладки


Комментариев нет:

Отправить комментарий