openpyxl 操作 Excel#

openpyxl 用于读写 Excel 2010 xlsx/xlsm/xltx/xltm 文件的 Python 库。

安装:

pip install openpyxl

新建工作簿#

无须在文件系统中创建文件即可开始使用 openpyxl。只要导入 Workbook 类就可以开始工作了:

from openpyxl import Workbook

wb = Workbook()

一个工作簿(workbook)至少有一个工作表(worksheet). 你可以通过 active 来获取这个属性:

ws = wb.active

你可以使用 create_sheet() 方法来创建新的工作表:

  1. 在结尾插入(默认)

ws1 = wb.create_sheet("Mysheet")

在开始位置插入:

ws2 = wb.create_sheet("Mysheet", 0)
  1. 插入倒数第二个位置

ws3 = wb.create_sheet("Mysheet", -1)

工作表在创建时会自动生成名字,以 (Sheet, Sheet1, Sheet2, …) 来进行命名。你也可以通过 Worksheet.title 属性来修改命名:

ws.title = "New Title"

默认情况下,包含该标题的选项卡的背景颜色为白色。你也可以使用 RRGGBB 颜色来改变 Worksheet.sheet_properties.tabColor 属性:

ws.sheet_properties.tabColor = "1072BA"

给工作表命名后,就可以将其作为工作簿的键:

ws3 = wb["New Title"]

使用 Workbook.sheetname 属性查看工作簿中所有工作表的名称:

print(wb.sheetnames)
['Mysheet1', 'New Title', 'Mysheet2', 'Mysheet']

遍历工作表:

for sheet in wb:
    print(sheet.title)
Mysheet1
New Title
Mysheet2
Mysheet

你可以在工作簿中创建工作表的副本:

source = wb.active
target = wb.copy_worksheet(source)

备注

只有单元格(包含值、样式、超链接和注释)以及确定的工作表属性(包含尺寸、格式和属性)会被复制。 其余的工作表/工作簿属性都不会被复制,例如:文件、图表。

你也 不能 跨工作簿复制工作表。工作簿以 read-only 或 write_only 模式打开时也无法复制。

操作数据#

访问单元格#

接下来可以开始修改单元格内容了。可以直接通过工作表的键来访问单元格:

c = ws['A4']

这将返回 A4 位置的单元格,如果它还不存在,则创建。可以直接赋值:

ws['A4'] = 4

还有 Worksheet.cell() 方法。

这提供了使用行和列表示法访问单元格的方法:

d = ws.cell(row=4, column=2, value=10)

备注

当工作薄在内存中被创建之后并没有此单元格,单元格只有在被第一次访问(access)的时候才会创建。

警告

由于这个特性,滚动单元格而不是直接访问它们将在内存中创建它们,即使您没有为它们分配值。

比如

for x in range(1,101):
    for y in range(1,101):
        ws.cell(row=x, column=y)

将在内存中创建 100x100 单元格。

访问大量单元格#

可以使用切片来访问一系列单元格:

cell_range = ws['A1':'C2']

一系列的行和列也可以通过类似的方法获取:

colC = ws['C']
col_range = ws['C:D']
row10 = ws[10]
row_range = ws[5:10]

也可以使用 Worksheet.iter_rows 方法:

for row in ws.iter_rows(min_row=1, max_col=3, max_row=2):
    for cell in row:
        print(cell)
<Cell 'New Title'.A1>
<Cell 'New Title'.B1>
<Cell 'New Title'.C1>
<Cell 'New Title'.A2>
<Cell 'New Title'.B2>
<Cell 'New Title'.C2>

同样 Worksheet.iter_cols 方法会返回列:

for col in ws.iter_cols(min_row=1, max_col=3, max_row=2):
    for cell in col:
        print(cell)
<Cell 'New Title'.A1>
<Cell 'New Title'.A2>
<Cell 'New Title'.B1>
<Cell 'New Title'.B2>
<Cell 'New Title'.C1>
<Cell 'New Title'.C2>

