openpyxl使用—Excel篇

1.安装openpyxl

安装openxlsx

 pip install openpyxl

2.创建/打开、保存工作簿

2.1创建新的工作簿

from openpyxl import Workbook
#新建一个工作簿
#实例化一个对象
wb =Workbook()
ws = wb.active #获取默认的工作表
print(ws.title)  # 返回工作表名,Sheet

#保存的位置
#加r是为了解决转义的问题
wb.save(r"F:\desktop\aaa\test.xlsx")
wb.close()

2.2 打开已有的工作簿

from openpyxl import load_workbook
# 打开已存在的工作簿
wb =load_workbook(r"F:\desktop\aaa\test.xlsx")

ws = wb.active
print(ws.title)
ws.title = "default_2" #改变标题名为default_2
wb.save(r"F:\desktop\aaa\test.xlsx")  #保存

2.3保存工作簿

2.3.1 保存为文件

from openpyxl import Workbook

wb = Workbook()
ws = wb.active 
print(ws.title) 
wb.save("./test.xlsx")  # 保存到硬盘
wb.close()  # 该方法在只读或只写模式下有用

注意,通过保存路径和文件名相同会覆盖原先的文件,不会有提示

2.3.2 保存为流文件

有时候你需要保存为流文件,通过web应用服务进行传输,可以使用下面的方法

from tempfile import NamedTemporaryFile
from openpyxl import Workbook

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

2.4 工作表信息

print(ws.max_row)  # 最大行数,例如14
print(ws.max_column)  # 最大列数,例如20
print(ws.dimensions)  # 已启用的单元格范围,例如A1:T14
print(ws.encoding)  # 编码类型,例如utf-8
print(ws.sheet_view)  # 对象信息

3.操作单元格(获取、修改、合并,删除等)

3.1 操作工作表(创建、改名、移动、复制、删除)

from openpyxl import Workbook
wb =Workbook()
ws1 = wb.active

print(ws1.title)  # 返回工作表名,Sheet
print(wb.sheetnames)

# create_sheet(title,index),接收两个参数,表名和位置
# title:表名
# index:下标即位置,从0开始
#创建sheet2工作簿,在2的位置,一开始索引为0
ws2 = wb.create_sheet("Sheet2",1)
ws3 = wb.create_sheet("Sheet3",2)


# 通过表名获取表
ws4=wb["Sheet3"]
print(ws4.title)

#将sheet3工作簿做移动一位, -1为向左移动,+1为向右移动
wb.move_sheet(ws3,-1)
#删除sheet3工作簿
# del wb["Sheet3"]

# 获取表的下标位置(下标从0开始)
index = wb.index(ws2)
print("get_index:", index)

print(wb.sheetnames)

3.2获取和修改单个单元格

from openpyxl import Workbook
wb = Workbook()
ws = wb.active

cell = ws["a6"]  # 通过坐标获取
cell2 = ws.cell(1, 2)  # 通过行列下标获取

# 直接修改某个单元格的值
ws["a5"] = 666  
ws['A3'] = datetime.datetime.now().strftime("%Y-%m-%d")  # 修改为时间类型
ws.append([1, 2, 3]) # 在最下面新增一行追加一个或多个值

# 先获取单元格对象然后再进行修改
cell = ws["a6"]
cell.value = 777
print(cell, cell.value)  # 输出:<Cell 'Sheet'.A6> 777

cell2 = ws.cell(6, 1)  # 第6行第1列,即A6
print(cell2, cell2.value)  # 输出:<Cell 'Sheet'.A6> 777

# 单元格坐标信息
print(c.coordinate)  # 单元格坐标,例如A6
print(c.column_letter)  # 单元格列名,例如A
print(c.col_idx)  # 单元列下标,例如1
print(c.row)  # 单元格所在行,例如6

1.如果使用cell(row, column, value)获取,第一个参数是行,第二个参数是列,下标都是从1开始,例如,ws[“a6”]等同于ws.cell(6, 1),但如果指定了第三个参数value,则修改了该单元格的值
2.只要访问了一个cell就会被创建,不管是否赋值

根据上面的方式,我们可以通过循环来准备一下数据

from openpyxl import Workbook
wb =Workbook()
ws = wb.active

x=1
for i in range(1,11):
    for j in range(1,6):
        ws.cell(i,j,x)
        x += 1
wb.save("./fangwen.xlsx")

这样我们就得到了从1~50共50个(10行5列)单元格的数据

image-20240125220815805

3.3 获取多个单元格

3.3.1 通过范围取值

from openpyxl import Workbook

