python+xlsxwriter+PIL自动压图贴图到Excel小工具

Posted medivhxu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python+xlsxwriter+PIL自动压图贴图到Excel小工具相关的知识,希望对你有一定的参考价值。

一、环境

    windows10/mac + python3.6

    python第三方库 xlsxwriter、PIL、argparse

二、需求

    1、运行每条测试case成功与否都需要把截图放在img文件夹里;

    2、把 (平台)_img 文件夹中的图片压缩写到small_img文件夹里;

    3、根据图片命名规则,顺序写入所属case number对应行,顺序写入每条case所有截图;

    4、根据平台来贴到excel最合适的位置;

    5、最重要一点,是给媳妇写的,提升工作效率;

三、文件树示例

    技术分享图片

三、Paste_pictures.py

#!/usr/bin/env python3
# coding=utf-8


import xlsxwriter
import datetime
import os
import logging

LOGGER = logging.getLogger(__name__)


def write_img_for_xls(file_name="test.xlsx", img_dir_name="img", sheet_name="案例截图",
                      set_img_row=210.75, set_img_column=23.38, x_scale=0.14, y_scale=0.14, ):
    """
    读取同目录img文件夹下的所有图片,格式支持png\jpg\bmp。
    图片必须遵从  1-1.png、1-2.png、2-1.png、2-2.png格式。
    注意:是将图片写入新的excel文件,如果老的excel中有数据,将会替换所有数据。
    
    file_name: 要写入的xlsx文件名
    img_dir_name: 图片存放的目录,必须与脚本在同一目录下
    set_img_row:设置行高
    set_img_column:设置列宽
    x_scale:图片宽度缩放比例
    y_scale:图片高度缩放比例
    
    :return: Nothing
    """
    start_time = datetime.datetime.now()

    xls_path = os.path.join(os.getcwd(), file_name)
    if not os.path.isfile(xls_path):
        raise MyException("what?你居然不把{}文件跟脚本放在同一个目录下!".format(file_name))

    img_path = os.path.join(os.getcwd(), img_dir_name)
    if not os.path.isdir(img_path):
        raise MyException("what?你都没有{}文件夹,我咋给你贴图啊~".format(img_dir_name))

    all_img = os.listdir(img_path)
    if not all_img:
        raise MyException("忽悠我呢?{}文件夹下啥都没有~".format(img_dir_name))

    w_book = xlsxwriter.Workbook(xls_path)
    img_sheet = w_book.add_worksheet(name=sheet_name)
    count_num = 0

    try:
        for unit_img in all_img:
            try:
                img_num = unit_img.split("-")
                row = int(img_num[0])
                column = int(img_num[1].split(".")[0])
                suffix = (unit_img.split(".")[1])
                if column == 0:
                    LOGGER.warning("图片名称格式有误直接略过!错误文件名:{},“-”前后数字必须从1开始!".format(unit_img))
                    continue
            except ValueError:
                LOGGER.warning("[-]图片命名规则有误直接略过!错误文件名是:{}".format(unit_img))
                continue

            LOGGER.info(">> 正在贴图到第{}条用例,第{}列...".format(row, column))
            img_sheet.set_column(firstcol=0, lastcol=0, width=8.38)
            img_sheet.write(row - 1, 0, "案例{}".format(row))

            small_img = os.path.join(os.getcwd(), "{}/{}-{}.{}".format(img_dir_name, row, column,
                                                                       suffix))

            img_sheet.set_column(firstcol=column, lastcol=column, width=set_img_column)
            img_sheet.set_row(row=row - 1, height=set_img_row)

            img_config = {
                x_scale: x_scale,
                y_scale: y_scale
            }

            result = img_sheet.insert_image(row - 1, column, small_img, img_config)
            img_sheet.write_url(row - 1, column + 1, url="https://my.oschina.net/medivhxu/blog/1590012")
            if not result:
                LOGGER.info("[+] 写入成功!")
                count_num += 1
            else:
                LOGGER.error("[-] 写入失败!")

    except Exception as e:
        raise MyException("受到不明外力袭击,程序...挂了....\n{}".format(e))
    finally:
        try:
            w_book.close()
        except PermissionError:
            raise MyException("你开着{}文件我让我咋写。。。赶紧关了!".format(file_name))
    LOGGER.info("--------------贴图完成--------------")
    LOGGER.info("程序贴图数量:{},贴图成功数量:{},贴图失败数量:{}".format(len(all_img), count_num, len(all_img) - count_num))


