Всем привет.
В статье приведу немного скриптов на Python для решения вопросов с которыми иногда пересекается инженер ПТО строительной организации. Склёпал сам по мотивам информации из инета. Профи будет скучно), уровень "без диплома программиста".

На рабочем ноуте используется: Win10, Python 3.11, Office2019. Програмлю в PyScripter (нравится мне он))

Пример 1. Надо архивировать (не в смысле запаковать)

Проводятся испытания отдельных участков трубопровода, штук 50-60. На каждое заводится папка. Так как ни заказчик, ни технадзор, ни еще кто-нибудь не могут определиться, как лучше оформить это дело, то возникает ситуация когда куча документов имеет 2-3-4 варианта оформления. Значит надо скрипт, который текущий вариант закинет в архивную папку. (Ну да, "мой комьютер" или "тотал коммандер" никак с этим не справляются)):

##------------ЗАДАЧА---------------------------
## Создать папку Архив, в ней создать подпапку
## Переместить все файлы ворд/Эксель в подпапку архива

import glob, os, shutil

source_dir = os.getcwd() #Path where your files are at the moment
dst = source_dir+'/Архив/Вар2' #Path you want to move your files to

if not os.path.isdir(dst):           #создаем архив
    os.makedirs(dst)

#либо выбираем типы файлов - типа оффис
format_move_files = ['*.xls*', '*.doc*']
#либо все.
##format_move_files = ['*.*']

for fmf in format_move_files:
    files = glob.iglob(os.path.join(source_dir, fmf))
    for file in files:
        if os.path.isfile(file):
            shutil.move(file, dst)

Пример 2. Создаем папки

Собралось как-то много файлов в одной папке. И тут супостаты опять пристали со своими хотелками - каждый файл в свою папку. Нууу, лааадно. Лучше день потерять....потом за минуту само сделается. Идея такова, тем же тоталом получаю список файлов, его сохраняю в файле "папки.txt". Вот теперь его скрипт должен открыть и наштамповать папок:

import os

codirovk = 'utf-8'

#------------вар2 работает------------------
with open('папки.txt', "r", encoding=codirovk) as file1:
    # итерация по строкам
    for line in file1:
        print(line.strip())
        if not os.path.isdir(line.strip()): ##делаем папки если их нет
            os.makedirs(line.strip())


#------------вар1 работает------------------
##file1 = open('папки.txt', "r")
##
### считываем все строки
##lines = file1.readlines()
##
### итерация по строкам
##for line in lines:
####    print(line.strip())
##    if not os.path.isdir(line.strip()): делаем папки если их нет
##        os.makedirs(line.strip())
##
### закрываем файл
##file1.close

Пример 3. Достаем файлы

И вот присылают как-то папку со сметами, а они каждый файл в отдельной папке, да еще вперемешку с ведомостями всякими, да еще названия файлов пляшут, ну и их 392шт. Шо делать, шо делать - расчехляем питон. Надо достать все файлы смет с попутным переименованием их в одну папку:

# задача - достать из множества папок все файлы в одну папку (где скрипт)
# допзадача - переименовать добавляя имена подпапок

import os, shutil
import glob
import re

dst = os.getcwd()
print(dst)

# допзадача переименовать добавляя имена подпапок
for name in sorted(glob.glob(dst+'/**/*локал*.xls', recursive=True)):
# имеем D:\1 На почту\КС-6\Глава 1\01-02-01Р-01\Л0326174_Локальная смета.xls
# вырезаем эту часть D:\1 На почту\КС-6\   и заменяем косые черты на тире
    newname = name[len(dst)+1:]
    newname = re.sub(r'\\', '-', newname)
    os.rename(name, newname)

# пройтись по всем папкам каталога запуска скрипта и переместить
# файлы *локал*.xls в папку скрипта
for name in sorted(glob.glob(dst+'/**/*локал*.xls', recursive=True)):
    try:
        shutil.move(name, dst)
    except:
        pass

Пример 4. Из ворда в пдф)

Есть пару десятков файлов ворда в формате docx, надо из них pdf. Ну конечно, сча я буду сидеть каждый ручками открывать и делать сохранить как. Хотя есть еще и пдф-принтеры.....Так, ладно, питон погнали:

##*******************работает только docx

#pip install docx2pdf

from docx2pdf import convert  
import os

convert(os.getcwd()) # конвертация текущего каталога

Пример 5 с подпримером 6. Работаем с Excel. Замена текста во множестве файлов.

