Мне нужно было создать подпрограмму, которая просматривает коллекцию сообщений Outlook, открывает вложения и извлекает любые табличные данные из zip-папки в фрейм данных pandas. Чтобы получить табличные данные, я создал функцию zip_to_dfs, которая принимает вложение Outlook MailItem в качестве аргумента).
#function to extract tabluar data within zip file to pandas dataframe. returns dictionary object(key=filename;value=pandas df)
import pandas as pd, zipfile, tempfile, os
def zip_to_dfs(attachment, extract_fn=None):
#returns diciontary object with filename for key and dataframes from attached files as values
df_objects = {}
tmp=tempfile.TemporaryFile().name
attachment.SaveAsFile(tmp)
if zipfile.is_zipfile(tmp)==True:
zf = zipfile.ZipFile(tmp)
#below subroutine could be made to separate function (read tablular to df) to make more readable
for file in zf.infolist():
extension = os.path.splitext(file.filename)[1]
if extension in ['.xls','.xlsx','.xlsm']:
temp_df = pd.read_excel(zf.open(file.filename), header=None)
df_objects.update({file.filename:temp_df})
elif file.filename.endswith(".csv"):
temp_df = pd.read_csv(zf.open(file.filename), header=None)
df_objects.update({file.filename:temp_df})
else:
raise NotImplementedError('Unexpected filetype: '+str(file.filename))
else:
raise NotImplementedError('Expected zip file')
return(df_objects)
Функция работает так, как задумано, но, вероятно, неэффективно. Кто-нибудь использовал библиотеки tempfile или zip-файлов? Если да, то знаете ли вы, очищаются ли методы Zipfile и TemporaryFile автоматически? Или эти файлы остаются открытыми на диске? Видели ли вы какие-либо другие очевидные проблемы при таком подходе?
Отредактированная версия кода:
def zipattach_to_dfs(attachment, extract_fn=None):
#evaluates zip file attachments and returns dictionary with file name as key and dataframes as values
df_objects = {}
with NamedTemporaryFile(suffix='.tmp', delete=False) as tmp:
attachment.SaveAsFile(tmp.name)
zf = ZipFile(tmp)
for file in zf.infolist():
datetime = (file.date_time)
key = (f'{file.filename}({datetime[0]}-{datetime[1]}-{datetime[2]})')
if isexcel(file) ==True:
temp_df = pd.read_excel(zf.open(file.filename), header=None)
df_objects.update({key:temp_df})
elif file.filename.endswith(".csv"):
temp_df = pd.read_csv(zf.open(file.filename), header=None)
df_objects.update({key:temp_df})
else:
raise NotImplementedError('Unexpected filetype: '+str(file.filename))
return (df_objects)
Спасибо за подтверждение. Я отредактировал сообщение, изменив функцию. Надеемся, что обработка zip-файла сделает эту функцию более эффективной с точки зрения ресурсов.
Вам тоже захочется сделать это с tempfile.TemporaryFile(). На самом деле важнее сделать это с временными файлами, поскольку вы хотите их удалить. Я не уверен, каков жизненный цикл временных файлов, которые никогда не закрываются.
Спасибо, я отредактировал редакцию с предложенной рекомендацией!






ZipFile также поддерживает оператор with. Итак, вот мое предложение, основанное на вашем коде (кодах):
def zip_to_dfs(attachment, extract_fn=None): # extract_fn ?
'''
Returns a dictionary object with filename for key
and dataframes from attached files as values.
'''
with NamedTemporaryFile(delete=False) as tmp:
attachment.SaveAsFile(tmp.name)
if is_zipfile(tmp):
with ZipFile(tmp) as zf:
for file in zf.infolist():
fn, exte = file.filename.rsplit(".", 1)
key = (f'{fn} ({"-".join(map(str, file.date_time[:3]))})')
if exte in {'xls', 'xlsx', 'xlsm', 'csv'}:
df_objects = {}
with zf.open(file) as zip_file:
if exte == 'csv':
df = pd.read_csv(zip_file, header=None)
else:
df = pd.read_excel(zip_file, header=None)
df_objects[key] = df
else:
raise NotImplementedError(
'Unexpected filetype: ' + file.filename
)
return df_objects
Вызов этой функции будет выглядеть примерно так:
from win32com.client import Dispatch
outlook = Dispatch("Outlook.Application").GetNamespace("MAPI")
folder = outlook.Folders("[email protected]").Folders("Inbox")
out = pd.concat(
[v.assign(date=k) for item in folder.Items for att in item.Attachments
if zip_to_dfs(att) for k,v in zip_to_dfs(att).items()
]
) # this will consolidate all the dfs in a single one
Эй, большое спасибо, что заглянул. Я определенно предпочитаю вашу переработанную версию своей. Я только что создал отдельную функцию для проверки того, является ли файл файлом Excel, так как мне, возможно, придется повторно использовать эту функцию в будущем.
Далее здесь. Я попробовал ваш метод, но после отладки вижу, что is_zipfile(tmp) каждый раз возвращает true, даже если он содержит файлы xls.
Потому что таблица Excel сама по себе является архивом. Вы можете заменить if is_zipfile(tmp): на if attachement.DisplayName.endswith('zip').
Спасибо! Очевидно, мне нужно многому научиться. Я ценю ваш вклад.
Кроме того, возможно, я не совсем ясно выразился. Вложения уже представляют собой zip-файлы. Они содержат разные документы Excel.
Они очищаются автоматически, если вы используете обработчики контекста (т.е. с... как угодно :)