如果需要遍历文件中的所有行和列,可以使用 Worksheet.rows 属性:

ws = wb.active
ws['C9'] = 'hello world'
tuple(ws.rows)
((<Cell 'Mysheet1'.A1>, <Cell 'Mysheet1'.B1>, <Cell 'Mysheet1'.C1>),
 (<Cell 'Mysheet1'.A2>, <Cell 'Mysheet1'.B2>, <Cell 'Mysheet1'.C2>),
 (<Cell 'Mysheet1'.A3>, <Cell 'Mysheet1'.B3>, <Cell 'Mysheet1'.C3>),
 (<Cell 'Mysheet1'.A4>, <Cell 'Mysheet1'.B4>, <Cell 'Mysheet1'.C4>),
 (<Cell 'Mysheet1'.A5>, <Cell 'Mysheet1'.B5>, <Cell 'Mysheet1'.C5>),
 (<Cell 'Mysheet1'.A6>, <Cell 'Mysheet1'.B6>, <Cell 'Mysheet1'.C6>),
 (<Cell 'Mysheet1'.A7>, <Cell 'Mysheet1'.B7>, <Cell 'Mysheet1'.C7>),
 (<Cell 'Mysheet1'.A8>, <Cell 'Mysheet1'.B8>, <Cell 'Mysheet1'.C8>),
 (<Cell 'Mysheet1'.A9>, <Cell 'Mysheet1'.B9>, <Cell 'Mysheet1'.C9>))

或者 Worksheet.columns 属性:

tuple(ws.columns)
((<Cell 'Mysheet1'.A1>,
  <Cell 'Mysheet1'.A2>,
  <Cell 'Mysheet1'.A3>,
  <Cell 'Mysheet1'.A4>,
  <Cell 'Mysheet1'.A5>,
  <Cell 'Mysheet1'.A6>,
  <Cell 'Mysheet1'.A7>,
  <Cell 'Mysheet1'.A8>,
  <Cell 'Mysheet1'.A9>),
 (<Cell 'Mysheet1'.B1>,
  <Cell 'Mysheet1'.B2>,
  <Cell 'Mysheet1'.B3>,
  <Cell 'Mysheet1'.B4>,
  <Cell 'Mysheet1'.B5>,
  <Cell 'Mysheet1'.B6>,
  <Cell 'Mysheet1'.B7>,
  <Cell 'Mysheet1'.B8>,
  <Cell 'Mysheet1'.B9>),
 (<Cell 'Mysheet1'.C1>,
  <Cell 'Mysheet1'.C2>,
  <Cell 'Mysheet1'.C3>,
  <Cell 'Mysheet1'.C4>,
  <Cell 'Mysheet1'.C5>,
  <Cell 'Mysheet1'.C6>,
  <Cell 'Mysheet1'.C7>,
  <Cell 'Mysheet1'.C8>,
  <Cell 'Mysheet1'.C9>))

访问值#

如果你只想要工作薄的值,你可以使用 Worksheet.values 属性。这会遍历工作簿中所有的行但只返回单元格值:

for row in ws.values:
   for value in row:
     print(value, end=" ")
None None None None None None None None None None None None None None None None None None None None None None None None None None hello world

Worksheet.iter_rowsWorksheet.iter_cols 可以用 values_only 参数来返回单元格值:

for row in ws.iter_rows(min_row=1, max_col=3, max_row=2, values_only=True):
    print(row)
(None, None, None)
(None, None, None)

数据存储#

一旦有了 Cell, 可以为其分配值:

c.value = 'hello, world'
print(c.value)
hello, world
d.value = 3.14
print(d.value)
3.14

保存至文件#

保存工作簿最简单和安全的方法就是使用 Workbook.save 方法:

wb = Workbook()
wb.save('../build/balances.xlsx')
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[25], line 2
      1 wb = Workbook()
----> 2 wb.save('../build/balances.xlsx')

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/site-packages/openpyxl/workbook/workbook.py:386, in Workbook.save(self, filename)
    384 if self.write_only and not self.worksheets:
    385     self.create_sheet()
