Python導出Excel圖表


Python自動化辦公的過程,部分涉及到導出Excel圖表;本篇主要講下使用python代碼將excel中的圖表導出為圖片的開發過程;

Python  版本:

C:\Users>python
Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>

Office版本:

數據准備

在導出圖表前,先准備仿真數據並繪制圖表,這里模仿運維工作的業務指標數據測試:

圖表已經在Excel中繪制:

python導出Excel圖表類

前期准備就緒,網上已有類似的導出Excel圖表類,但是在后面的使用中發現問題,即關鍵函數已在下面代碼中標紅:

 1 import win32com,os
 2 from win32com.client import Dispatch
 3 import pythoncom
 4 '''
 5 啟用win32模塊導出excel的圖表,圖表需要打開加載緩存才能導出
 6 '''
 7 class Pyxlchart(object):
 8     """
 9     This class exports charts in an Excel Spreadsheet to the FileSystem
10     win32com libraries are required.
11     """
12     def __init__(self):
13         '''
14         初始化圖表
15         '''
16         pythoncom.CoInitialize()    #多線程使用win32com調用com組件的時初始化
17         self.WorkbookDirectory = ''  #excel文件所在目錄
18         self.WorkbookFilename = ''  #文件名稱
19         self.GetAllWorkbooks = False  #獲取所有book
20         self.SheetName = ''     #sheet名稱
21         self.ChartName = ''     #導出單張圖表時,指定圖表名稱
22         self.GetAllWorkbookCharts = False
23         self.GetAllWorksheetCharts = True
24         self.ExportPath = ''    #導出的文件路徑
25         self.ImageFilename = '' #導出的圖片名稱
26         self.ReplaceWhiteSpaceChar = '_'
27         self.ImageType = 'jpg'  #定義導出的圖片類型
28     def __del__(self):
29         pass
30     def start_export(self,_visible=False):
31         if self.WorkbookDirectory == '':
32             return "WorkbookDirectory not set"
33         else:
34             self._export(_visible)
35     def _export(self,_visible=False): 36         excel = Dispatch("excel.application")
37         #啟用獨立的進程調用excel,Dispatch會強行關閉正在打開的excel
38         #可以使用 DispatchEx為單獨調用線程,不影響已經打開的excel
39         excel = Dispatch("excel.application")
40         excel.Visible = False
41         wb = excel.Workbooks.Open(os.path.join(self.WorkbookDirectory ,self.WorkbookFilename))
42         self._get_Charts_In_Worksheet(wb,self.SheetName,self.ChartName)
43         wb.Close(False)
44         excel.Quit()
45 
46     def _get_Charts_In_Worksheet(self,wb,worksheet = "", chartname = ""):
47         if worksheet != "" and chartname != "":
48             sht = self._change_sheet(wb,worksheet)
49             cht = sht.ChartObjects(chartname)
50 
51             self._save_chart(cht)
52             return
53         if worksheet == "":  #導出表格中所有圖表
54             for sht in wb.Worksheets:
55                 for cht in sht.ChartObjects():
56                     if chartname == "":
57                         self._save_chart(cht)
58                     else:
59                         if chartname == cht.Name:
60                             self._save_chart(cht)
61         else:   #導出指定sheet中的圖標
62             sht = wb.Worksheets(worksheet)
63             for cht in sht.ChartObjects():
64                 if chartname == "":
65                     self._save_chart(cht)
66                 else:
67                     if chartname == cht.Name:
68                         self._save_chart(cht)
69     def _change_sheet(self,wb,worksheet):
70         try:
71             return wb.Worksheets(worksheet)
72         except:
73             raise NameError('Unable to Select Sheet: ' + worksheet + ' in Workbook: ' + wb.Name)
74     def _save_chart(self,chartObject):
75         '''
76         保存圖標到指定路徑
77         :param chartObject: 圖表名稱
78         :return:
79         '''
80         imagename = self._get_filename(chartObject.Name)
81         savepath = os.path.join(self.ExportPath,imagename)
82         #print(savepath)
83 
84         chartObject.Chart.Export(savepath,self.ImageType)
85 
86     def _get_filename(self,chartname):
87         """
88         獲取導出圖表的文件名稱
89         Replaces white space in self.WorkbookFileName with the value given in self.ReplaceWhiteSpaceChar
90         If self.ReplaceWhiteSpaceChar is an empty string then self.WorkBookFileName is left as is
91         """
92         if self.ReplaceWhiteSpaceChar != '':
93             chartname.replace(' ',self.ReplaceWhiteSpaceChar)
94         if self.ImageFilename == '': #未指定導出的圖片名稱,則與圖表名稱一致
95             return chartname + "." + self.ImageType
96         else: #指定了導出圖片的命名格式
97             return self.ImageFilename + "_" + chartname + "." + self.ImageType

 調用代碼:

