巧用python win32com模块操作excel文件

  Python操作excel文件的第三方库有很多,小爬就常用openPyxl库来操作已有的excel文件,它对xlsx、xlsm等格式的支持都较好。可openPyxl也有不足,它难以实习VBA中的很多功能。如果我们平日里对VBA语法很熟悉,则可以通过win32com.client来操纵excel文件,语法非常类似。

  之所以不直接使用VBA,是因为VBA擅长跟excel打交道,不擅长跟外部应用打交道。小爬最近就遇到这样一个自动化场景:先利用python爬虫的方法,获取到服务器端的多个excel文件,然后对这些excel文件进行跨表操作,单纯的VBA实现起来比较繁琐,用python和VBA各实现一部分不利于脚本的封装,割裂感较强。

  话不多说,我们看看如何用win32com来控制excel,首先我们需要用pip安装pywin32库,之后就可以使用了:

import win32com.client

下面的代码演示了一些常规的语法操作,与vba如出一辙,只是需要代码pythonic:

import win32com.client
import os
base_dir=os.path.dirname(os.path.abspath(__file__)) # 获取当前路径
xlApp = win32com.client.Dispatch('Excel.Application')
xlApp.Visible=1 # 显示excel界面
filename="test.xlsx"
fullPath=os.path.join(base_dir,filename) # 得到完整的filepath
xlBook = xlApp.Workbooks.Open(fullPath, ReadOnly = False) #打开对饮的excel文件
sht = xlBook.Worksheets('Sheet1') # 打开对应名称的sheet
sht.UsedRange.ClearContents() # 对当前使用区域清除内容
nrows=sht.UsedRange.Rows.Count # 获取使用区域的行数
sht.UsedRange.Copy() #复制
sht.Activate() # 激活当前工作表

光这些还不够,比如我们希望实现excel的复制&粘贴值操作,vba的语法类似这样:

Sub 宏1()
'
    Range("A1").Select
    Selection.CurrentRegion.Select
    Selection.Copy
    Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
End Sub

这段VB风格的代码如何pythonic且不出错呢,你可能首先会想这样改造:

    Range("A1").Select()
    Selection.CurrentRegion.Select()
    Selection.Copy()
    Selection.PasteSpecial(Paste=xlPasteValues, Operation=xlNone, SkipBlanks=False, Transpose=False)

VBA中默认你操作的当前worksheet,所以可以直接使用Range对象,Selection对象,但是python中不能直接这样简写,改造和简化后应该是:

    sht.Range("A1").CurrentRegion.Copy()
    sht.Range("A1").CurrentRegion.PasteSpecial(Paste=xlPasteValues, Operation=xlNone, SkipBlanks=False, Transpose=False)

其实这样还是会报错,因为python并无法知道xlPasteValues、xlNone这些常量到底为多少,因为我们没有提前定义它。如何查询这些常量实际的值,一个简单的方法还是通过VBA的对象浏览器,打开excel,按住快捷键Alt + F11进入VBE界面,见下图:

 

 

 

 

 

   

 

 

 有了这些,上面的伪代码就可以改成如下形式,成功运行通过:

sht.cells(1,1).PasteSpecial(Paste=-4163, Operation=-4142, SkipBlanks=False, Transpose=False)

  小爬知道了这些后,想用分列功能将excel某列文本型数值转为常规的数值格式,操作如动态所示,

vba代码示例如下:

    Columns("A:A").Select
    Selection.TextToColumns Destination:=Range("A1"), DataType:=xlDelimited, _
        TextQualifier:=xlDoubleQuote, ConsecutiveDelimiter:=False, Tab:=True, _
        Semicolon:=False, Comma:=False, Space:=False, Other:=False, FieldInfo _
        :=Array(1, 1), TrailingMinusNumbers:=True

  我们故技重施,可以查到xlDelimited、xlDoubleQuote 这些常量的值,但是这个Array(1, 1) 怎么转化为python语法,小爬目前还没找到合适的方法,有知道的童鞋,可以留言区告诉我,谢谢~

上面的例子说明,上文提到的方法有一定局限性。其实我们可以利用选择性粘贴(乘以1)来达到同样的效果:文本型数字转常规数值,演示动图如下:

 

 这段方法,需要借助一个辅助单元格,将其赋值1,待操作完毕后,再清空该辅助单元格的值即可,这个用VBA代码示例如下:

    Range("B2").Select
    ActiveCell.FormulaR1C1 = "1"
    Range("B2").Select
    Selection.Copy
    Range("A2:A20").Select
    Selection.PasteSpecial Paste:=xlPasteAll, Operation:=xlMultiply, _
        SkipBlanks:=False, Transpose:=False

这次,我们可以用上面的方法得到xlPasteAll、xlMultiply等常量的值,将上面的代码python化即可;

利用这个方法,我们还可以很轻易得到某一列的最后一行的行号,比如A列的,可以这样写:

max_rowA=sht.cells(sht.Rows.Count,1).End(-4162).Row

