时间:2019-11-13  来源:简书  作者:爱捣腾的吴大爷  阅读:2

实现方法很多,不仅限于Python,一般场景下通过VBA去做应该会更方便。

主要是因为同事有类似的需求,需要批量解除大量Excel文档的工作簿保护,统计其中的信息,而他又使用了Python,我正好没写过,顿时来了兴致就写了一下。

首先推荐几个非常棒的在线工具,简单便捷

在线EXCEL(xls,xlsx)文档打开密码移除工具

在线EXCEL(xls,xlsx)文档限制解除工具

可以快速方便的帮你移除excel文档的相关限制,如果你想取消密码,同样也适用

Python操作Excel的库如xlrd、openpyxl等都无法实现解除工作簿保护,因此只能通过COM组件的方式调用Excel来解除。

我将其通过COM组件调用Excel解除工作簿保护的部分单独提取了出来,因为其它地方也可能会用到。

用法上涉及到了configparser(解析配置文件)、logging(日志记录)以及win32com.client的Dispatch(COM组件调用)等模块。

注释我已经尽可能详细,初学者或感兴趣的可以尝试折腾。

你可能需要通过以下命令安装configparser

pip install configparser

其主要代码如下:

# Excel 工具

#!/usr/bin/env python3

"""

Excel 工具

"""

import configparser

import logging

import os

import win32com.client

from win32com.client import Dispatch

class ExcelTools:

def __init__(self):

# 创建 logger 实例

# 日志记录器不会直接实例化,而是始终通过模块级方法logging.getLogger(name)进行实例化

# 多次调用getLogger()具有相同的 name 将始终返回对同一个Logger对象的引用

# 通过 __name__ 来命名 日志记录器 可用来追踪包/模块 ,从logger名字直观上找到事件发生处

# __name__ 是python模块中的一个内置属性

# 一个模块的 __name__ 的值取决于您如何应用模块。

# 如果 import 一个模块,那么模块__name__ 的值通常为模块文件名,

# 如果直接运行模块,在这 种情况下, __name__ 的值将为"__main__"。

logger = logging.getLogger(__name__)

# 设置该日志记录器处理的阈值[也就是设置一个记录的级别,低于这个级别的日志会被忽略]

logger.setLevel(logging.DEBUG)

# 创建日志记录器记录时的内容格式

# 以下格式为:发生时间-调用模块的名称-调用方法的名称-源文件的行号-记录级别-事件描述消息

formatter = logging.Formatter(

'%(asctime)s - %(name)s - %(funcName)s - %(lineno)d - %(levelname)s - %(message)s')

# 创建日记处理程序,我们要输出到控制台则需要创建一个控制台处理程序以便处理记录的日志

# 创建 控制台处理程器 并设置 级别 以进行调试

console = logging.StreamHandler()

# 设置该控制台处理器的日志记录级别

console.setLevel(logging.DEBUG)

# 添加格式化程序到控制台

console.setFormatter(formatter)

# 添加控制台处理器添加到日志记录器

# 可以将多个处理器添加到该日志记录器中,如同时可以添加一个FileHandler文件处理器以便将日志同时记录到文件中

logger.addHandler(console)

self.logger = logger

try:

self.excel = excel = win32com.client.Dispatch('Excel.Application')

logger.info("Excel COM库版本:%s" % excel.Version)

except Exception as err:

logger.error('未检测到Excel COM组件: %s' % err)

def decode(self, path, password):

'解除Excel的工作簿保护'

logger = self.logger

excel = self.excel

try:

# os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表。

for file in os.listdir(path):

# 获取该文件的绝对路径

absFilePath = os.path.abspath(os.path.join(path, file))

logger.info('文件路径:%s' % absFilePath)

# 判断是否是一个文件,如果不是文件则跳过

if not os.path.isfile(absFilePath):

continue

# excel打开该文件

wb = excel.Workbooks.Open(absFilePath)

# excel隐藏界面,运行在后台,不显示

excel.Visible = False

try:

logger.info('去除工作簿保护:%s' % file)

# 解除保护

wb.Unprotect(password)

except Exception as err:

logger.error('解除 %s 的工作簿保护出错:%s' % (file, err))

# 保存文件

wb.Save()

# 文件保存并关闭

wb.Close(SaveChanges=True)

finally:

# 确保在出错的时候也能正确的关闭Excel

# 未关闭在后台运行的Excel可能会导致很多奇怪的问题

# 如果出现莫名其妙的错误,请查看任务管理器关闭所有的Excel进程

if hasattr(excel, 'Quit'):

logger.info('关闭Excel')

excel.Quit()

def main():

'主方法'

# 创建配置文件解析实例

conf = configparser.ConfigParser()

# 指定配置文件路径和编码

conf.read('conf.ini', 'utf-8') # 文件路径

# 读取配置信息

path = conf.get("Conf", "path")

password = conf.get("Conf", "password")

xls_tools = ExcelTools()

xls_tools.decode(path, password)

if __name__ == "__main__":

main()

conf.ini配置文件如下:

[Conf]

path=文件路径

password=密码

Top