docPath = r'E:\temp'
if __name__=='__main__':
    Ect = Pyxlchart()
    Ect.WorkbookDirectory = docPath
    Ect.WorkbookFilename = 'Test.xlsx'
    Ect.SheetName = "圖表"   #圖表所在的sheet名稱
    Ect.ExportPath = r'E:\temp\Export_Img'  #圖片的導出路徑
    Ect.start_export()
   print("All Chart is Export!")

執行成功,接下來到上面設置的導出路徑查看導出的圖片:

 E:\temp\Export_Img 的目錄
2018-12-18  11:20                 0 Chart 1.jpg
2018-12-18  11:20                 0 Chart 2.jpg
2018-12-18  11:20            39,583 Chart 3.jpg
2018-12-18  11:20            38,950 Chart 4.jpg
               4 個文件         78,533 字節
E:\temp\Export_Img>

  從文件查看中看到,圖表文件已經成功導出;

圖表導出的問題

  但是,圖表的導出並未能完全成功,從以上文件信息中看到導出的圖片存在0字節的文件;點擊查看圖片可發現提示為空文件

   具體原因分析:

  經過本人多次的測試和探索發現:有效的圖片為Excel的圖表區域顯示頁面,通俗一點的說,即打開excel的圖表所在sheet,當前屏幕顯示了哪些圖表,導出的圖片就正常;在我個人認為可能是Office或Python對Excel的某種緩存功能,實際的緩存范圍大概在當前顯示頁面的150%左右,超出區域的圖表在未加載的情況下,導出成了0字節錯誤文件;

   即使發現了這個BUG,網上搜索也未能找到有效的類似"關閉加載緩存"的技術貼,那么還得根據導出圖表的基礎邏輯解決;

  繼續測試,在Excel的圖表中縮放顯示全部圖片測試,按照測試數據圖表范圍,縮放25%可顯示全部圖表(>_>或者把所有圖表拖動到一個頁面顯示):

 E:\temp\Export_Img 的目錄
2018-12-18  11:20    <DIR>          .
2018-12-18  11:20    <DIR>          ..
2018-12-18  12:11             5,347 Chart 1.jpg
2018-12-18  12:11             5,595 Chart 2.jpg
2018-12-18  12:11             5,764 Chart 3.jpg
2018-12-18  12:11             5,888 Chart 4.jpg
               4 個文件         22,594 字節

  如上述文件查看所示,當圖表所在的sheet頁面顯示了所有圖表時,所有圖表的圖片都成功的導出;

  但是,縮放導出的圖片是根據Excel的圖標實際顯示大小來導出的,所以縮放模式下,導出的圖片大小、清晰度都不能正常使用;

解決方案

  綜上所述,已知Python根據Excel的圖標實際顯示來導出,那么,可以讓Python的導出代碼執行前加載所有正常圖表,在之前的python導出Excel圖表的類中,使用異步方式調用excel.application,即文檔以后台方式導出圖表;

  如果需要完成Excel的所有圖表加載,即必須手動或代碼干預導出過程,在類中已經有代碼可以設置文檔可見;

excel.Visible = True    #設置導出Excel是否可見,當值為True時,可見打開的Excel

  修改原代碼:

