目录

打开表格,读取数据

获取到当前已打开的excel (22/9/20)

win32的写入操作 与 下拉列表(数据验证)

隐藏表(行列)

刷格式 

数据透视表 

复制粘贴,快速下拉



使用win32com操作excel有点类似于VBA(双厨狂喜),这是一个很顾名思义的库,他的作用是模拟用户去做一些操作,像一些简单的例如操作office全家桶完全不在话下。

安装

pip install pywin32

以下进入正文:

打开表格,读取数据

import win32com.client

path=r'C:\Users\gztsrayz\Desktop\酸奶.xlsx'
excel = win32com.client.DispatchEx('Excel.Application')   #这个是必备的,
                                               使用win32建新excel也需要他
excel.Visible = True          #是否可视化
wb = excel.Workbooks.Open(path, UpdateLinks=False, ReadOnly=False)

wb1 = excel.Workbooks.Add()   #新建excel

wb1.Worksheets.Add().Name = '帅比'   #新建sheet,.Name 与后面可不写
ws1 = wb1.Worksheets('帅比')

wb.Save()     #保存
wb.SaveAs(r'C:xxx/xxx/milk.xlsx')    #另存为
wb.Close(False)               #关闭,问就是不保存
#如果你想看到excel温馨的提示,括号里就别填东西
excel.quit()
print([i.Name for i in wb.Sheets])     #查看已有sheet名
ws = wb.Worksheets('汇总')
#ws = wb.Worksheets(1)
#ws = wb.Worksheets[0]                #同样的,通过名字或者位置来指定表
      #在测试的时候还发现通过位置选定表时()和[]都可用,但是[]获取位置是从0开始计
                 

ws.UsedRange.Rows                #最大使用行(包括中间的空行),注意某些行若曾经存在过数据
ws.UsedRange.Columns             #即单元格被设置了格式,使用此种方法会被判定为被占用
                                 #导致读出来的行数可能并不正确
ws.Range("A1").End(-4121).Row       # 模拟 ctrl + ↓ ,从此格往下读到空行,或者跳过空行
ws.Range("A100").End(-4162).Row     # ctrl + ↑
#同理还有-4159 向左,-4161 向右

list0=ws.Range('A2:A5').Value        #跨行读出来是二维元组
sheet1.Range('A2:Z2').AutoFilter(17,"精英干员")  #筛选

=========================================================================

获取到当前已打开的excel (22/9/20)

当我们打开多个同样的表时,从第二个开始都会变成只读模式。

当关掉第一份表,第二份表就会弹出提示,而这个提示会导致我们的代码报错

判断这个excel是否打开,可以利用os遍历文件夹判断是否存在临时文件

        for i in os.listdir(path0):
            if '~$' in i and 'xlsx' in i:  #可以用名字判断,更加准确
                break
        else:
            print('此文件已打开')
            time.sleep(0.5)
            sys.exit()

但是这样只能够判断是否打开,而无法接着打开的excel进行操作,于是又尝试了下面这一段代码

import win32com.client
ee = win32com.client.GetActiveObject('Excel.Application')   #只能用注册名
print(ee.name)
# ee = ee.Workbooks(1)   #当只有一个excel时此句可有可无
# print(ee.name)
ws =ee.Worksheets(1)
ws.Range('A1').Value

===============================
if len(ee.Workbooks)>1:   #判断该进程下有多少打开的excel文件
    for i in ee.Workbooks:
        print(i.name)
else:
    print(ee.Workbooks(1).name)

值得注意的是,我们每次使用win32com打开excel都会生成一个新的进程,而人手打开的excel会归在同一个进程下 (此图中第一个进程为人手打开)

而代码似乎只能识别到第一个进程?(后续我会继续尝试可否识别到第二第三个进程)

也就是说若第一个进程是以人手打开的情况下,我们可以很轻松的判断这个文件是否打开,并且接手它。(PS:当然这里只是举个例子,事实上我们每次使用win32com操作完excel都应该关闭创建的excel进程,除非有特例如user运行完了不希望退出excel以方便直接观看)

如果你的第一个进程是win32com创建的,哪怕你将它所打开的excel关闭了,也依然会读取到这个进程,然后报错

在注释掉此句代码之后,我们会获取到目前活跃的excel文件(即最近一次选中的excel)(仅限在第一个进程中)

ee = ee.Workbooks(1)

 通过接手直接打开的excel 而不是使用win32com打开,在关闭的时候我们可以减去关闭excel进程的这一操作,因为进程会在我们关闭excel时自动结束。

我个人习惯在Quit()之后使用这种方法

import   win32process,win32api,win32con
def close_excel_by_force(self,excel):  # 关闭进程

        # Get the window's process id's
        hwnd = excel.Hwnd
        #import win32gui
        #hwnd = win32gui.FindWindowEx(0,0,None,name)
        t, p = win32process.GetWindowThreadProcessId(hwnd)
        # Ask window nicely to close
        try:
            handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE, 0, p)
            if handle:
                win32api.TerminateProcess(handle, 0)
                win32api.CloseHandle(handle)
        except:
            pass

 =========================================================================

win32的写入操作 与 下拉列表(数据验证)

list1=[['das'],['hdfg'],['asg'],[456]]
list2=[45,132,46]
ws.Range('G1:G4').Value=list1
ws.Range('H1:J1').Value=list2

#亦或者是
ws.Range(ws.Cells(1,1),ws.Cells(2,2)).Value=[['a','s'],['d','a']]