Мы строили, строили и наконец - построили. Но....да твою ж дивизию: ну почему когда 85% исполнительной документации уже сделано и подписано (а это примерно 1200-1500 актов скрытых работ, сделанных в эксель) вдруг какой-то заразе приходит в голову поменять подписанта из-за игр генподрядчик-субподрядчик-субсубподрядчик. И, шо, эти люди думают, радостно потирая ручки, шо я буду мучиться. Ну держитесь - где там мой удав, пора придушить воооон того....А ладно, смотрим шо есть в интернете для питона. А там много чего есть, те что приглянулись вот:

  • openpyxl - работает только с xlsx Для его использования возникла подзадача сконвертировать из xls в xlsx. Смотрим вопрос в инете. Находим pyexcel, xls2xlsx - но рушат форматирование. Находим pywin32. Делаем:

# Задача: конвертация эксель из xls в xlsx
# Все остальные библиотеки работают вкось
# Эксель должен быть установлен на компе!!!

# pip bloks
##     python -m pip install --upgrade pywin32

# import bloks
import os

import win32com.client as win32

# config bloks
path =os.getcwd()
format_files = ('.xls')

pred_prefiks_file_name = ''

# relise bloks
for root, dirs, files in os.walk(path):
    for file in files:
        if(file.endswith(format_files, 0, len(file))):
##            print(os.path.join(file))## - этот вариант выводит только имя файла
            print(file)
##        print(os.path.join(root, file))## - этот вариант выводит полный путь и имя файла
            # -------делаем через COM объект -----------------------------
            excel = win32.gencache.EnsureDispatch('Excel.Application')
            wb = excel.Workbooks.Open(os.path.join(root, file))
            wb.SaveAs(pred_prefiks_file_name+os.path.join(root, file)+'x', FileFormat = 51)    #FileFormat = 51 is for .xlsx extension
            wb.Close()                               #FileFormat = 56 is for .xls extension
            excel.Application.Quit()

input("Работа завершена. Тисни ентер.")

Далее обрабатываем openpyxl, но он тоже нарушает форматирование. Ищем дальше и находим:

  • aspose-cells -добавляет лист 'Evaluation Warning' и может еще какое ограничение содержит, так как он платный.

Практически он работает как надо. Делаю через него скрипт, а лишний лист удаляю с помощью pywin32.

## худо бедно работает как надо мне, но компонент платный

##pip install aspose-cells
##pip install aspose-cells-python
##python -m pip install --upgrade pywin32

import os
from aspose.cells import Workbook, ReplaceOptions
import win32com.client as win32

path =os.getcwd()
format_files = ('.xlsx')##'.pdf','.doc', '.docx', '.dwg', '.py' ## - закомментировать если нужны все файлы

find_str = 'Руководитель проекта ООО "Капуста" '
removent_str = 'Заместитель генерального директора по объектам ООО "Рога и копыта"'

# ВНИМАНИЕ - замена осуществляет по полным данным в ячейке. Т.е. меняется ячейка на ячейку

pred_prefiks_file_name = ''

for root, dirs, files in os.walk(path):
    for file in files:
        if(file.endswith(format_files, 0, len(file))):## - закомментировать если нужны все файлы
            print(file)
            # Load Excel file
            workbook = Workbook(file)   ##Workbook("Workbook.xlsx")
            # Create replace options
            replace = ReplaceOptions()
            # Set case sensitivity and text matching options - раздел выдает ошибку
##            replace.setCaseSensitive(False)
##            replace.setMatchEntireCellContents(False)
            # Replace text
            workbook.replace(find_str, removent_str, replace)
            # Save as Excel XLSX file
            workbook.save(pred_prefiks_file_name+file);

## этот блок удаляет последний добавляемый лист 'Evaluation Warning' - по факту просто последний
            excel = win32.gencache.EnsureDispatch('Excel.Application')
            wb = excel.Workbooks.Open(os.path.join(root, pred_prefiks_file_name+file))
            ws = wb.ActiveSheet
            try:
                excel.DisplayAlerts=False
                wb.Worksheets(wb.Sheets.Count).Delete()
            finally:
                pass
            wb.Save()
            wb.Close()
            excel.Application.Quit()

input("Работа завершена. Тисни ентер.")

Да шо ж я упускаю что-то. Как то все топорно. Стоп, эту работу лучше экселя никто не сделает, а это значит вот же решение - pywin32. И конвертировать ничего не надо было. Ищем материалы в инете, просматриваем, пробуем раз 50 с попутным улучшением, готово:

##python -m pip install --upgrade pywin32

import os
import win32com.client as win32
import re

path = os.getcwd()
format_files = ('.xlsx', '.xls')##'.pdf','.doc', '.docx', '.dwg', '.py' ## - закомментировать если что-то не надо

# ищем строку
find_str = 'Сидоров Ю.М.'
# строка для замены найденной строки
removent_str = 'Сидоров Ю.А.'