class MyException(Exception):
    pass


write_img_for_xls()

 

技术分享图片技术分享图片

四、run

技术分享图片

五、result

技术分享图片

价值:

    手动贴图需要半小时?1小时?贴错了?不,这些我都不要,仅需不到5秒,全部搞定,然后就可以干点想干的事~

性能分析:

    其实贴图并不需要4秒+,因为xlsxwriter这个模块是自动创建cell的,但是这不是主要原因,主要原因是因为图片太大了,所以保存时间会随着图片加载到内存而线性增长(图片较大或过多,容易导致脚本直接崩溃),优化方式是选用openpyxl模块和最中要的图片预处理。

六、PIL图片压缩

    1、code

#!/usr/bin/env python3
# coding=utf-8

import os
from PIL import Image


def compression_img(read_dir_name="img", save_img_file=True, height_shrink_multiple=2, width_shrink_multiple=2):
    ‘‘‘
    自动压缩指定文件夹下的所有图片
    
    :param read_dir_name: 指定读取图片的文件夹名称,必须在当前目录下
    :param save_img_file: 是否保存压缩后的图片
    :param height_shrink_multiple: 设置图片压缩高度的倍数
    :param width_shrink_multiple: 设置图片压缩宽度的倍数
    :return: nothing
    ‘‘‘

    img_path = os.path.join(os.getcwd(), read_dir_name)
    all_img = os.listdir(img_path)
    for unit_img in all_img:
        try:
            img_num = unit_img.split("-")
            row = int(img_num[0])
            column = int(img_num[1].split(".")[0])
            suffix = (unit_img.split(".")[1])
            if column == 0:
                print("图片名称格式有误直接略过!错误文件名:{},“-”前后数字必须从1开始!".format(unit_img))
                continue
        except ValueError:
            print("[-]图片命名规则有误直接略过!请参考1-1.png格式从新运行或手动解决!")
            print("错误文件名是:{}".format(unit_img))
            continue

        img_fp = os.path.join(img_path, unit_img)
        origin_img = Image.open(img_fp)
        w, h = origin_img.size
        small_img = origin_img.resize((int(w / height_shrink_multiple), int(h / width_shrink_multiple)))
        if save_img_file:
            img_save_fp = os.path.join(os.getcwd(), "small_img")
            if os.path.isdir(os.path.join(os.getcwd(), "small_img")):
                print("warning, 已有small_img文件夹!直接保存到里面了!")
                small_img.save(os.path.join(img_save_fp, ("{}-{}.{}".format(row, column, suffix))))
            else:
                os.mkdir("small_img")
                print("新建文件夹“small_img”,压缩后的图片将存储在该文件夹中。")
                small_img.save(os.path.join(img_save_fp, ("{}-{}.{}".format(row, column, suffix))))
        print(">> 正在处理图像{}-{}.{},原像素高和宽{},压缩后的高和宽{}".format(row, column, suffix,
                                                            (w, h), small_img.size))
        small_img.close()
    print("--------------图片压缩完成--------------")


compression_img()

 

    2、再次运行Paste_pictures.py

技术分享图片

可以明显看出,保存文件的时间有非常显著的提升。

七、模块化

    把以上两个功能合并,增加平台类型,根据需求增加了3个平台的图片缩放比例和宽高,增加运行log,增加作为主程序命令行运行。以后如果扩展的话直接调用或者写个类,增加几个返回值就可以了。

#!/usr/bin/env python3
# coding=utf-8

import xlsxwriter
import datetime
import os
import argparse
import logging
from PIL import Image

LOGGER = logging.getLogger(__name__)

PLATFORM = {"phone": {
    "x_scale": 0.29,
    "y_scale": 0.29,
    "width": 23.38,
    "height": 210.75
},
    "pad": {
        x_scale: 0.2,
        "y_scale": 0.2,
        "width": 58,
        "height": 230
    },
    "oppo": {
        x_scale: 0.29,
        y_scale: 0.3,
        "width": 22.17,
        "height": 230
    }
}