--> 386 save_workbook(self, filename)

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/site-packages/openpyxl/writer/excel.py:291, in save_workbook(workbook, filename)
    279 def save_workbook(workbook, filename):
    280     """Save the given workbook on the filesystem under the name filename.
    281 
    282     :param workbook: the workbook to save
   (...)
    289 
    290     """
--> 291     archive = ZipFile(filename, 'w', ZIP_DEFLATED, allowZip64=True)
    292     workbook.properties.modified = datetime.datetime.now(tz=datetime.timezone.utc).replace(tzinfo=None)
    293     writer = ExcelWriter(workbook, archive)

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/zipfile/__init__.py:1331, in ZipFile.__init__(self, file, mode, compression, allowZip64, compresslevel, strict_timestamps, metadata_encoding)
   1329 while True:
   1330     try:
-> 1331         self.fp = io.open(file, filemode)
   1332     except OSError:
   1333         if filemode in modeDict:

FileNotFoundError: [Errno 2] No such file or directory: '../build/balances.xlsx'

警告

该操作将覆盖现有文件而不发出警告。

可以指定属性 template=True 将工作簿保存为模板:

from openpyxl import  load_workbook

wb = load_workbook('../build/balances.xlsx')
wb.template = True
wb.save('../build/document_template.xltx')
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[26], line 3
      1 from openpyxl import  load_workbook
----> 3 wb = load_workbook('../build/balances.xlsx')
      4 wb.template = True
      5 wb.save('../build/document_template.xltx')

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/site-packages/openpyxl/reader/excel.py:346, in load_workbook(filename, read_only, keep_vba, data_only, keep_links, rich_text)
    316 def load_workbook(filename, read_only=False, keep_vba=KEEP_VBA,
    317                   data_only=False, keep_links=True, rich_text=False):
    318     """Open the given filename and return the workbook
    319 
    320     :param filename: the path to open or a file-like object
   (...)
    344 
    345     """
--> 346     reader = ExcelReader(filename, read_only, keep_vba,
    347                          data_only, keep_links, rich_text)
    348     reader.read()
    349     return reader.wb

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/site-packages/openpyxl/reader/excel.py:123, in ExcelReader.__init__(self, fn, read_only, keep_vba, data_only, keep_links, rich_text)
    121 def __init__(self, fn, read_only=False, keep_vba=KEEP_VBA,
    122              data_only=False, keep_links=True, rich_text=False):
--> 123     self.archive = _validate_archive(fn)
    124     self.valid_files = self.archive.namelist()
    125     self.read_only = read_only

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/site-packages/openpyxl/reader/excel.py:95, in _validate_archive(filename)
     88             msg = ('openpyxl does not support %s file format, '
     89                    'please check you can open '
     90                    'it with Excel first. '
     91                    'Supported formats are: %s') % (file_format,
     92                                                    ','.join(SUPPORTED_FORMATS))
     93         raise InvalidFileException(msg)
---> 95 archive = ZipFile(filename, 'r')
     96 return archive

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/zipfile/__init__.py:1331, in ZipFile.__init__(self, file, mode, compression, allowZip64, compresslevel, strict_timestamps, metadata_encoding)
   1329 while True:
   1330     try:
-> 1331         self.fp = io.open(file, filemode)
   1332     except OSError:
   1333         if filemode in modeDict:

FileNotFoundError: [Errno 2] No such file or directory: '../build/balances.xlsx'

备注

可以使用 openpyxl.load_workbook() 打开已存在的工作簿。

保存成流#

如果想把文件保存成流。例如当使用 Pyramid, Flask 或 Django 等 web 应用程序时,可以提供 NamedTemporaryFile

from tempfile import NamedTemporaryFile
from openpyxl import Workbook
wb = Workbook()
with NamedTemporaryFile() as tmp:
    wb.save(tmp.name)
    tmp.seek(0)
    stream = tmp.read()