def _export(self):
        excel = Dispatch("excel.application")
        # 啟用獨立的進程調用excel,Dispatch會強行關閉正在打開的excel
        # 可以使用 DispatchEx為單獨調用線程,不影響已經打開的excel
        excel.Visible = True
        wb = excel.Workbooks.Open(os.path.join(self.WorkbookDirectory, self.WorkbookFilename))  
        #如需干預Excel圖表導出,需要在文檔打開到開始導出之間,加載完所有圖表
        self._get_Charts_In_Worksheet(wb, self.SheetName, self.ChartName)
        wb.Close(False)
        excel.Quit()

  解決方式顯而易見,過程不多做描述;個人是使用win32api、win32con模塊模擬鍵盤操作加載所有圖表,因無法確認圖表sheet所在的位置,需提前將圖表所在的sheet設置在Excel文檔的最后;或者可根據實際情況,由代碼完成所有sheet的加載操作(比如多按幾下pagadown翻頁,屏幕識別判定內容范圍等....)

   完整代碼如下:

  1 import os,time,sys
  2 import win32api
  3 import win32con
  4 from win32com.client import Dispatch
  5 import pythoncom
  6 '''
  7 啟用win32模塊導出excel的圖表,圖表需要打開加載緩存才能導出
  8 '''
  9 class Pyxlchart(object):
 10     """
 11     This class exports charts in an Excel Spreadsheet to the FileSystem
 12     win32com libraries are required.
 13     """
 14     def __init__(self):
 15         '''
 16         初始化圖表
 17         '''
 18         pythoncom.CoInitialize()
 19         self.WorkbookDirectory = ''  #excel文件所在目錄
 20         self.WorkbookFilename = ''  #文件名稱
 21         self.GetAllWorkbooks = False  #獲取所有book
 22         self.SheetName = ''     #sheet名稱
 23         self.ChartName = ''     #導出單張圖表時,指定圖表名稱
 24         self.GetAllWorkbookCharts = False
 25         self.GetAllWorksheetCharts = True
 26         self.ExportPath = ''    #導出的文件路徑
 27         self.ImageFilename = '' #導出的圖片名稱
 28         self.ReplaceWhiteSpaceChar = '_'
 29         self.ImageType = 'jpg'
 30     def __del__(self):
 31         pass
 32     def start_export(self,_visible=True):
 33         if self.WorkbookDirectory == '':
 34             return "WorkbookDirectory not set"
 35         else:
 36             self._export(_visible)
 37     def _export(self,_visible):
 38         """
 39         Exports Charts as determined by the settings in class variabels.
 40         """
 41         excel = Dispatch("excel.application")
 42         #啟用獨立的進程調用excel,Dispatch容易沖突【會強行關閉正在打開的excel】
 43         #使用 DispatchEx為單獨調用線程,不影響已經打開的excel
 44 
 45         excel.Visible = _visible
 46         wb = excel.Workbooks.Open(os.path.join(self.WorkbookDirectory ,self.WorkbookFilename))
 47 
 48         time.sleep(5)  # 等5秒等待進程打開加載文檔
 49         # 使用打開excel的方式,則模擬鍵盤事件觸發加載所有圖表
 50         if excel.Visible == 1 or excel.Visible == True:
 51             win32api.keybd_event(17, 0, 0, 0)  # 鍵盤按下  ctrl鍵
 52             time.sleep(1)
 53             for k in range(4):
 54                 win32api.keybd_event(34, 0, 0, 0)  # ctrl+pageDown的組合會跳轉sheet,20次跳轉可以到最后的圖表
 55             win32api.keybd_event(36, 0, 0, 0)  # 鍵盤按下  home鍵,和上個按鍵形成組合鍵,回到第一行開頭
 56             win32api.keybd_event(17, 0, win32con.KEYEVENTF_KEYUP, 0)
 57             win32api.keybd_event(36, 0, win32con.KEYEVENTF_KEYUP, 0)
 58 
 59             # 當表格過大時,只能保存到頁面顯示的圖標,故此需要先循環翻頁將所有圖片加載
 60             for i in range(15):  # 翻頁加載所有圖表
 61                 win32api.keybd_event(34, 0, 0, 0)  # 每次讀取之后翻頁
 62                 win32api.keybd_event(34, 0, win32con.KEYEVENTF_KEYUP, 0)
 63                 time.sleep(0.5)
 64 
 65         #圖片加載完成,好了,導出圖片繼續進行
 66         self._get_Charts_In_Worksheet(wb,self.SheetName,self.ChartName)
 67         wb.Close(True)
 68         excel.Quit()
 69 
 70 
 71     def _get_Charts_In_Worksheet(self,wb,worksheet = "", chartname = ""):
 72         if worksheet != "" and chartname != "":
 73             sht = self._change_sheet(wb,worksheet)
 74             cht = sht.ChartObjects(chartname)
 75 
 76             self._save_chart(cht)
 77             return
 78         if worksheet == "":  #導出表格中所有圖表
 79             for sht in wb.Worksheets:
 80                 for cht in sht.ChartObjects():
 81                     if chartname == "":
 82                         self._save_chart(cht)
 83                     else:
 84                         if chartname == cht.Name:
 85                             self._save_chart(cht)
 86         else:   #導出指定sheet中的圖標
 87             sht = wb.Worksheets(worksheet)
 88             for cht in sht.ChartObjects():
 89                 if chartname == "":
 90                     self._save_chart(cht)
 91                 else:
 92                     if chartname == cht.Name:
 93                         self._save_chart(cht)
 94     def _change_sheet(self,wb,worksheet):
 95         try:
 96             return wb.Worksheets(worksheet)
 97         except:
 98             raise NameError('Unable to Select Sheet: ' + worksheet + ' in Workbook: ' + wb.Name)
 99     def _save_chart(self,chartObject):