wb = Workbook()

ws = wb.active

i = 1
for x in range(1, 11):
    for y in range(1, 21):
        ws.cell(row=x, column=y, value=i)
        i += 1

# wb.save("test.xlsx")

row_cells = ws[2]  # 选取第2行(下标从1开始)
print(row_cells)  # 输出:(A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, L2, M2, N2, O2, P2, Q2, R2, S2, T2)

col_cells = ws["b"]  # 选取B列
print(col_cells)  # 输出:(B1, B2, B3, B4, B5, B6, B7, B8, B9, B10)

row_range_cells = ws[2:5]  # 选取2、3、4、5共4行
print(row_range_cells)  # 输出:
# ((A2, B2, C2, D2, E2, F2, G2, H2, I2, J2, K2, L2, M2, N2, O2, P2, Q2, R2, S2, T2),
# (A3, B3, C3, D3, E3, F3, G3, H3, I3, J3, K3, L3, M3, N3, O3, P3, Q3, R3, S3, T3),
# (A4, B4, C4, D4, E4, F4, G4, H4, I4, J4, K4, L4, M4, N4, O4, P4, Q4, R4, S4, T4),
# (A5, B5, C5, D5, E5, F5, G5, H5, I5, J5, K5, L5, M5, N5, O5, P5, Q5, R5, S5, T5))

col_range_cells = ws["B:D"]  # 选取B、C、D共3列
print(col_range_cells)  # 输出:
# ((B1, B2, B3, B4, B5, B6, B7, B8, B9, B10),
# (C1, C2, C3, C4, C5, C6, C7, C8, C9, C10),
# (D1, D2, D3, D4, D5, D6, D7, D8, D9, D10))

range_cells = ws["c3:f6"]  # 选取 C3到F6区域共16个元素
print(range_cells)  # 输出:
# ((C3, D3, E3, F3),
# (C4, D4, E4, F4),
# (C5, D5, E5, F5),
# (C6, D6, E6, F6))

1.以上输出应该类似<Cell ‘Sheet’.A2>、 <Cell ‘Sheet’.B2>,为了好看,简化为A2、B2的形式
2.以上获取到的多个单元格,返回的是元组或元组套元组,可以通过遍历的方式访问或修改

3.3.2通过iter_rows或iter_cols迭代取值

iter_rows()与iter_cols()都可以指定最大最小的行列,下标从1开始
返回结果是生成器

...
# wb.save("test.xlsx")

cells = ws.iter_rows(min_row=1, max_row=3, min_col=2, max_col=5)
for cell in cells:
    print(cell)
# 输出:
# (B1, C1, D1, E1)
# (B2, C2, D2, E2)
# (B3, C3, D3, E3)

cells = ws.iter_cols(min_row=1, max_row=3, min_col=2, max_col=5)
for cell in cells:
    print(cell)
# 输出:
# (B1, B2, B3)
# (C1, C2, C3)
# (D1, D2, D3)
# (E1, E2, E3)

iter_cols和iter_rows都可以指定参数values_only=True,这样只返回值而不是cell对象

也可以使用rows或columns属性遍历全部行或列,values属性取出所有值,它们都得到迭代器,但是注意只读模式下columns属性无效

for cell in ws.rows:
    print(cell)

for cell in ws.columns:
    print(cell)

for row in ws.values:
    for value in row:
        print(value)

3.4操作单元格

3.4.1 合并单元格

合并单元格,会保留最左上角的单元格的数据和样式,其他单元格会被清空,即使取消合并。即,合并之后只保留左上角第一个单元格的数据和样式

from openpyxl import Workbook

wb = Workbook()
ws = wb.active
i = 1
for x in range(1, 11):
    for y in range(1, 21):
        ws.cell(row=x, column=y, value=i)
        i += 1

print(ws["C2"].value)  # 输出:23
ws.merge_cells("A1:F3")
ws.unmerge_cells("A1:F3")
print(ws["C2"].value)  # 输出:None
# 等同于下面的代码
# ws.merge_cells(start_row=1, start_column=1, end_row=3, end_column=6)
# ws.unmerge_cells(start_row=1, start_column=1, end_row=3, end_column=6)
wb.save("./test.xlsx")

3.4.2 删除或插入行列

from openpyxl import Workbook

wb = Workbook()
ws = wb.active
i = 1
for x in range(1, 11):
    for y in range(1, 21):
        ws.cell(row=x, column=y, value=i)
        i += 1