# делаем 4 замены по порядку, а можно было и через список сделать

#Сидоров Ю.М.                                     Сидоров Ю.А.
#Руководитель проекта ООО "Капуста"               Заместитель генерального директора по объектам ООО "Рога и копыта"
#Сидоров Ю.М. Приказ №15 29.03.2023               Сидоров Ю.А. Приказ № П100  от  31.09.2022
#Иванов А.А.                                      Сидоров Ю.А.

#количество строк в которых надо делать поиск
row_find = 125
#количество столбцов в которых надо делать поиск
col_find = 40

#если надо спереди имени файла добавить префикс
pred_prefiks_file_name = ''

excel = win32.gencache.EnsureDispatch('Excel.Application')

for root, dirs, files in os.walk(path):
    for file in files:
        if ((file.endswith(format_files, 0, len(file))) and (not file.endswith('~$', 0, 2))):
            # not file.endswith('~$', 0, 2) - защита от временных файлов эксель которые имена начинаются на ~$
            print('файл: ',file)
            #print(os.path.join(file))## - этот вариант выводит только имя файла
            #print(os.path.join(root, file))## - этот вариант выводит полный путь и имя файла
##            excel = win32.gencache.EnsureDispatch('Excel.Application')
            wb = excel.Workbooks.Open(os.path.join(root, file))
            print('листов: ', wb.Worksheets.Count)
            for sh_i in range(1, wb.Worksheets.Count+1):  #перебираем листы книги
                ws = wb.Worksheets(sh_i)    # идем на лист и активируем его
                ws.Activate()
                print('лист: ', sh_i)
                fp = 0
                cii2 = 0
                for row_i in range(1, row_find): #ws.Columns.Count - это вообще все строки
                    for col_i in range(1, col_find): #ws.Rows.Count - это вообще все колонки
                        if ws.Cells(row_i,col_i).Value:
                            cii2 = cii2 + 1
                            #вариант с re - заменяет внутри
                            data_from_cell = str(ws.Cells(row_i,col_i).Value) #получаем строку из данных
                            result = re.findall(find_str, data_from_cell)
                            if len(result) > 0:
                                print('в ячейке: ',row_i,',',col_i)
                                print('данные в ячейке: '+data_from_cell)
                                fp = 1
                                data_from_cell2 = re.sub(find_str, removent_str, data_from_cell, count=0)
                                print('измененные данные: '+data_from_cell2)
                                ws.Cells(row_i,col_i).Value = data_from_cell2
                                print('замена выполнена')

                            #вариант через данные эксель - совпадение по ячейке полностью - плохой вариант
    ##                        if ws.Cells(row_i,col_i).Value == find_str:
    ##                            print('найдено на листе: ', sh_i)
    ##                            print('в ячейке: ',row_i,', ',col_i)
    ##                            fp = 1
    ##                            ws.Cells(109,1).Value = removent_str
    ##                            print('замена выполнена')

                if fp == 0:
                    print('не найдено на листе: ', sh_i)
                print('пройдено ячеек: ', cii2)
            wb.Save(os.path.join(root, pred_prefiks_file_name+file))
            wb.Close()
##            excel.Application.Quit()

excel.Application.Quit()

print('')
print('----------------------------------------------')
input("Работа завершена. Тисни ентер.")

В общем как поиск/замену сделать через pywin32 не нашел. Перебираю заданную область по ячейково. Сначала всю ячейку заменял - но это отдельный текст внутри ячейки не заменишь, а только целиком. Потом через модуль re уже стало работать, как я и хотел. Работает медленнее, чем aspose, но мне как-то спокойнее)).

P.S.: Так и живём в ПТО :-)