ws.Range('C1:C5').Validation.Add(Type=3, AlertStyle=1, Operator=1, Formula1="=$G$1:$G$7")
#   $  符号不能少
#注意数据验证的源范围只能在同excel内
#val = ws.Cells(6,1).Validation
#val.Add(Type=3, AlertStyle=1, Operator=1, Formula1="=Sheet1!A1:A5")
#val.IgnoreBlank = -1
#val.InCellDropdown = -1
#val.InputTitle = ""
#val.ErrorTitle = ""
#val.InputMessage = ""
#val.ErrorMessage = ""
#val.ShowInput = -1
#val.ShowError = -1

========================================================================= 

隐藏表(行列)

ws.Visible= False   #隐藏sheet,显示为True,隐藏为False
ws.Range('A:G').EntireColumn.Hidden=True
ws.Range('1:5').EntireRow.Hidden=True

========================================================================= 

刷格式 

既然有写入,那么自然就要刷格式

ws.Range("A1:B1").Interior.ColorIndex=44           #刷单元格颜色
ws.Range("A2").Font.ColorIndex=23                   #刷字体颜色
ws.Range('A2:B2').NumberFormatLocal = "@"     #设置单元格格式为文本
ws.Range("A1:B1").Font.Bold = True            #加粗
ws1.Range('A1:H1').VerticalAlignment=2        #垂直居中   1上3下
ws1.Range('A1:H1').HorizontalAlignment=3      #水平居中   2左4右  
ws.Range("C1:D1").Merge()          #合并单元格,合并之后读写均需选中第一个单元格

ws.Range("A1:T1").Columns.AutoFit() 
#自动调节边框宽度,有时调节出来不准确,需要手动再校正
ws.Range("A1").ColumnWidth = 40          # 手动设置宽度

 ps : 这个B列就是我说的时灵时不灵的情况

 =========================================================================

数据透视表 

给大家来一个数据透视表的简单版:

PivotSourceRange = ws.Range("M1:O10")    #数据源
PivotTargetRange = ws.Range("R2")        #存放位置

PivotCache = wb.PivotCaches().Create(SourceType=1, \
SourceData=PivotSourceRange, Version=5)
#这里的version需要注意,不同版本的excel可能version会有偏差

PivotTable = PivotCache.CreatePivotTable(\
TableDestination=PivotTargetRange,TableName=PivotTableName)
PivotTable.PivotFields("不能说的").Orientation = 1               #行筛选 
PivotTable.PivotFields("我好想你").Orientation = 4     #求和项值   

 =========================================================================

复制粘贴,快速下拉

在我刚开始使用win32com的时候,时常会遇到要先将数据源筛选再拿取的情况,我一开始的想法是,先筛选再全部吃,但是这种方法,不论你使用win32com还是xlrd读取数据,你获得的最大行列都是不受筛选影响的。后来我想到了不进行   AutoFilter  筛选,而是在我吃数据的时候添加筛选条件,但是这样如果条件多的话,写出来会很冗杂很不美观,(ps:虽然长名称确实易于理解,但是放在长代码里却会看的人眼花缭乱)

ws.AutoFilterMode=False   #取消这个表的所有筛选
ws.Range('A1:Q1').AutoFilter(7,'精英干员')  #在你选定的范围中是第几列
ws.Range('A1:Q1').AutoFilter(6,'*龙门*')
ws.Range('A1:Q1').AutoFilter(5,'人外娘')
============================================
list1=[]
for i in (0,nrows):
     if ws.Range('E%d'%i).Value=='人外娘' and ws.Range('F%d'%i).Value=='龙门' 
        and ws.Range('G%d'%i).Value=='精英干员':
            list1.append(ws.Range('A%d:Q%d'%(i,i)).Value)

再后来我发现了复制粘贴的方法,虽然与一开始一样,获得的最大行是筛选前的,但是被筛选隐藏的部分却不会被选中,这样一来我就能让我的代码更加的简洁,在写入的时候也不用再一行行的写入,运行时间得到了极大的提升。

ws.Range('A1:Q1').AutoFilter(7,'精英干员')
ws.Range('A1:Q1').AutoFilter(6,'龙门')
ws.Range('A1:Q1').AutoFilter(5,'人外娘')

wb.Worksheets.Add().Name='铜雀台'
ws1=wb.Worksheets('铜雀台')

ws.Range('A1:Q%d'%nrows).Copy(ws1.Range('A1')) #同理Cut则是剪切

# ws1.Range('K1').Copy()
# ws1.Range('K14').PasteSpecial(-4163)   #值粘贴

这样一来,我就将8行的代码直接缩短了一半

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

excel的使用总不会是单纯的复制粘贴,我们经常需要将数据进行一些加工处理

比如最经典的成绩单

不论数据的多少,我们总是习惯使用公式,包括但不仅限于excel的函数,在我最开始使用win32com的时候,我使用的是这种方法:

for i in range(0,nrows):
       ws.Range('H%d'%i).Value='=F%d+G%d'%(i,i)

 但很快我就发现了这种方法的局限性,平时玩玩还可以,但要是放到大文件中,它写入的速度就实在是太慢了,excel的处理经常一个表就会有上千近万条,像这样一个一个格子得写到什么时候啊

于是我又发现了这种方法

ws.Range('H2').Value='=F2+G2'
ws.Range('H2').AutoFill(ws.Range('H2:H%d'%nrows))

我只需要声明一次我要的方法,后面的格子就会自动调用

使用这两种方法,我原来运行一次要六分钟的代码,现在只需要几十秒就能解决。

 
小贴士

 最后的最后,给大家一个温馨的提示

因为win32com部分内容是VBA与差不多的,所以可能大家会感到困惑。

这种时候我们只需要打开excel的视图,点击录制宏,录制下我们想进行的操作,

然后再查看宏,就能看到这些操作的VBA代码啦

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