ws.insert_cols(5)  # 在第5列即E列插入1列,原来的E列及后面的列都往后移动
ws.insert_rows(2, 3)  # 在第2行后面插入3行
ws.delete_cols(2, 3)  # 从2列开始往后删除3列
ws.delete_rows(5, 3)  # 从5行开始往后删除3行

wb.save("./test.xlsx")

3.4.3 移动单元格

可以使用move_range()合并指定范围的单元格,但是注意,如果移动到的位置原来有数据会被覆盖掉,移动之后公式会丢失,可以通过设置translate=True来更新,默认是False

from openpyxl import Workbook

wb = Workbook()
ws = wb.active
i = 1
for x in range(1, 11):
    for y in range(1, 21):
        ws.cell(row=x, column=y, value=i)
        i += 1

ws.move_range("B1:D3", rows=6, cols=-1, translate=False)  # 移动单元格,向下移动6行,向左移动1列

wb.save("./test.xlsx")

4.设置样式(字体样式、行列宽高、对齐方式等)

4.1字体样式

from openpyxl import Workbook
from openpyxl.styles import Font

wb = Workbook()
ws = wb.active

# 默认字体样式
ws["A1"] = "A1"

# 自定义字体样式
ws["B2"] = "B2"
font = Font(
    name="微软雅黑",   # 字体
    size=15,         # 字体大小
    color="0000FF",  # 字体颜色,用16进制rgb表示
    bold=True,       # 是否加粗,True/False
    italic=True,     # 是否斜体,True/False
    strike=None,     # 是否使用删除线,True/False
    underline=None,  # 下划线, 可选'singleAccounting', 'double', 'single', 'doubleAccounting'
)
ws["B2"].font = font

wb.save("./test.xlsx")

4.2 行列宽高

from openpyxl import Workbook
wb = Workbook()
ws = wb.active

ws.row_dimensions[2].height = 30  # 设置第2行高度为30
ws.column_dimensions["B"].width = 30  # 设置B列宽度为30

wb.save("./test.xlsx")

4.3 对齐方式

from openpyxl import Workbook
from openpyxl.styles import Alignment

wb = Workbook()
ws = wb.active

ws.row_dimensions[2].height = 30  # 设置第2行高度为30
ws.column_dimensions["B"].width = 30  # 设置B列宽度为30

# 默认字体样式
ws["A1"] = "A1"

ws["B2"] = "B1"
ws['B2'].alignment = Alignment(
    horizontal='left',  # 水平对齐,可选general、left、center、right、fill、justify、centerContinuous、distributed
    vertical='top',  # 垂直对齐, 可选top、center、bottom、justify、distributed
    text_rotation=0,  # 字体旋转,0~180整数
    wrap_text=False,  # 是否自动换行
    shrink_to_fit=False,  # 是否缩小字体填充
    indent=0,  # 缩进值
)

wb.save("./test.xlsx")

4.4 边框

from openpyxl import Workbook
from openpyxl.styles import Border, Side

wb = Workbook()
ws = wb.active

ws["B2"] = "B2"

side = Side(
    style="medium",  # 边框样式,可选dashDot、dashDotDot、dashed、dotted、double、hair、medium、mediumDashDot、mediumDashDotDot、mediumDashed、slantDashDot、thick、thin
    color="ff66dd",  # 边框颜色,16进制rgb表示
)

ws["B2"].border = Border(
    top=side,  # 上
    bottom=side,  # 下
    left=side,  # 左
    right=side,  # 右
    diagonal=side  # 对角线
)

wb.save("./test.xlsx")

4.5 填充和渐变

from openpyxl import Workbook
from openpyxl.styles import PatternFill, GradientFill

wb = Workbook()
ws = wb.active

ws["B2"] = "B2"

fill = PatternFill(
    patternType="solid",  # 填充类型,可选none、solid、darkGray、mediumGray、lightGray、lightDown、lightGray、lightGrid
    fgColor="F562a4",  # 前景色,16进制rgb
    bgColor="0000ff",  # 背景色,16进制rgb
    # fill_type=None,  # 填充类型
    # start_color=None, # 前景色,16进制rgb
    # end_color=None    # 背景色,16进制rgb
)
ws["B2"].fill = fill
ws["B3"].fill = GradientFill(
    degree=60,  # 角度
    stop=("000000", "FFFFFF")  # 渐变颜色,16进制rgb
)

wb.save("./test.xlsx")

5.使用公式、复制(翻译)公式

5.1可用公式

当然我演示的openpyxl版本是3.0.9,一共支持352个公式,公式保存在一个frozenset类型的集合了,我们可以通过python的in语法判断是否支持某个公式,记住每个公式都是大写的