这回,你应该能看出来上面代码中的-4162 是怎么来的了。

 

希望上面列举的示例,能给各位希望用python win32com.client来操作excel文件的童鞋,一点点借鉴和提示!还不赶紧动手试试?

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

(2020-08-27日更新)

再次查阅了微软的官方文档后,找到关于数据分列(TextToColumns)实现文本型数值转为常规数值的解决方法,见网址:https://docs.microsoft.com/en-us/office/vba/api/excel.range.texttocolumns

详细内容如下:

 

Parameters

 

PARAMETERS
NameRequired/OptionalData typeDescription
Destination Optional Variant A Range object that specifies where Microsoft Excel will place the results. If the range is larger than a single cell, the top left cell is used.
DataType Optional XlTextParsingType The format of the text to be split into columns.
TextQualifier Optional XlTextQualifier Specifies whether to use single, double, or no quotes as the text qualifier.
ConsecutiveDelimiter Optional Variant True to have Excel consider consecutive delimiters as one delimiter. The default value is False.
Tab Optional Variant True to have DataType be xlDelimited and to have the tab character be a delimiter. The default value is False.
Semicolon Optional Variant True to have DataType be xlDelimited and to have the semicolon be a delimiter. The default value is False.
Comma Optional Variant True to have DataType be xlDelimited and to have the comma be a delimiter. The default value is False.
Space Optional Variant True to have DataType be xlDelimited and to have the space character be a delimiter. The default value is False.
Other Optional Variant True to have DataType be xlDelimited and to have the character specified by the OtherChar argument be a delimiter. The default value is False.
OtherChar Optional Variant Required if Other is True; the delimiter character when Other is True. If more than one character is specified, only the first character of the string is used; the remaining characters are ignored.
FieldInfo Optional Variant An array containing parse information for the individual columns of data. The interpretation depends on the value of DataType.

When the data is delimited, this argument is an array of two-element arrays, with each two-element array specifying the conversion options for a particular column. The first element is the column number (1-based), and the second element is one of the XlColumnDataType constants specifying how the column is parsed.
DecimalSeparator Optional Variant The decimal separator that Microsoft Excel uses when recognizing numbers. The default setting is the system setting.
ThousandsSeparator Optional Variant The thousands separator that Excel uses when recognizing numbers. The default setting is the system setting.
TrailingMinusNumbers Optional Variant Numbers that begin with a minus character.

 

  

  可以看到 关于excel录屏得到的VBA分列的代码中,“FieldInfo :=Array(1, 1)”,FieldInfo 字段是可选的,当我们并不准备把数据分成几列的时候,可以不需要该字段。比如我想要对F列进行分列,将文本型数字转为数字,pythonic(win32com.client)的代码是这样的,亲测可用:

workNumSht.Columns("F:F").TextToColumns(Destination=workNumSht.Range("F1"), DataType=2, TrailingMinusNumbers=True)

 

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

(2020-12-21日更新)

上文的方法中,当时小爬还没能找到VBA中的Array类型如何利用python win32com模块来表达,现已找到解决办法。我们知道在VBA中,如下代码就可以轻松将某一行值写入一个一维数组:

dim arr() as string
arr=sheet.Range("A1:M1").Value

我们就可以假定vba中的sheet.Range("A1:M1").Value 属性可以得到一个类似于Array(1,1)的数组对象。我们可以这样去验证这个vba数组对象在python win32com中是如何实现的。我试着利用win32com模块来 print(type(sht.Range("A1:B1").Value)),结果系统返回了一个Tuple元组类型。

这个时候,我们VBA中录制得到的分列功能(TextToColumns)就可以随意python化了。比如下图中,我们希望将B列的字母和数字分开成两列:

  

 

VBA录制宏得到这段分列的代码如下:

Sub 宏1()
'
'
    Columns("B:B").Select
    Selection.TextToColumns Destination:=Range("B1"), DataType:=xlDelimited, _
        TextQualifier:=xlDoubleQuote, ConsecutiveDelimiter:=False, Tab:=False, _
        Semicolon:=False, Comma:=False, Space:=False, Other:=True, OtherChar _
        :="_", FieldInfo:=Array(Array(1, 1), Array(2, 1)), TrailingMinusNumbers:=True
End Sub

我们在python win32com.client中可以这样表达:

xlDelimited=1
xlDoubleQuote=1
sht.Columns("B:B").TextToColumns(Destination=sht.Range("B1"), DataType=xlDelimited, 
TextQualifier=xlDoubleQuote, ConsecutiveDelimiter=False, Tab=False, Semicolon=False, 
Comma=False, Space=False, Other=True, OtherChar ="_", FieldInfo=((1, 1),(2,1)),
 TrailingMinusNumbers=True)

该方法亲测可用,特此奉上~~~

快来扫码关注我的公众号 获取更多爬虫、数据分析的知识!

 

posted @ 2020-08-26 23:59  NewJune  阅读(15371)  评论(6编辑  收藏  举报