def _check_os_dir(read_dir_name, small_dir_name="small", xlsx_file_name="test.xlsx"):
    ‘‘‘
    检测img文件夹及文件夹中的内容、xlsx文件是否存在
    
    :param read_dir_name: 要读取图片的文件夹
    :param small_dir_name: 压缩的图片文件夹
    :param xlsx_file_name: excel文件名
    :return: 
            all_img:所有图片对象
            xls_path:excel文件路径
            img_path:图片文件路径
    ‘‘‘

    full_name = read_dir_name + "_img"
    img_path = os.path.join(os.getcwd(), full_name)

    LOGGER.info(img_path)
    assert os.path.isdir(img_path), "what?你都没有{}文件夹,我咋给你贴图啊!!!".format(full_name)

    all_img = os.listdir(img_path)
    assert all_img, "{}文件夹里啥也没有,咋贴!!!".format(full_name)

    xls_path = os.path.join(os.getcwd(), xlsx_file_name)
    assert os.path.isfile(xls_path), "{}文件不存在!!!".format(xlsx_file_name)

    # small_full_name = small_dir_name + datetime.datetime.now().strftime("%Y%m%d%H%M%S")
    if full_name == small_dir_name:
        return all_img, xls_path, img_path

    # os.mkdir("{}".format(small_full_name))
    # LOGGER.warning("新建文件夹{},压缩后的图片将存储在该文件夹中。".format(small_dir_name))

    return all_img, xls_path, img_path


def _compression_img(read_dir_name, small_dir_name="small", height_shrink_multiple=2, width_shrink_multiple=2):
    ‘‘‘
    自动压缩指定文件夹下的所有图片

    :param read_dir_name: 读取图片文件夹的名称
    :param small_dir_name:如果压缩图片就读取该文件夹下的压缩图片
    :param height_shrink_multiple: 设置图片压缩高度的倍数
    :param width_shrink_multiple: 设置图片压缩宽度的倍数
    :return: small_dir_name: 压缩后的图片文件名
    ‘‘‘

    full_small_dir_name = small_dir_name + "_img"
    _check_os_dir(read_dir_name=read_dir_name, small_dir_name=full_small_dir_name)

    img_path = os.path.join(os.getcwd(), read_dir_name + "_img")
    all_img = os.listdir(img_path)
    for unit_img in all_img:
        try:
            img_num = unit_img.split("-")
            row = int(img_num[0])
            column = int(img_num[1].split(".")[0])
            suffix = (unit_img.split(".")[1])
            if column == 0:
                LOGGER.warning("图片名称格式有误直接略过!错误文件名:{},“-”前后数字必须从1开始!".format(unit_img))
                continue
        except ValueError:
            LOGGER.warning("[-]图片命名规则有误直接略过!错误文件名是:{}".format(unit_img))
            continue

        img_fp = os.path.join(img_path, unit_img)
        origin_img = Image.open(img_fp)
        w, h = origin_img.size
        small_img = origin_img.resize((int(w / height_shrink_multiple), int(h / width_shrink_multiple)))

        small_img.save(os.path.join(os.getcwd(), "{}/{}-{}.{}".format(full_small_dir_name, row, column, suffix)))
        LOGGER.info(">> 正在处理图像{}-{}.{},原像素高和宽{},压缩后的高和宽{}".format(row, column, suffix, (w, h), small_img.size))
        try:
            small_img.close()
        except Exception as e:
            LOGGER.debug("未知错误\n{}".format(e))
    LOGGER.info("--------------图片压缩完成--------------")
    return small_dir_name


