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小工具