目录
打开表格,读取数据
获取到当前已打开的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 + ↑
list0=ws.Range('A2:A5').Value #跨行读出来是二维元组
sheet1.Range('A2:Z2').AutoFilter(17,"精英干员") #筛选
=========================================================================
当我们打开多个同样的表时,从第二个开始都会变成只读模式。
当关掉第一份表,第二份表就会弹出提示,而这个提示会导致我们的代码报错
判断这个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
=========================================================================
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代码啦