from openpyxl.utils import FORMULAE

print(FORMULAE)  # frozenset({'ODD', 'VDB', 'RANK', 'LOGEST', 'ISNONTEXT', 'COUNTA'...
print(len(FORMULAE))  # 352

# 判断是否支持某个公式,公式名区分大小写
print("SUM" in FORMULAE)  # True
print("PI" in FORMULAE)  # True
print("sum" in FORMULAE)  # False

使用公式很简单,你只要记得公式名和用法,直接像在Excel那样输入即可,例如,下面的求和、求平均值

from openpyxl import Workbook

wb = Workbook()
ws = wb.active

ws.append(["价格1", "价格2", "总和", "平均值"])
ws.append([22, 63])
ws.append([11, 88])
ws.append([15, 68])

ws["c2"] = "=SUM(A2,B2)"  # 求和
ws["d2"] = "=AVERAGE(A2:B2)"  # 求平均值
wb.save("test.xlsx")

image-20240125230402487

5.2 翻译公式

过Excel的同学都知道,当某个单元格使用了公式,可以通过拖动填充柄的方式快速复制上一个公式进行填充,在openpyxl做法如下

from openpyxl import Workbook
from openpyxl.formula.translate import Translator

wb = Workbook()
ws = wb.active

ws.append(["价格1", "价格2", "总和", "平均值"])
ws.append([22, 63])
ws.append([11, 88])
ws.append([15, 68])

ws["c2"] = "=SUM(A2,B2)"
ws["d2"] = "=AVERAGE(A2:B2)"
# C3、C4使用上面的C2的求和公式
ws["C3"] = Translator(formula="=SUM(A2,B2)", origin="C2").translate_formula("C3")
ws["C4"] = Translator(formula="=SUM(A2,B2)", origin="C2").translate_formula("C4")

wb.save("test.xlsx")

结果如下图

image-20240125230441230

当然,既然是重复操作,我们要使用优雅的循环写法

from openpyxl import Workbook
from openpyxl.formula.translate import Translator
....
ws["c2"] = "=SUM(A2,B2)"
ws["d2"] = "=AVERAGE(A2:B2)"
# C3、C4使用上面的C2的求和公式
for cell in ws["C3:C4"]:
    # ws["C3"] = Translator(formula="=SUM(A2,B2)", origin="C2").translate_formula("C3")
    cell[0].value = Translator(formula="=SUM(A2,B2)", origin="C2").translate_formula(cell[0].coordinate)
wb.save("test.xlsx")

6. 插入图标(例入折线图)

6.1 图表

Excel支持的图表类型还挺多的,包括柱状图、折线图、饼图、雷达图等等,2D和3D都有,而且支持很多自定义配置,例如颜色、大小、位置等。因为内容较多,所以我这里只举例折线图,其他图表类型大家可以参考官方文档
https://openpyxl.readthedocs.io/en/stable/charts/introduction.html

6.2折线图代码

from openpyxl import Workbook
from openpyxl.chart import LineChart, Reference

wb = Workbook()
ws = wb.active

# 准备数据
rows = [
    ['月份', '桃子', '西瓜', '龙眼'],
    [1, 38, 28, 29],
    [2, 52, 21, 35],
    [3, 39, 20, 69],
    [4, 51, 29, 41],
    [5, 29, 39, 31],
    [6, 30, 41, 39],
]
for row in rows:
    ws.append(row)

# 创建图表
c1 = LineChart()
c1.title = "折线图"  # 标题
c1.style = 13  # 样式
c1.y_axis.title = '销量'  # Y轴
c1.x_axis.title = '月份'  # X轴

# 选择数据范围
data = Reference(ws, min_col=2, min_row=1, max_col=4, max_row=7)
c1.add_data(data, titles_from_data=True)

# 线条样式
s0 = c1.series[0]
s0.marker.symbol = "triangle"  # triangle为三角形标记, 可选circle、dash、diamond、dot、picture、plus、square、star、triangle、x、auto
s0.marker.graphicalProperties.solidFill = "FF0000"  # 填充颜色
s0.marker.graphicalProperties.line.solidFill = "0000FF"  # 边框颜色
# s0.graphicalProperties.line.noFill = True  # 改为True则隐藏线条,但显示标记形状

s1 = c1.series[1]
s1.graphicalProperties.line.solidFill = "00AAAA"
s1.graphicalProperties.line.dashStyle = "sysDot"  # 线条点状样式
s1.graphicalProperties.line.width = 80000  # 线条大小,最大20116800EMUs

