Задать вопрос
@IvanIvanichNN
Атишничаю потихоньку

Как оптимизировать работу с файлами в Python?

Всем привет! Подскажите кто чем может по следующему вопросу.
Есть 16 тысяч экселек, данные из которых мне нужно будет слить воедино. Однако названия листов несмотря на одинаковое содержание различаются, впрочем как и их порядок. Хочу для начала с помощью Python переименовать листы и привести их названия к единому виду чтобы можно было собирать с них данные по единому ключу. Набросал код, который собирает из файлов уникальные названия листов и выгружает их в файлик для последующей обработки:
def sheet_name_file():
    from openpyxl import load_workbook, Workbook
    import os
    sheetname_set = set()
    for dir in os.listdir('C:\\Users\Иван Данчук\Documents\Тест_shutil'):
        path = f'C:\\Users\Иван Данчук\Documents\Тест_shutil\{dir}'
        for file in os.listdir(path):
            wb = load_workbook(os.path.join(path, file))
            sheetname_set.update(wb.sheetnames[0:10])
            print(file)
        wb2 = Workbook()
        ws = wb2.active
        for row in range(1):
            ws.append(list(sheetname_set))
        wb2.save(f'unique_sheet_name_list_{dir}.xlsx')


Затестил его производительность - за 12 минут он обработал 40 файлов в двух папках! То есть на всю обработку уйдет где то 80 часов.
В связи с этим у меня следующие вопросы:
1) можно ли распараллелить обработку файлов по директориям с помощью библиотеки multiprocessing ? Если можно помогите с примером кода - как это правильно сделать?
2) можно ли этот код оптимизировать для обработки?
3) и стоит ли такие масштабные вещи с Excel'ьками проводить в Python? Или лучше в VBA погрузиться для решения подобных задач?
  • Вопрос задан
  • 451 просмотр
Подписаться 1 Средний Комментировать
Решение пользователя Пычев Анатолий К ответам на вопрос (3)
Или лучше в VBA погрузиться для решения подобных задач?

VBA - однопоточный, расспаралелить не получится.

но если только собрать названия листов, то странно большое время у Вас для такой задачи.
Я тут на коленке набросал код на VBA, так он 200 файлов за 4 минуты обработал, и это без оптимизации кода (excel 32, win 10, 4ядра по 3ГГц).
Можете запустить у себя, для этого открыть новую книгу, открыть редактор макросов, выбрать лист на котором будете работать и вставить следующий код (полной заменой)
Код для сбора списка листов из всех книг директории
Function FilenamesCollection(ByVal FolderPath As String, Optional ByVal Mask As String = "", _
                             Optional ByVal SearchDeep As Long = 999) As Collection
    ' © EducatedFool  excelvba.ru/code/FilenamesCollection
    ' Получает в качестве параметра путь к папке FolderPath,
    ' маску имени искомых файлов Mask (будут отобраны только файлы с такой маской/расширением)
    ' и глубину поиска SearchDeep в подпапках (если SearchDeep=1, то подпапки не просматриваются).
    ' Возвращает коллекцию, содержащую полные пути найденных файлов
    ' (применяется рекурсивный вызов процедуры GetAllFileNamesUsingFSO)

    Set FilenamesCollection = New Collection    ' создаём пустую коллекцию
    Set FSO = CreateObject("Scripting.FileSystemObject")    ' создаём экземпляр FileSystemObject
    GetAllFileNamesUsingFSO FolderPath, Mask, FSO, FilenamesCollection, SearchDeep ' поиск
    Set FSO = Nothing     ' очистка строки состояния Excel
End Function
 
