我想使用Python从Office / Excel文档中添加和提取文件。到目前为止添加东西很容易,但是为了提取我还没有找到一个干净的解决方案。
要清楚我已经得到了什么,以及我在下面写了一些小例子 test.py 并进一步解释。
test.py
import win32com.client as win32
import os
from tkinter import messagebox
import win32clipboard
# (0) Setup
dir_path = os.path.dirname(os.path.realpath(__file__))
print(dir_path)
excel = win32.gencache.EnsureDispatch('Excel.Application')
wb = excel.Workbooks.Open(dir_path + "\\" + "test_excel.xlsx")
ws = wb.Worksheets.Item(1)
objs = ws.OLEObjects()
# (1) Embed file
f = dir_path + "\\" + "test_txt.txt"
name = "test_txt_ole.txt"
objs.Add( Filename=f, IconLabel=name )
# (2) Access embedded file
obj = objs.Item(1) # Get single OLE from OLE list
obj.Copy()
win32clipboard.OpenClipboard()
data = win32clipboard.GetClipboardData(0xC004) # Binary access
win32clipboard.EmptyClipboard()
win32clipboard.CloseClipboard()
messagebox.showinfo(title="test_txt_ole.txt", message=str(data))
# (3) Press don't save here to keep
# wb.Close() # Will close excel document and leave excel opened.
excel.Application.Quit() # Will close excel with all opened documents
对于准备(步骤0),它会打开一个给定的Excel文档,其中包含一个之前使用excel中的新文档按钮创建的工作表。 在步骤(1)中,它使用API将给定的文本文件嵌入到Excel文档中。使用文本编辑器在内容“TEST123”之前创建了文本文件。之后在步骤(2)中,它尝试使用剪贴板从嵌入式OLE读回内容,并打开一个消息框,显示剪贴板中OLE的内容。最后(3)程序关闭打开的文档。要保持不变的设置,请按此处。
这个解决方案的一大缺点是使用剪贴板来破坏剪贴板中的任何用户内容,这在生产环境中是不好的风格。此外,它使用未记录的选项进行剪贴板。
更好的解决方案是将安全的OLE或OLE嵌入文件安全到python数据容器或我选择的文件。在我的例子中,我使用TXT文件轻松识别文件数据。最后,我将使用ZIP作为一体化解决方案,但TXT文件解决方案足以支持base64数据。
0xC004的来源= 49156:https://danny.fyi/embedding-and-accessing-a-file-in-excel-with-vba-and-ole-objects-4d4e7863cfff
这个VBA示例看起来很有趣,但我对VBA没有任何线索:Saving embedded OLE Object (Excel doc) to file in Excel 2010 vs 2013
答案 0 :(得分:4)
好吧,我发现Parfait的解决方案有点hackish(在坏的意义上)因为
所以,我写了一个替代解决方案。其实质如下:
解压缩.xlsx文件(或新XML中的任何其他Office文件 格式,不受密码保护)到临时路径。
遍历' / xxx / embeddings'中的所有.bin文件。 (' xxx' = ' XL'或者'字'或者' ppt'),并创建一个包含.bin的字典 文件'临时路径作为键和返回的字典 第3步作为值。
根据(不是非常)从.bin文件中提取信息 记录良好)Ole Packager格式,并返回信息为 一本字典。 (不仅仅将原始二进制数据检索为'内容' 来自.txt,但任何文件类型,例如.PNG)
我还在学习Python,所以这并不完美(没有错误检查,没有性能优化),但你可以从中获得想法。我在几个例子上测试了它。 这是我的代码:
import tempfile
import os
import shutil
import zipfile
import glob
import pythoncom
import win32com.storagecon
def read_zipped_xml_bin_embeddings( path_zipped_xml ):
temp_dir = tempfile.mkdtemp()
zip_file = zipfile.ZipFile( path_zipped_xml )
zip_file.extractall( temp_dir )
zip_file.close()
subdir = {
'.xlsx': 'xl',
'.xlsm': 'xl',
'.xltx': 'xl',
'.xltm': 'xl',
'.docx': 'word',
'.dotx': 'word',
'.docm': 'word',
'.dotm': 'word',
'.pptx': 'ppt',
'.pptm': 'ppt',
'.potx': 'ppt',
'.potm': 'ppt',
}[ os.path.splitext( path_zipped_xml )[ 1 ] ]
embeddings_dir = temp_dir + '\\' + subdir + '\\embeddings\\*.bin'
result = {}
for bin_file in list( glob.glob( embeddings_dir ) ):
result[ bin_file ] = bin_embedding_to_dictionary( bin_file )
shutil.rmtree( temp_dir )
return result
def bin_embedding_to_dictionary( bin_file ):
storage = pythoncom.StgOpenStorage( bin_file, None, win32com.storagecon.STGM_READ | win32com.storagecon.STGM_SHARE_EXCLUSIVE )
for stastg in storage.EnumElements():
if stastg[ 0 ] == '\1Ole10Native':
stream = storage.OpenStream( stastg[ 0 ], None, win32com.storagecon.STGM_READ | win32com.storagecon.STGM_SHARE_EXCLUSIVE )
result = {}
result[ 'original_filename' ] = '' # original filename in ANSI starts at byte 7 and is null terminated
stream.Seek( 6, 0 )
while True:
ch = stream.Read( 1 )
if ch == '\0':
break
result[ 'original_filename' ] += ch
result[ 'original_filepath' ] = '' # original filepath in ANSI is next and is null terminated
while True:
ch = stream.Read( 1 )
if ch == '\0':
break
result[ 'original_filepath' ] += ch
stream.Seek( 4, 1 ) # next 4 bytes is unused
temporary_filepath_size = 0 # size of the temporary file path in ANSI in little endian
temporary_filepath_size |= ord( stream.Read( 1 ) ) << 0
temporary_filepath_size |= ord( stream.Read( 1 ) ) << 8
temporary_filepath_size |= ord( stream.Read( 1 ) ) << 16
temporary_filepath_size |= ord( stream.Read( 1 ) ) << 24
result[ 'temporary_filepath' ] = stream.Read( temporary_filepath_size ) # temporary file path in ANSI
result[ 'size' ] = 0 # size of the contents in little endian
result[ 'size' ] |= ord( stream.Read( 1 ) ) << 0
result[ 'size' ] |= ord( stream.Read( 1 ) ) << 8
result[ 'size' ] |= ord( stream.Read( 1 ) ) << 16
result[ 'size' ] |= ord( stream.Read( 1 ) ) << 24
result[ 'contents' ] = stream.Read( result[ 'size' ] ) # contents
return result
你可以像这样使用它:
objects = read_zipped_xml_bin_embeddings( dir_path + '\\test_excel.xlsx' )
obj = objects.values()[ 0 ] # Get first element, or iterate somehow, the keys are the temporary paths
print( 'Original filename: ' + obj[ 'original_filename' ] )
print( 'Original filepath: ' + obj[ 'original_filepath' ] )
print( 'Original filepath: ' + obj[ 'temporary_filepath' ] )
print( 'Contents: ' + obj[ 'contents' ] )
答案 1 :(得分:2)
考虑使用Windows临时目录,该目录将在嵌入工作簿时临时存储OLE对象的文件源。此解决方案中没有使用剪贴板,而是使用物理文件。
使用这种方法,您需要检索当前用户的名称并遍历temp目录的所有文件: C:\ Documents and Settings \ {username} \ Local Settings \ Temp (Windows Vista / 7/8/10的标准Excel转储文件夹)。此外,使用带有in
的条件同名搜索,其包含原始文件的基本名称,因为具有数字后缀(1),(2),(3),......的多个版本可能存在,具体取决于脚本运行了多少次。在这里尝试甚至正则表达式搜索。
最后,下面的例程使用try...except...finally
块来清楚地存在Excel对象而不管错误,但会输出任何异常消息。请注意,这只是使用文本文件的Windows解决方案。
import win32com.client as win32
import os, shutil
from tkinter import messagebox
# (0) Setup
dir_path = cd = os.path.dirname(os.path.abspath(__file__))
print(dir_path)
try:
excel = win32.gencache.EnsureDispatch('Excel.Application')
wb = excel.Workbooks.Open(os.path.join(dir_path, "test_excel.xlsx"))
ws = wb.Worksheets(1)
objs = ws.OLEObjects()
# (1) Embed file
f = os.path.join(dir_path, "test_txt.txt")
name = "test_txt_ole.txt"
objs.Add(Filename=f, IconLabel=name).Name = 'Test'
# (2) Open file from temporary folder
ole = ws.OLEObjects(1)
ole.Activate()
# (3) Grab the recent like-named file
user = os.environ.get('USERNAME')
outfile = os.path.join(dir_path, "test_txt_out.txt")
tempfolder = r"C:\Documents and Settings\{}\Local Settings\Temp".format(user)
for subdir, dirs, files in os.walk(tempfolder):
for file in sorted(files, reverse=True):
if 'test_txt' in file:
tempfile = os.path.join(tempfolder, file)
break
shutil.copyfile(tempfile, outfile)
# (4) Read text content
with open(outfile, 'r') as f:
content = f.readlines()
# (5) Output message with content
messagebox.showinfo(title="test_txt_ole.txt", message="".join(content))
except Exception as e:
print(e)
finally:
wb.Close(True) # CLOSES AND SAVES WORKBOOK
excel.Quit # QUITS EXCEL APP
# RELEASES COM RESOURCES
ws = None; wb = None; objs = None; ole = None; excel = None
Tkinter Messagebox
答案 2 :(得分:2)
我构建了一个python模块来完成此检查。 https://pypi.org/project/AttachmentsExtractor/该模块也可以在任何操作系统上运行。
在安装库之后,使用以下代码段 代码:
from AttachmentsExtractor import extractor
abs_path_to_file='Please provide absolute path here '
path_to_destination_directory = 'Please provide path of the directory where the extracted attachments should be stored'
extractor.extract(abs_path_to_file,path_to_destination_directory) # returns true if one or more attachments are found else returns false.
答案 3 :(得分:0)
我最近试图回答一个类似的问题:我可以从 excel 文件中提取嵌入的 Word 文档并将它们保存到磁盘吗?
调整此页面上的答案(并利用 Excel 文件是压缩文件集合(主要是 XML 文件)这一知识)可以轻松执行:
这是一个执行上述操作的片段:
import zipfile
import tempfile
import os
import glob
import shutil
import sys
def extract_embedded_files(file_path,
save_path,
sub_dir='xl'):
"""
Extracts embedded files from Excel documents, it takes advantage of
excel being a zipped collection of files. It creates a temporary folder,
extracts all the contents of the excel folder there and then moves the
embedded files to the requested save_path.
Parameters:
----------
file_path : str,
The path to the excel file to extract embedded files from.
save_path : str,
Path to save the extracted files to.
sub_dir : str,
one of 'xl' (for excel), 'word' , or 'ppt'.
"""
# make a temporary directory
temp_dir = tempfile.mkdtemp()
# extract contents excel file to temporary dir
zip_file = zipfile.ZipFile(file_path)
zip_file.extractall(temp_dir)
zip_file.close()
# find all embedded files and copy to save_path
embeddings_dir = f'{temp_dir}/{sub_dir}/embeddings/'
embedded_files = list(glob.glob(embeddings_dir+'*'))
for file in embedded_files:
shutil.copy(file, save_path)