100         '''
101         保存圖標到指定路徑
102         :param chartObject: 圖表名稱
103         :return:
104         '''
105         imagename = self._get_filename(chartObject.Name)
106         savepath = os.path.join(self.ExportPath,imagename)
107         #print(savepath)
108 
109         chartObject.Chart.Export(savepath,self.ImageType)
110 
111     def _get_filename(self,chartname):
112         """
113         獲取導出圖表的文件名稱
114         Replaces white space in self.WorkbookFileName with the value given in self.ReplaceWhiteSpaceChar
115         If self.ReplaceWhiteSpaceChar is an empty string then self.WorkBookFileName is left as is
116         """
117         if self.ReplaceWhiteSpaceChar != '':
118             chartname.replace(' ',self.ReplaceWhiteSpaceChar)
119         if self.ImageFilename == '': #未指定導出的圖片名稱,則與圖表名稱一致
120             return chartname + "." + self.ImageType
121         else: #指定了導出圖片的命名格式
122             return self.ImageFilename + "_" + chartname + "." + self.ImageType
123 
124 
125 docPath = r'E:\temp'
126 if __name__=='__main__':
127     Ect = Pyxlchart()
128     Ect.WorkbookDirectory = docPath
129     Ect.WorkbookFilename = 'Test.xlsx'
130     Ect.SheetName = "圖表"   #圖表所在的sheet名稱
131     Ect.ExportPath = r'E:\temp\Export_Img'  #圖片的導出路徑
132     Ect.start_export()
View Code

  執行,再次查看執行結果;

 E:\temp\Export_Img 的目錄
2018-12-18  11:20    <DIR>          .
2018-12-18  11:20    <DIR>          ..
2018-12-18  12:39            40,649 Chart 1.jpg
2018-12-18  12:39            41,048 Chart 2.jpg
2018-12-18  12:39            45,048 Chart 3.jpg
2018-12-18  12:39            44,672 Chart 4.jpg
               4 個文件        171,417 字節

  如上所示,文件的字節大小明顯較比縮放導出模式大;到文件目錄中雙擊圖片查看,本次導出的圖片大小、清晰度均正常

  總結

   從python導出Excel的圖表來說,這一塊的功能比較適用用單個圖表的導出操作,如果涉及到大量的批量的圖表導出,這種導出方式不太友好;實際工作如果涉及到批量的簡單圖表制作,重復度較高的工作性質可以由 matplotlib 模塊自己繪制圖表;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM