目前有一个需求,就是批量读取当前目录下所有文件夹里的Excel文件,去获取出Excel文件中的图片,并根据图片对应的行去获取某列的值作为命名方式进行命名,并统一保存在一个新的文件夹里面。
自己花了几个小时写了一个小工具出来,利用的是openpyxl这个库,其他库用了提取效果不太好,这个提取效果挺不错的。以下代码要根据实际需求,将“货品编码”改成你对应需要的值。如果你不需要命名规则,则直接去掉都行。
第一个版本,针对于不规则分布图片的Excel,进行每个单元格进行遍历,比较费时:
import os
from openpyxl import load_workbook
from openpyxl.drawing.image import Image
from openpyxl_image_loader import SheetImageLoader
from openpyxl.utils.cell import get_column_letter
from PIL import Image
# 创建文件夹
def create_folder():
if not os.path.exists('images'):
os.makedirs('images')
print("成功创建/更新文件夹!")
# 获取当前目录下的文件夹
def get_folders(directory):
folders = []
for entry in os.scandir(directory):
if entry.is_dir():
folders.append(entry.name)
print("成功获取当前目录的文件夹!")
return folders
# 提取图片
def extract_images():
# 创建存放文件夹
create_folder()
# 获取当前目录下的文件夹
folders = get_folders('.')
i = 1
num = 1
# 遍历当前目录下的文件夹
for folder in folders:
print(f"正在遍历第{i}个文件夹{folder}......")
# 进行提取图片
num = extract_images_from_excel(folder, num)
i += 1
# 进行提取图片
def extract_images_from_excel(folder, num):
# 遍历当前文件夹内的所有文件
for entry in os.scandir('.\\'+folder):
# 如果当前对象是文件且后缀是xlsx
if entry.is_file() and entry.name.endswith('.xlsx'):
print(f'{folder}下的Excel文件路径为:{entry.path}')
# 打开当前文件
wb = load_workbook(entry.path)
# 获取当前xlsx的所有Sheet表
worksheets = wb.worksheets
# 遍历xlsx中每一个Sheet
for ws in worksheets:
# 获取当前列名为货品编码的列序号
code_index = ''
for column in ws.iter_cols():
if column[0].value == "货品编码":
code_index = column[0].column
# 创建图片加载对象
image_loader = SheetImageLoader(ws)
# 每一行进行遍历,获取行序号和该行数据
for row_index, row in enumerate(ws.rows, start=1):
# 每一列进行遍历
for column_index in range(1, len(row) + 1):
# 获取列序号
column_letter = get_column_letter(column_index)
# 如果当前单元格是图片
if image_loader.image_in(f'{column_letter}{row_index}'):
# 获取图片
image = image_loader.get(f'{column_letter}{row_index}')
# 获取图片格式
image_type = image.format
# 获取当前行的货品编码列的值
code = ws.cell(row=(row_index), column=code_index).internal_value
# 保存图片(保存命名为 序号_货品编码)
print(f'正在提取单元格{column_letter}{row_index + 1}的图片......')
image.save(f"./images/{code}_{num}.{image_type}")
# 序号递增
num += 1
# 关闭文件对象
wb.close()
return num
if __name__ == '__main__':
print("此版本是针对于图片分布不规则的情况,提取图片速度尚且较慢")
print("开始提取......")
# 提取图片
extract_images()
print("提取完成!")
第二个版本,针对于某一列统一分布图片的Excel,只会进行有图片那一列的遍历,比较快速:
import os
from openpyxl import load_workbook
from openpyxl.drawing.image import Image
from openpyxl_image_loader import SheetImageLoader
from openpyxl.utils.cell import get_column_letter
from PIL import Image
# 创建文件夹
def create_folder():
if not os.path.exists('images'):
os.makedirs('images')
print("成功创建/更新文件夹!")
# 获取当前目录下的文件夹
def get_folders(directory):
folders = []
for entry in os.scandir(directory):
if entry.is_dir():
folders.append(entry.name)
print("成功获取当前目录的文件夹!")
return folders
# 提取图片
def extract_images():
# 创建存放文件夹
create_folder()
# 获取当前目录下的文件夹
folders = get_folders('.')
i = 1
num = 1
# 遍历当前目录下的文件夹
for folder in folders:
print(f"正在遍历第{i}个文件夹{folder}......")
# 进行提取图片
num = extract_images_from_excel(folder, num)
i += 1
# 进行提取图片
def extract_images_from_excel(folder, num):
# 遍历当前文件夹内的所有文件
for entry in os.scandir('.\\'+folder):
# 如果当前对象是文件且后缀是xlsx或者xls
if entry.is_file() and (entry.name.endswith('.xlsx') or entry.name.endswith('.xls')):
print(f'{folder}下的Excel文件路径为:{entry.path}')
# 打开当前文件
wb = load_workbook(entry.path)
# 获取当前xlsx的所有Sheet表
worksheets = wb.worksheets
# 遍历xlsx中每一个Sheet
for ws in worksheets:
# 获取当前列名为货品编码的列序号
code_index = ''
for column in ws.iter_cols():
if column[0].value == "货品编码":
code_index = column[0].column
# 创建图片加载对象
image_loader = SheetImageLoader(ws)
# 记录第一次遍历的标志
img_sign_index = ''
# 每一行进行遍历,获取行序号和该行数据
for row_index, row in enumerate(ws.rows, start=1):
# 只有第一次才会进行每列遍历,去找到图片所在的列
if img_sign_index == '':
# 每一列进行遍历
for column_index in range(1, len(row) + 1):
# 获取列序号
column_letter = get_column_letter(column_index)
if image_loader.image_in(f'{column_letter}{row_index}'):
# 获取对应图片的列序号
img_sign_index = column_letter
break
# 如果不为空,则证明有图片,反之直接跳过
if img_sign_index != '':
# 后面遍历直接去找图片所在的列
image = image_loader.get(f'{img_sign_index}{row_index}')
# 获取图片格式
image_type = image.format
# 获取当前行的货品编码列的值
code = ws.cell(row=(row_index), column=code_index).internal_value
# 保存图片(保存命名为 序号_货品编码)
print(f'正在提取单元格{img_sign_index}{row_index + 1}的图片......')
image.save(f"./images/{num}_{code}.{image_type}")
# 序号递增
num += 1
# 关闭文件对象
wb.close()
return num
if __name__ == '__main__':
print("此版本是针对于图片集中分布在一列的情况,能更快提取图片出来")
print("开始提取......")
# 提取图片
extract_images()
print("提取完成!")
注释都比较清楚,但是现在有一个问题就是,image_loader.get()方法只能提取包含于单元格内的图片,不能获取在单元格边缘有溢出的图片,目前还没有去解决这个问题,之后解决后会更新。
2023-07-03 更新
第三个版本
此版本不是遍历单元格,是直接找图片,再锁定图片的中心行位置去找相应的货品编码,效率更高,而且不会因为图片位于单元格边缘存在识别不到的问题。
import os
from openpyxl import load_workbook
import os
from openpyxl_image_loader import SheetImageLoader
from openpyxl.utils.cell import get_column_letter
# 创建文件夹
def create_folder():
if not os.path.exists('images'):
os.makedirs('images')
print("成功创建/更新文件夹!")
# 获取当前目录下的文件夹
def get_folders(directory):
folders = []
for entry in os.scandir(directory):
if entry.is_dir():
folders.append(entry.name)
print("成功获取当前目录的文件夹!")
return folders
# 提取图片
def extract_images():
# 创建存放文件夹
create_folder()
# 获取当前目录下的文件夹
folders = get_folders('.')
i = 1
num = 1
# 遍历当前目录下的文件夹
for folder in folders:
print(f"正在遍历第{i}个文件夹{folder}......")
# 进行提取图片
num = extract_images_from_excel(folder, num)
i += 1
# 进行提取图片
def extract_images_from_excel(folder, num):
# 遍历当前文件夹内的所有文件
for entry in os.scandir('.\\'+folder):
# 如果当前对象是文件且后缀是xlsx或者xls
if entry.is_file() and entry.name.endswith('.xlsx'):
print(f'{folder}下的Excel文件路径为:{entry.path}')
# 打开当前文件
wb = load_workbook(entry.path)
# 遍历每一个Sheet
for sheet_name in wb.sheetnames:
sheet = wb[sheet_name]
image_loader = SheetImageLoader(sheet)
# 获取当前列名为货品编码的列序号
code_index = ''
for column in sheet.iter_cols():
if column[0].value == "货品编码":
code_index = column[0].column
# 遍历Sheet中的所有图片
for image in sheet._images:
# 获取图片中心行数,判断货品编码是哪一个
row_index = (int(((image.anchor._from.row + 1) + (image.anchor.to.row + 1)) / 2))
# 获取当前行的货品编码列的值(取中间值)
code = sheet.cell(row=row_index, column=code_index).value
# 获取图片格式
img_format = image.format
# 重新将图片获取出来(因为获取下标这个image没有存储方法),直接通过定位左上角坐标将图片取出来
img = image_loader.get(f'{get_column_letter(image.anchor._from.col + 1)}{image.anchor._from.row + 1}')
# 保存图片
print(f'正在提取货品编码为{code}的图片{image}......')
img.save(f'./images/{num}_{code}.{img_format}')
# 序号递增
num += 1
# 关闭文件对象
wb.close()
return num
# v1.0:此版本是针对于图片分布不规则的情况,提取图片速度尚且较慢
# v1.1:此版本是针对于图片集中分布在一列的情况,能更快提取图片出来。
# v1.2:此版本解决图片位于Excel边界时存在的问题,只要图片中心行在这一行,就可以匹配相应的国家编码,同时不用去遍历,直接获取图片。
if __name__ == '__main__':
print("开始提取......")
# 提取图片
extract_images()
print("提取完成!")
2023-10-08 更新
第四个版本:增加了图片的压缩,不需要压缩的可以直接不调用压缩犯法即可,增加了交互,听取了评论区大佬的意见,现在可以提取同一单元格多张图片,在此谢谢评论区大佬。
import math
from openpyxl import load_workbook
import os
from PIL import Image
# 命名规则
good_code = ""
# 命名字典
name_dict = {}
# 图片数量
img_num = 0
# 记录哪些文件夹已经被提取过了
folder_name_dict = {}
# 是否输出提取文本
is_text = True
# 创建文件夹
def create_folder():
if not os.path.exists('images'):
os.makedirs('images')
print("成功创建/更新images文件夹!")
# 提取图片
def extract_images(stop):
if stop:
return
global img_num
global good_code
folder = input("请输入需要提取的文件夹名称(不输入则遍历当前目录下未提取过的所有文件夹):")
good_code = input("请输入命名规则对应表格中的名字(不输入则默认为货品编码):")
if good_code == "":
good_code = "货品编码"
if folder != '':
# 查找指定文件夹
extract_images_from_excel(folder)
else:
folders = []
for entry in os.scandir('.'):
if entry.is_dir():
folders.append(entry.name)
i = 1
# 记录可提取的文件夹的数量
number = 0
# 遍历当前目录下的文件夹
for folder in folders:
if folder in folder_name_dict:
continue
print(f"正在遍历第{i}个文件夹{folder}......")
# 进行提取图片
extract_images_from_excel(folder)
number += 1
i += 1
if number == 0:
print("没有可供提取的文件夹了!")
return
is_success()
img_num = 0
status = input("\n是否继续提取(输入Y表示是,输入其他则退出):")
if status == "Y" or status == "y":
extract_images(False)
else:
extract_images(True)
def is_success():
if img_num == 0:
if is_text:
print(f'没有提取到图片!')
else:
print(f'成功提取{img_num}张图片!')
print("图片提取完成,请到images文件夹中查看!")
# 进行提取图片
def extract_images_from_excel(folder):
global img_num
global is_text
is_have_excel = False
path = os.path.join('.', folder)
if not os.path.exists(path):
print(f'{folder}文件夹未找到!')
return
# 判断文件夹是否已经被提取过了
if folder not in folder_name_dict:
is_text = True
else:
print(f'{folder}文件夹已经被提取过了!')
is_text = False
return
try:
# 遍历当前文件夹内的所有文件
for entry in os.scandir(path):
# 如果当前对象是文件且后缀是xlsx或者xls
if entry.is_file() and entry.name.endswith('.xlsx'):
is_have_excel = True
print(f'{folder}下的Excel文件路径为:{entry.path}')
# 打开当前文件
wb = load_workbook(entry.path)
# 遍历每一个Sheet
for sheet_name in wb.sheetnames:
sheet = wb[sheet_name]
# 获取当前列名为货品编码的列序号
code_index = ""
for column in sheet.iter_cols():
if column[0].value == good_code:
code_index = column[0].column
break
if code_index == "":
print(f'列名{good_code}在{entry.path}的文件中不存在!')
break
else:
folder_name_dict[folder] = True
# 遍历Sheet中的所有图片
for image in sheet._images:
# 获取图片中心行数,判断货品编码是哪一个
row_index = (int(((image.anchor._from.row + 1) + (image.anchor.to.row + 1)) / 2))
# 获取当前行的货品编码列的值(取中间值)
code = ""
if code_index != "":
code = str(sheet.cell(row=row_index, column=code_index).value)
# 获取图片格式
img_format = image.format
# 这个if else只是命名规则,不重要
if code not in name_dict:
name_dict[code] = 1
else:
name_dict[code] = name_dict[code] + 1
save_path = f"./images/{code}-{name_dict[code]}.{img_format}"
# 保存
file = open(save_path, "wb")
file.write(image.ref.getvalue())
file.close()
# 压缩图片
compress_and_save_image(save_path)
img_num += 1
break
# 关闭文件对象
wb.close()
except FileNotFoundError:
# 处理文件未找到异常
print(f'{folder}文件夹未找到!')
extract_images(good_code)
except Exception as e:
# 处理其他异常
print("提取图片异常:", e)
if not is_have_excel:
print(f'{folder}文件夹内未找到Excel文件!')
folder_name_dict[folder] = True
# 压缩图片
def compress_and_save_image(image_path):
# 打开原始图片
original_image = Image.open(image_path)
# 检查文件大小,并根据需要进行进一步压缩,压缩到1M
if os.path.getsize(image_path) > 1024 * 1024:
size = os.path.getsize(image_path)
# 压缩到1mb需要压缩的比例(百分比)
quality = math.floor(((1024 * 1024) / size) * 100)
original_image.save(image_path, optimize=True, quality=quality)
original_image.close()
# v1.0:此版本是针对于图片分布不规则的情况,提取图片速度尚且较慢
# v1.1:此版本是针对于图片集中分布在一列的情况,能更快提取图片出来。
# v1.2:此版本解决图片位于Excel边界时存在的问题,只要图片中心行在这一行,就可以匹配相应的国家编码,同时不用去遍历,直接获取图片。
# v1.3:此版本是让用户自己输入指定的文件夹,增加异常交互。
# v1.4:此版本增加了对1MB以上图片的压缩,解决了多图片在同一单元格的问题。
if __name__ == '__main__':
print("开始提取......")
# 创建存放文件夹
create_folder()
# 提取图片
extract_images(False)
# 最后加入输入语句,以阻塞程序的执行
input("按下任意键以关闭程序")