学生试卷中的题目有要提交截图的,也有要提交文件的,为了方便学生考试,允许单独交或者嵌入Word中提交,那么事后如何整理学生的答案?单独提交的比较方便,直接扫描文件名匹配名字后放入指定文件夹即可。但是嵌入到Word中的图片和文件怎么提取出来呢?
现有如下需求:提取出一个Word文档中所有的图片(png、jpg)和嵌入的文件(任意格式)放入到指定的文件夹。
解决
docx是一个压缩包,解压缩后图片一般都放在文档名.docx\word\media\目录下:
而嵌入式文件一般都放在文档名.docx\word\embeddings\目录下:
经过询问度娘,发现提取图片比较简单,直接使用docx库中的Document.part.rels{k:v.target_ref}找到文件的相对路径,用Document.part.rels{k:v.target_part.blob}读出文件内容。简单判断一下路径和文件后缀是不是我们需要的media下的png文件和embeddings下的bin文件,是的话写入到新文件中即可:
提取图片
安装python-docx库
pip install python-docx
提取
import os from docx import Document # pip install python-docx is_debug = True if __name__ == '__main__': # 需要导出的Word文档路径 # Python学习交流群 279199867 target_file = r'paper\HBase试题.docx' # 导出文件所在目录 output_dir = r'paper\output' # 加载Word文档 doc = Document(target_file) # 遍历Word包中的所有文件 dict_rel = doc.part.rels # r_id:文件身份码,rel:文件对象 for r_id, rel in dict_rel.items(): if not ( # 如果文件不是在media或者embeddings中的,直接跳过 str(rel.target_ref).startswith('media') or str(rel.target_ref).startswith('embeddings') ): continue # 如果文件不是我们想要的后缀,也直接跳过 file_suffix = str(rel.target_ref).split('.')[-1:][0] if file_suffix.lower() not in ['png', 'jpg', 'bin']: continue # 如果输出目录不存在,创建 if not os.path.exists(output_dir): os.makedirs(output_dir) # 构建导出文件的名字和路径 file_name = r_id + '_' + str(rel.target_ref).replace('/', '_') file_path = os.path.join(output_dir,file_name) # 将二进制数据写入到新位置的文件中 with open(file_path, "wb") as f: f.write(rel.target_part.blob) # 打印结果 if is_debug: print('导出文件成功:', file_name)
运行结果:
可以看到,图片都能正常导出,但是学生嵌入的JAVA文件并没有导出,或者说导出的是bin文件,没有完全导出。
提取嵌入式文件
再次询问度娘发现,这种其实也是zip压缩包,但是不能直接提取出,它有个更专业的名字,叫ole文件,我们之前的doc、xls、ppt等没有带x的上古文档文件都是这种格式。那如何提取出文件呢?度娘告诉我有个叫oletools的项目可以,于是下载下来浅浅地分析了下,发现确实可以!
oletools项目地址:https://github.com/decalage2/oletools
或者gitee上别人转存的地址:https://gitee.com/yunqimg/oletools
我是用的gitee上的版本,因为github打不开 QwQ
经相关文档介绍,项目下的oletools-master\oletools\oleobj.py就可以提取这种bin后缀的ole文件,简单试一下,在oleobj.py所在目录下打开命令行,把刚刚提取出的rId12_embeddings_oleObject1.bin文件复制到oleobj.py所在目录,执行如下命令:
注意: 在此之前我执行了一下安装oletools的命令,如果不安装可能会出错:pip install oletools,或者说oleobj.py依赖olefile:pip install olefile,在安装oletools时顺便安装了olefile。
python oleobj.py rId12_embeddings_oleObject1.bin
成功导出
Microsoft Windows [版本 10.0.22000.708] (c) Microsoft Corporation。保留所有权利。 D:\Minuy\Downloads\oletools-master\oletools-master\oletools>python oleobj.py rId12_embeddings_oleObject1.bin oleobj 0.56 - http://decalage.info/oletools THIS IS WORK IN PROGRESS - Check updates regularly! Please report any issue at https://github.com/decalage2/oletools/issues ------------------------------------------------------------------------------- File: 'rId12_embeddings_oleObject1.bin' extract file embedded in OLE object from stream '\x01Ole10Native': Parsing OLE Package Filename = "Boos.java" Source path = "D:\111\´ó20´óÊý¾Ý Àî¾üÁé\Boos.java" Temp path = "C:\Users\ADMINI~1\AppData\Local\Temp\Boos.java" saving to file rId12_embeddings_oleObject1.bin_Boos.java D:\Minuy\Downloads\oletools-master\oletools-master\oletools>
导出的文件也能正常访问:
于是把oletools目录复制到工程项目下,稍微修改一下oleobj.py能让我的代码调用它,在oleobj.py中添加如下代码:
def export_main(ole_files, output_dir, log_leve=DEFAULT_LOG_LEVEL): ensure_stdout_handles_unicode() logging.basicConfig(level=LOG_LEVELS[log_leve], stream=sys.stdout, format='%(levelname)-8s %(message)s') # 启用日志模块 log.setLevel(logging.NOTSET) any_err_stream = False any_err_dumping = False any_did_dump = False for container, filename, data \ in xglob.iter_files(ole_files, recursive=False, zip_password=None, zip_fname='*'): if container and filename.endswith('/'): continue # 输出文件夹 err_stream, err_dumping, did_dump = \ process_file(filename, data, output_dir) any_err_stream |= err_stream any_err_dumping |= err_dumping any_did_dump |= did_dump return_val = RETURN_NO_DUMP if any_did_dump: return_val += RETURN_DID_DUMP if any_err_stream: return_val += RETURN_ERR_STREAM if any_err_dumping: return_val += RETURN_ERR_DUMP return return_val def export_ole_file(ole_files, output_dir, debug=False): debug_leve = 'critical' if debug: debug_leve = 'info' # 导出 result = export_main( ole_files, output_dir, debug_leve ) if result and debug: print('导出ole文件出错', ole_files)
在提取文件的代码后面加上如下调用:
if str(rel.target_ref).startswith('embeddings'): # 解压嵌入式文件 export_ole_file([file_path], output_dir)
再次运行
成功导出嵌入到Word中的文件!
成功解决问题~