Комментарии (16)


  1. omaxx
    16.09.2023 16:14
    +4

    Используйте pathlib, и ваши примеры станут гораздо компактнее


  1. EShin
    16.09.2023 16:14
    +1

    Тоже приходилось заниматься документацией для стройки. Делал для себя генератор актов. Был шаблон акта с плейсхолдерами, файл xls со списком выполняемых работ, датами, материалами. Скрипт пробегался по строках таблички и генерировал кучу актов.


  1. FruTb
    16.09.2023 16:14
    +1

    На винде есть павершелл. Вроде стандартные bash команды оно уже тоже умеет. Это про первые скрипты.

    А про поиск и замену - стоить помнить что xlsx/docx это тупо zip архивы с пачкой xml и бинарных картинок внутри.


    1. danjahjah
      16.09.2023 16:14
      +3

      Баш и павершелл это, конечно, круто, но, имхо, питон проще, удобнее и универсальнее для подобных задачек. Особенно для человека чья проф деятельность не связана напрямую с программированием.


    1. NAI
      16.09.2023 16:14
      +1

      Ага, есть тут у меня REST-клиент написанный на PS и вроде бы все хорошо в 90% случаев, НО раз в 10-20 загрузок шелл виснет и пока не нажмёшь Enter ничего не происходит -> сессия отваливается -> загрузка обрывается -> теряется ~400р.

      Ну и на линукс это добро перевозить сомнительное удовольствие. В общем для MVP ок, для чего-то серьезного все равно надо переписывать на python


  1. andrewrubanov
    16.09.2023 16:14

    Тоже работаю в ПТО. Автор конечно молодец, но для тех, кто не работал с PyScripter, мог бы накидать вкратце руководство что куда там нажимать.

    По поводу автоматизации и шаблонизации актов лучше использовать "слияние" в word+excel. В скрипты не каждый сможет, а стандартные кнопочки в ворде понятны любой домохозяйке.


    1. Robastik
      16.09.2023 16:14

      Нативное слияние не очень заходит домохозяйкам. Есть варианты более востребованные.


      1. andrewrubanov
        16.09.2023 16:14

        Посмотрел описание, имеет место быть. Но для своей работы в ПТО я не увидел какого-либо преимущества перед стандартным слиянием. Зачем использовать стороннюю надстройку, когда встроенный функционал справляется с этой же задачей.


        1. Robastik
          16.09.2023 16:14

          Даже не знаю чем не устраивает встроенный функционал, но народ активно ищет что-то.


    1. Kraleks Автор
      16.09.2023 16:14
      +2

      Ну PyScripter интуитивно понятен, схож с остальными IDE. Я пробовал и пайчарм, и VSC, и другие. Пайчарм конечно монстр, но уж через чур, не пришло еще время для него). VSC тож понравился и одно время 2-мя пользовался. Но вот мне, как практически новичку, PyScripter полностью устроил. Наворотами я не пользуюсь в виде точек останова, трассеров, да даже отслеживания переменных. Потому моё руководство будет "никаким". Там просто можно попробовать, не понравилось - удалил и забыл).

      Таким слиянием не пользовался. А ворд терпеть не могу - постоянно форматирование уплывает по неведомым законам. Да и в экселе сумму столбца узнать, что-то посчитать мгновенно можно, а ворд бестолковый. Я его б вообще запретил. Ну вот вы скажете, а как же письма писать - дык я вам отвечу: письма в экселе просто замечательные выходят. Под рукой практически все есть в непечатаемой зоне: все объекты, все договора, все адресаты и обращения к ним, все исполнители и даже шапки всех филиалов если надо. Болванка письма делается мгновенно, без необходимости как в ворде искать предыдущее письмо для заготовки. Одно только плохо - все остальные делопроизводители начинают биться в истерике и вешаться.


      1. andrewrubanov
        16.09.2023 16:14

        Эко вы сударь извращаяетесь! Форматирование уплывает у вас.... Я конечно встречал таких упоротых, кто готов руками землю копать, но для этого уже придумана лопата, и она называется ворд. Делопроизводители не просто так в истерике бьются, потому что таких, кто в экселе составляет текстовые документы, надо лишать премий с дальнейшим увольнением в случае непонимания.


  1. coregabe
    16.09.2023 16:14

    Добрый чел спасибо, я так парился с PDF да и с Excel ещё я делал с Google Cheat? всё собственно сделал но так вымотался


  1. coregabe
    16.09.2023 16:14
    +1

    Могу про Google описать, import моментальный в таблицу


  1. vserge
    16.09.2023 16:14

    Большое спасибо за статью.

    Хотел бы уточнить почему для управления всем техническим документооборотом не используете стандартные решения типа ASCON pilot-ice или CSoft CADLIB, да есть еще целый список других решений?


    1. Kraleks Автор
      16.09.2023 16:14

      Ну тут так скажу - работа не постоянная в плане места и одного объекта. Начали объект, построили, сдали, поехали на другой. К тому же не все время работаешь, ездишь на перевахтовки, а то и на другие объекты. Дальше - не все ПТО-шники могут/знают/умеют или хотят научится. Как стандарта по ведению документооборота в организации не принято. Я вот до сих пор борюсь с правильным наименованием файлов, шо б на него глянул и понимал, что внутри, но...не могу победить. Когда работу передаю всем легко, так как у меня все структурировано и по полочкам разложено. А как мне подхватывать чью-то работу - это ужас. По большому счету все как в фильме "Ночной продавец"))). Потому и не используем(.


  1. Energy85
    16.09.2023 16:14

    С вордами и экселями в vba можно сделать всё что угодно