s2 = c1.series[2]  # 采用默认设置
s2.smooth = True  # 线条平滑

ws.add_chart(c1, "A8")  # 图表位置

wb.save("line.xlsx")

大概过程是,创建一个图表(Chart)–指定数据范围(Reference)–设置系列(series)样式–添加到工作表中

7.过滤和排序

7.1过滤和排序

如果你想对表格进行过滤或排序,openpyxl有提供对应的设置,但是,只是添加过滤排序选项,并不会真的操作数据,如果想要操作,还是得在Excel中手动点击

image-20240125230724634

2.代码

from openpyxl import Workbook

wb = Workbook()
ws = wb.active

# 准备数据
rows = [
    ['月份', '桃子', '西瓜', '龙眼'],
    [1, 38, 28, 29],
    [2, 52, 21, 35],
    [3, 39, 20, 69],
    [4, 51, 29, 41],
    [5, 39, 39, 31],
    [6, 30, 41, 39],
]
for row in rows:
    ws.append(row)

ws.auto_filter.ref = "A1:D7"  # 选择数据范围
ws.auto_filter.add_filter_column(1, ["39", "29", "30"])  # 选择第2列为过滤数据(下标从0开始),并勾选需要过滤的数据项
ws.auto_filter.add_sort_condition("C2:C7", True)  # 设置排序范围,第二个参数是是否倒序,默认为否

wb.save("./openpyxl/test.xlsx")

效果如下

image-20240125230752631

7.2 pandas排序

虽然openpyxl不能真的实现排序,但是我们可以借助超级强大的pandas轻松实现排序

import pandas as pd
# 读取上一步保存的Excel文件
df = pd.read_excel("./openpyxl/test.xlsx", sheet_name="Sheet")
df_value = df.sort_values(by=["桃子", "西瓜"], ascending=False)  # 如果"桃子"数据相同再按照"西瓜"进行排列
# 保存文件
writer = pd.ExcelWriter('./openpyxl/sort_file.xlsx')
df_value.to_excel(writer, sheet_name='Sheet1', index=False)
writer.save()

8. 只读模式、只写模式

说明:

前面我们使用的normal模式进行读写Excel文件,这是一种兼顾读写相对比较平衡的模式,但是,数据加载到内存占用的资源是比较大的,大概是文件的50倍,如果你的Excel文件本身就10M,加载之后程序
需要占用0.5G内存,这很不划算(大内存电脑请自动忽略),所以我们需要考虑是不是可以选择只读只写模式以便提高性能

8.1只读模式

只读模式,如果你需要读取很大的Excel文件,但是又不改变和保存,例如只读取数值用于其他数据分析,这时候我们完全可以使用只读模式提供性能

from openpyxl import load_workbook

# 加载Excel文件时使用read_only指定只读模式
wb = load_workbook(filename='large_file.xlsx', read_only=True)
ws = wb['big_data']

# 可以正常读取值
for row in ws.rows:
    for cell in row:
        print(cell.value)

# 注意:读取完之后需要手动关闭避免内存泄露
wb.close()

load_workbook参数说明:

定义:
def load_workbook(filename, read_only=False, keep_vba=KEEP_VBA, data_only=False, keep_links=True)

参数:
read_only:是否只读,默认False
keep_vba:是否使用VBA编程,默认False
data_only:是否只加载数据值,即丢弃公式、排序等操作,默认False
keep_links:是否保留超链接,默认True

8.2 只写模式

如果文件是以写为主,可以在创建工作簿的时候指定为只写模式以便提高性能,不管文件有多大,都可以把内存保持在10M以下

from openpyxl import Workbook
from openpyxl.cell import WriteOnlyCell
from openpyxl.comments import Comment
from openpyxl.styles import Font

wb = Workbook(write_only=True)  # 创建工作簿时指定只写模式
ws = wb.create_sheet()  # 需要通过create_sheet创建一个sheet

# 可以正常保存数据
for _ in range(100):
    ws.append([i for i in range(200)])  # 只能通过append写

# 如果需要保留公式、注释等操作,可以使用WriteOnlyCell
cell = WriteOnlyCell(ws, value="冰冷的希望")
cell.font = Font(name='黑体', size=15)
cell.comment = Comment(text="这是注释", author="pan")

ws.append([cell])

wb.save('openpyxl/test.xlsx')

只写模式注意点:
1.需要通过create_sheet()创建表
2.只能通过append()增加数据,不能通过cell或iter_rows()
3.wb.save()之后不能再修改,否则抛出WorkbookAlreadySaved异常