def write_img_for_xls(platform, read_dir_name, sheet_name="案例截图", xlsx_file_name="test.xlsx"):
    """
    读取同目录img文件夹下的所有图片,格式支持png\jpg\bmp。
    图片必须遵从  1-1.png、1-2.png、2-1.png、2-2.png格式。
    注意:是将图片写入新的excel文件,如果老的excel中有数据,将会替换所有数据。

    platform: 平台名称,包括phone、pad,web目前没实现
    read_dir_name: 要读取图片的文件夹名称
    xlsx_file_name: 要写入的xlsx文件名
    sheet_name: 写入excel中sheet的名字

    :return: nothing
    """
    all_img, xls_path, img_path = _check_os_dir(xlsx_file_name=xlsx_file_name, read_dir_name=read_dir_name)

    w_book = xlsxwriter.Workbook(xls_path)
    img_sheet = w_book.add_worksheet(name=sheet_name)
    count_num = 0

    try:
        for unit_img in all_img:
            try:
                img_num = unit_img.split("-")
                row = int(img_num[0])
                column = int(img_num[1].split(".")[0])
                suffix = (unit_img.split(".")[1])
                if column == 0:
                    LOGGER.warning("图片名称格式有误直接略过!错误文件名:{},“-”前后数字必须从1开始!".format(unit_img))
                    continue
            except ValueError:
                LOGGER.warning("[-]图片命名规则有误直接略过!错误文件名是:{}".format(unit_img))
                continue

            LOGGER.info(">> 正在贴图到第{}条用例,第{}列...".format(row, column))
            img_sheet.set_column(firstcol=0, lastcol=0, width=8.38)
            img_sheet.write(row - 1, 0, "案例{}".format(row))

            small_img = os.path.join(os.getcwd(), "{}/{}-{}.{}".format(read_dir_name+"_img", row, column,
                                                                       suffix))

            img_sheet.set_column(firstcol=column, lastcol=column, width=PLATFORM.get(platform).get("width"))
            img_sheet.set_row(row=row - 1, height=PLATFORM.get(platform).get("height"))

            x_ = PLATFORM.get(platform).get("x_scale")
            y_ = PLATFORM.get(platform).get("y_scale")
            img_config = {"x_scale": x_, "y_scale": y_}

            result = img_sheet.insert_image(row - 1, column, small_img, img_config)
            img_sheet.write_url(row - 1, column + 1, url="https://my.oschina.net/medivhxu/blog/1590012")
            if not result:
                LOGGER.info("[+] 写入成功!")
                count_num += 1
            else:
                LOGGER.error("[-] 写入失败!")

    except Exception as e:
        raise MyException("受到不明外力袭击,程序...挂了....\n{}".format(e))
    finally:
        try:
            w_book.close()
        except PermissionError:
            raise MyException("你开着{}文件我让我咋写。。。赶紧关了!".format(xlsx_file_name))
    LOGGER.info("--------------贴图完成--------------")
    LOGGER.info("程序贴图数量:{},贴图成功数量:{},贴图失败数量:{}".format(len(all_img), count_num, len(all_img) - count_num))


class MyException(Exception):
    pass


if __name__ == __main__:
    parser = argparse.ArgumentParser()
    parser.add_argument("-p", help="必须选择平台phone、pad、oppo")
    group = parser.add_mutually_exclusive_group()
    group.add_argument("-a", action="store_true", help="压缩图片且贴图到excel")
    group.add_argument("-w", action="store_true", help="直接贴图到excel")

    args = parser.parse_args()

    logging.basicConfig(level=logging.DEBUG,
                        format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s,
                        datefmt=%a, %d %b %Y %H:%M:%S,
                        filename=Paste_pictures_{}.log.format(datetime.datetime.now().strftime("%Y%m%d%H%M%S")),
                        filemode=w)

    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    formatter = logging.Formatter(%(name)-12s: %(levelname)-8s %(message)s)
    console.setFormatter(formatter)
    logging.getLogger(‘‘).addHandler(console)

    if args.p not in PLATFORM.keys():
        raise MyException("参数错误,必须在{}中选择".format(PLATFORM.keys()))
    if args.a:
        LOGGER.info(">>> 选择参数-a,即压缩图片,又贴图。")
        r_small_dir_name = _compression_img(read_dir_name=args.p, small_dir_name="small")
        write_img_for_xls(platform=args.p, read_dir_name=r_small_dir_name)
    elif args.w:
        LOGGER.info(">>> 选择参数-w,只贴图。")
        write_img_for_xls(platform=args.p, read_dir_name=args.p)
    else:
        LOGGER.error("参数错误")

 

技术分享图片技术分享图片

windows command和linux/mac terminal下运行效果:

 

技术分享图片

The end~

以上是关于python+xlsxwriter+PIL自动压图贴图到Excel小工具的主要内容,如果未能解决你的问题,请参考以下文章

python模块XlsxWriter

python模块之XlsxWriter

python学习-xlsxwriter模块

Python+Selenium进行UI自动化测试项目中,常用的小技巧3:写入excel表(python,xlsxwriter)

python模块之XlsxWriter 详解

python PIL图片自动旋转