用python + openpyxl处理excel2007文档思路以及心得


Posted in Python onJuly 14, 2014

寻觅工具

确定任务之后第一步就是找个趁手的库来干活。 Python Excel上列出了xlrd、xlwt、xlutils这几个包,但是

它们都比较老,xlwt甚至不支持07版以后的excel
它们的文档不太友好,都可能需要去读源代码,而老姐的任务比较紧,加上我当时在期末,没有这个时间细读源代码
再一番搜索后我找到了openpyxl,支持07+的excel,一直有人在维护,文档清晰易读,参照Tutorial和API文档很快就能上手,就是它了~

安装

这个很容易,直接pip install openpyxl,呵呵呵~

因为我不需要处理图片,就没有装pillow。

一些考虑

源文件大约一个在1~2MB左右,比较小,所以可以直接读入内存处理。
既然是处理excel,何况他们整个组显然都是win下干活(数据都用excel存了= =,商科的人啊……),这个脚本还是在win下做吧
这个任务完全不需要我对现有的文件做修改!?濉??抑灰?寥搿⒋?怼⒃傩闯隽硪桓鑫募?托辛?/p>

学习使用

嗯,就是打开cmd,然后用python的shell各种玩这个模块来上手……(win下没有装ipython,?澹?/p>

做这个小脚本基本上我只需要import两个东西

from openpyxl import Workbook
from openpyxl import load_workbook

load_workbook顾名思义是把文件导入到内存,Workbook是最基本的一个类,用来在内存里创建文件最后写进磁盘的。

干活

首先我需要导入这个文件

inwb = load_workbook(filename)

得到的就是一个workbook对象

然后我需要创建一个新的文件

outwb = Workbook()

接着在这个新文件里,用create_sheet新建几个工作表,比如

careerSheet = outwb.create_sheet(0, 'career')

就会从头部插入一个叫career的工作表(也就是说用法类似python list的insert)

接下来我需要遍历输入文件的每个工作表,并且按照表名做一些工作(e.g.如果表名不是数字,我不需要处理),openpyxl支持用字典一样的方式通过表名获取工作表,获取一个工作簿的表名的方法是get_sheet_names

for sheetName in inwb.get_sheet_names():
  if not sheetName.isdigit():
    continue
  sheet = inwb[sheetName]

得到工作表之后,就是按列和行处理了。openpyxl会根据工作表里实际有数据的区域来确定行数和列数,获取行和列的方法是sheet.rows和sheet.columns,它们都可以像list一样用。比如,如果我想跳过数据少于2列的表,可以写

if len(sheet.columns) < 2:
  continue

如果我想获取这个工作表的前两列,可以写

colA, colB = sheet.columns[:2]

除了用columns和rows来得到这个工作表的行列之外,还可以用excel的单元格编码来获取一个区域,比如

cells = sheet['A1':'B20']

有点像excel自己的函数,可以拉出一块二维的区域~

为了方便处理,遇到一个没有C列的工作表,我要创建一个和A列等长的空的C列出来,那么我可以用sheet.cell这个方法,通过传入单元格编号和添加空值来创建新列。

alen = len(colA)
for i in range(1, alen + 1):
  sheet.cell('C%s' % (i)).value = None

注意:excel的单元格命名是从1开始的~

上面的代码也显示出来了,获取单元格的值是用cell.value(可以是左值也可以是右值),它的类型可以是字符串、浮点数、整数、或者时间(datetime.datetime),excel文件里也会生成对应类型的数据。

得到每个单元格的值之后,就可以进行操作了~openpyxl会自 动将字符串用unicode编码,所以字符串都是unicode类型的。

除了逐个逐个单元格用cell.value修改值以外,还可以一行行append到工作表里

sheet.append(strA, dateB, numC)

 最后,等新的文件写好,直接用workbook.save保存就行

outwb.save("test.xlsx")

这个会覆盖当前已有的文件,甚至你之前读取到内存的那个文件。

一些要注意的地方
如果要在遍历一列的每个单元格的时候获取当前单元格的在这个column对象里的下标

for idx, cell in enumerate(colA):
  # do something...

为了防止获取的数据两端有看不见的空格(excel文件里很常见的坑),记得strip()

如果工作表里的单元格没有数据,openpyxl会让它的值为None,所以如果要基于单元格的值做处理,不能预先假定它的类型,最好用

if not cell.value
  continue

之类的语句来先行判断

如果要处理的excel文件里有很多noise,比如当你预期一个单元格是时间的时候,有些表的数据可能是字符串,这时候可以用

if isinstance(cell.value, unicode):
  break

之类的语句处理。

win下的cmd似乎不太好设定用utf-8的code page,如果是简体中文的话可以用936(GBK),print的时候会自动从unicode转换到GBK输出到终端。
 

一些帮忙处理中文问题的小函数
我处理的表有一些超出GBK范围的字符,当我需要把一些信息print出来监控处理进度的时候非常麻烦,好在它们都是可以无视的,我直接用空格替换再print也行,所以加上一些我本来就要替换掉的分隔符,我可以:

# annoying seperators
dot = u'\u00b7'
dash = u'\u2014'
emph = u'\u2022'
dot2 = u'\u2027'

seps = (u'.', dot, dash, emph, dot2)

def get_clean_ch_string(chstring):
  """Remove annoying seperators from the Chinese string.

  Usage:
    cleanstring = get_clean_ch_string(chstring)
  """
  cleanstring = chstring
  for sep in seps:
    cleanstring = cleanstring.replace(sep, u' ')
  return cleanstring

此外我还有一个需求,是把英文名[空格]中文名分成英文姓、英文名、中文姓、中文名。

首先我需要能把英文和中文分割开,我的办法是用正则匹配,按照常见中英文字符在unicode的范围来套。匹配英文和中文的正则pattern如下:

# regex pattern matching all ascii characters
asciiPattern = ur'[%s]+' % ''.join(chr(i) for i in range(32, 127))
# regex pattern matching all common Chinese characters and seporators
chinesePattern = ur'[\u4e00-\u9fff. %s]+' % (''.join(seps))

英文就用ASCII可打印字符的范围替代,常见中文字符的范围是\u4e00-\u9fff,那个seps是前面提到过的超出GBK范围的一些字符。 除了简单的分割,我还需要处理只有中文名没有英文名、只有英文名没有中文名等情况,判断逻辑如下:

def split_name(name):
  """Split [English name, Chinese name].

    If one of them is missing, None will be returned instead.
  Usage:
    engName, chName = split_name(name)
  """
  matches = re.match('(%s) (%s)' % (asciiPattern, chinesePattern), name)
  if matches: # English name + Chinese name
    return matches.group(1).strip(), matches.group(2).strip()
  else:
    matches = re.findall('(%s)' % (chinesePattern), name)
    matches = ''.join(matches).strip()
    if matches: # Chinese name only
      return None, matches
    else: # English name only
      matches = re.findall('(%s)' % (asciiPattern), name)
      return ''.join(matches).strip(), None

得到了中文名之后,我需要分割成姓和名,因为任务要求不需要把姓名分割得很明确,我就按照常见的中文名姓名分割方式来分——两个字or三个字的第一个字是姓,四个字的前两个字是姓,名字带分隔符的(少数民族名字)分隔符前是姓(这里用到了前面的get_clean_ch_string函数来移除分隔符),名字再长一些又不带分割符的,假设整个字符串都是名字。(注意英语的first name 指的是名,last name指的是姓,2333)

def split_ch_name(chName):
  """Split the Chinese name into first name and last name.

    * If the name is XY or XYZ, X will be returned as the last name.
    * If the name is WXYZ, WX will be returned as the last name.
    * If the name is ...WXYZ, the whole name will be returned
     as the last name.
    * If the name is ..ABC * XYZ..., the part before the seperator
     will be returned as the last name.
  Usage:
    chFirstName, chLastName = split_ch_name(chName)
  """
  if len(chName) < 4: # XY or XYZ
    chLastName = chName[0]
    chFirstName = chName[1:]
  elif len(chName) == 4: # WXYZ
    chLastName = chName[:2]
    chFirstName = chName[2:]
  else: # longer
    cleanName = get_clean_ch_string(chName)
    nameParts = cleanName.split()
    print u' '.join(nameParts)
    if len(nameParts) < 2: # ...WXYZ
      return None, nameParts[0]
    chLastName, chFirstName = nameParts[:2] # ..ABC * XYZ...
  return chFirstName, chLastName

分割英文名就很简单了,空格分开,第一部分是名,第二部分是姓,其他情况暂时不管就行。

Python 相关文章推荐
使用Python编写简单的画图板程序的示例教程
Dec 08 Python
Python正则表达式完全指南
May 25 Python
python+matplotlib实现动态绘制图片实例代码(交互式绘图)
Jan 20 Python
pip install urllib2不能安装的解决方法
Jun 12 Python
python 对给定可迭代集合统计出现频率,并排序的方法
Oct 18 Python
浅谈python str.format与制表符\t关于中文对齐的细节问题
Jan 14 Python
python3常用的数据清洗方法(小结)
Oct 31 Python
Pytorch保存模型用于测试和用于继续训练的区别详解
Jan 10 Python
python opencv 检测移动物体并截图保存实例
Mar 10 Python
python使用ctypes库调用DLL动态链接库
Oct 22 Python
python中pyqtgraph知识点总结
Jan 26 Python
matplotlib绘制正余弦曲线图的实现
Feb 22 Python
Python库urllib与urllib2主要区别分析
Jul 13 #Python
Python文件夹与文件的操作实现代码
Jul 13 #Python
分享15个最受欢迎的Python开源框架
Jul 13 #Python
使用 Python 获取 Linux 系统信息的代码
Jul 13 #Python
Python文件操作类操作实例详解
Jul 11 #Python
Python编写检测数据库SA用户的方法
Jul 11 #Python
python实现的生成随机迷宫算法核心代码分享(含游戏完整代码)
Jul 11 #Python
You might like
解决cPanel无法安装php5.2.17
2014/06/22 PHP
PHP经典面试题之设计模式(经常遇到)
2015/10/15 PHP
PHP仿微信多图片预览上传实例代码
2016/09/13 PHP
PHP使用PDO访问oracle数据库的步骤详解
2017/09/29 PHP
PHP Redis扩展无法加载的问题解决方法
2019/08/22 PHP
PHP设计模式之数据访问对象模式(DAO)原理与用法实例分析
2019/12/12 PHP
Javascript无阻塞加载具体方式
2013/06/28 Javascript
get(0).tagName获得作用标签示例代码
2014/10/08 Javascript
node.js中的path.join方法使用说明
2014/12/08 Javascript
Express实现前端后端通信上传图片之存储数据库(mysql)傻瓜式教程(一)
2015/12/10 Javascript
js变量提升深入理解
2016/09/16 Javascript
html5+CSS 实现禁止IOS长按复制粘贴功能
2016/12/28 Javascript
三分钟学会用ES7中的Async/Await进行异步编程
2018/06/14 Javascript
JavaScript中arguments和this对象用法分析
2018/08/08 Javascript
layui默认选中table的CheckBox复选框方法
2019/09/19 Javascript
[01:27]DOTA2电竞之夜 今夜共饮庆功酒
2014/08/02 DOTA
打印出python 当前全局变量和入口参数的所有属性
2009/07/01 Python
Python遍历目录中的所有文件的方法
2016/07/08 Python
Python新手入门最容易犯的错误总结
2017/04/24 Python
Python设计模式之命令模式简单示例
2018/01/10 Python
python实现机器人行走效果
2018/01/29 Python
Python开发最牛逼的IDE——pycharm
2018/08/01 Python
python文件操作之批量修改文件后缀名的方法
2018/08/10 Python
Django实现网页分页功能
2019/10/31 Python
python和js交互调用的方法
2020/06/23 Python
django 将自带的数据库sqlite3改成mysql实例
2020/07/09 Python
CSS3利用text-shadow属性实现多种效果的文字样式展现方法
2016/08/25 HTML / CSS
html5的新玩法——语音搜索
2013/01/03 HTML / CSS
科沃斯机器人官网商城:Ecovacs
2016/08/29 全球购物
国际鲜花速递专家:Floraqueen
2016/11/24 全球购物
C#怎么让一个窗口居中显示?
2015/10/20 面试题
道德模范先进事迹
2014/02/14 职场文书
吃空饷专项治理工作实施方案
2014/03/04 职场文书
2014年评职称工作总结
2014/11/20 职场文书
运动会5000米加油稿
2015/07/21 职场文书
python 如何用terminal输入参数
2021/05/25 Python