Function GetAllFileNamesUsingFSO(ByVal FolderPath As String, ByVal Mask As String, ByRef FSO, _
                                 ByRef FileNamesColl As Collection, ByVal SearchDeep As Long)
    ' перебирает все файлы и подпапки в папке FolderPath, используя объект FSO
    ' перебор папок осуществляется в том случае, если SearchDeep > 1
    ' добавляет пути найденных файлов в коллекцию FileNamesColl
    On Error Resume Next: Set curfold = FSO.GetFolder(FolderPath)
    If Not curfold Is Nothing Then    ' если удалось получить доступ к папке

        ' раскомментируйте эту строку для вывода пути к просматриваемой
        ' в текущий момент папке в строку состояния Excel
        ' Application.StatusBar = "Поиск в папке: " & FolderPath

        For Each fil In curfold.Files    ' перебираем все файлы в папке FolderPath
            If fil.Name Like "*" & Mask Then FileNamesColl.Add fil.Path
        Next
        SearchDeep = SearchDeep - 1    ' уменьшаем глубину поиска в подпапках
        If SearchDeep Then    ' если надо искать глубже
            For Each sfol In curfold.SubFolders    ' перебираем все подпапки в папке FolderPath
                GetAllFileNamesUsingFSO sfol.Path, Mask, FSO, FileNamesColl, SearchDeep
            Next
        End If
        Set fil = Nothing: Set curfold = Nothing    ' очищаем переменные
    End If
End Function

Sub LoopThroughFiles(ByVal sDirName As String, ByRef lRow As Long, ByVal sMask As String)
   On Error Resume Next
   Dim folder$, coll As Collection
   Dim EX As Excel.Application
   Dim wkb As Workbook
   Dim wks As Worksheet
   Dim file As Variant
   Dim i As Long
   Dim v As Variant
   
 
    folder$ = sDirName
    If Dir(folder$, vbDirectory) = "" Then
        MsgBox "Не найдена папка «" & folder$ & "»", vbCritical
        Exit Sub        ' выход, если папка не найдена
    End If
 
    Set coll = FilenamesCollection(folder$, sMask)        ' получаем список файлов по маске из папки
    If coll.Count = 0 Then
'        MsgBox "В папке «" & Split(folder$, "\")(UBound(Split(folder$, "\")) - 1) & "» нет ни одного подходящего файла!", _
               vbCritical, "Файлы для обработки не найдены"
        Exit Sub        ' выход, если нет файлов
    End If
 
   Set EX = New Application
   EX.Visible = False
   
   ' перебираем все найденные файлы
   For Each file In coll
    
      Cells(lRow, 2) = CStr(file)
      
      Set wkb = EX.Workbooks.Open(Filename:=file)

      ' Если книга не пуста
      If wkb.Sheets.Count > 0 Then
         i = 1
         ReDim v(1 To wkb.Sheets.Count)
         ' Получаем названия листов
         For Each wks In wkb.Sheets
            v(i) = wks.Name
            i = i + 1
         Next wks

      End If

      Cells(lRow, 3) = Join(v, ",")

      wkb.Close False
            
      DoEvents
      
      lRow = lRow + 1
    
      DoEvents
    Next file
    
   Set wks = Nothing: Set wkb = Nothing: Set EX = Nothing
   Set colShts = Nothing
    
End Sub

Sub LoopThroughDirs()
   Dim lLastRow As Long
   Dim lRow As Long
   Dim i As Long
   Dim v As Variant
   Dim dTime As Double

   lRow = 2
   lLastRow = Cells(Rows.Count, 1).End(xlUp).Row
   
   v = Range(Cells(2, 1), Cells(lLastRow, 2))
   
   dTime = Time()
   For i = LBound(v) To UBound(v)
      Application.StatusBar = "Обрабатывается директория " & i & " из " & UBound(v)
      Call LoopThroughFiles(v(i, 1), lRow, "*.xls")
      Call LoopThroughFiles(v(i, 1), lRow, "*.xlsx")
      Call LoopThroughFiles(v(i, 1), lRow, "*.xlsm")
      DoEvents
   Next i
   MsgBox "Готово за " & CStr(CDate(Time() - dTime))
End Sub


для запуска в первой колонке заполнить директории которые он будет смотреть. Просматриваются вложенные директории до 999 грубины.

Запустить процедуру LoopThroughDirs, для этого поставить курсор на название и нажать F5

Результат
5f05adc35867d361594006.png
Ответ написан