Python — zipfile對壓縮檔的操作方式

Sean Yeh
Python Everywhere -from Beginner to Advanced
11 min readSep 27, 2022

--

Great Harbor Bridge, Kaohsiung, Taiwan, photo by Sean Yeh

搬家時,通常都會把要搬運的物品裝箱並打包起來,透過運送公司的卡車將一箱箱事先打包好的箱子運送到指定地點,也就是新的家。當物品到達指定地點後,再將一個個打包的箱子拆箱還原。為了減少運送的體積,可能會將物品中的空氣抽空,讓體積變小。這樣做的好處是,我們可以使用較小體積的箱子來裝箱,同時讓搬運卡車也可以一次多送幾箱,節省運送的費用。

Photo Generated by Midjourney

與搬家的概念一樣,使用ZIP壓縮檔案也會產生類似的效果。在一個ZIP壓縮檔案中,可以包括很多檔案。這些被壓縮的檔案會被打包在一個副檔名為zip的檔案中。除了把檔案包裹再一起外,壓縮的過程也會讓檔案縮小其佔用的空間。整體來說,體積會比原來所有檔案加總起來要來得小。因此非常便於在網路上面傳輸與交換。

壓縮檔案裡面的資料,如果需要被使用,可以將Zip檔案解壓縮(如同包裹拆箱)。檔案一旦被解壓縮之後,裡面的檔案就會被還原回來。即使裡面有子資料夾,也會原封不動的還原回來。

電腦上的壓縮解壓縮

以目前大家使用的電腦來說,其作業系統(Windows或MacOS)中大多內建壓縮與解壓縮的程式。要執行壓縮或解壓縮檔案,可以說是非常的容易。以Mac電腦為例,只要先將要壓縮的檔案或資料夾選取起來,並按下右鍵,就會出現下面的選單,其中會看到「壓縮」的項目,可供我們壓縮檔案。

Python與Zip壓縮

若不想要凡事都「徒手」處理的話,是否可以透過程式來幫我們「大量的」壓縮與解壓縮檔案?

的確,Python連這個也會。真是無所不能!

在Python程式中,有一個zipfile模組,我們可以使用其模組中的函式來撰寫程式壓縮ZIP檔案或解壓縮ZIP檔案。以下針對zipfile模組的使用方式分別說明之。

匯入zipfile模組

要使用zipfile模組模組,必須先匯入zipfile模組。

import zipfile

檢查是否為ZIP 壓縮檔

若要確認某一個檔案是否為ZIP壓縮檔,可以使用zipfile模組的 is_zipfilec函式。

import zipfile# 檔案名稱
file='abc.zip'
# 檢查是否為壓縮檔
print(f'{file}: {zipfile.is_zipfile(file)}')

上面的程式碼會在螢幕上印出壓縮檔案的名稱與是否為壓縮檔(zipfile.is_zipfile(file)),如果是壓縮檔就會印出True,反之會印出False。

讀取ZIP壓縮檔

若要讀取ZIP檔案的內容,必須建立ZipFile物件。我們可以透過ZipFile()函式建立ZipFile物件。呼叫ZipFile()函式時,需要傳入一個副檔名為zip(.zip)的壓縮檔案。

import zipfilepath = '檔案路徑'
ZIP = zipfile.ZipFile(f'{path}/ZIP檔案')

請注意zipfile模組與函式兩者的書寫方式。作為模組名稱時,zipfile全部為小寫,而當zipfile作為函式時,要寫成ZipFile,其中Z與F都是大寫。

如果把上面的ZIP印出來的話,會得到下面的結果:

<zipfile.ZipFile filename=’file/demo.zip’ mode=’r’>

這是一個物件,路徑為file,檔名為demo.zip以及模式為讀取。

查看壓縮檔內容

ZipFile物件中有個namelist() 函式可以取得壓縮檔案內的所有檔案與資料夾的名稱,這些資訊透過list串列方式返回給我們。如果需要進一步的資料,可以再對串列中的每一個項目使用getinfo()infolist()函式,取得其餘更詳細的資訊。

取得被壓縮的檔名

以下使用 namelist() 函式取得 ZIP 壓縮檔案之中所有檔案名稱。

ZIP = zipfile.ZipFile(f'ZIP檔案.zip')ZIP.namelist()

取得被壓縮檔的詳細資訊

使用infolist()函式取得詳細資訊

ZIP.infolist()

或針對個別檔案使用getinfo()取得資訊,getinfo()裡面需要帶入個別檔案作為參數:

for name in ZIP.namelist():
info = ZIP.getinfo(name)
print(info)

解壓縮ZIP檔

全部解壓縮

ZipFile物件中的extractall()函式可以解壓縮ZIP檔案,並且將解壓縮後的檔案存放在目前的工作資料夾下面。程式碼示範如下:

import zipfile# 檔案名稱
file='abc.zip'
ZIP = zipfile.ZipFile(file)
ZIP.extractall()
ZIP.close()

當執行這段程式碼之後,就會將檔案(例如上面的abc.zip)解壓縮到目前的工作資料夾下面。如果想要將解壓縮的「檔案們」存在與工作資料夾不同的其他位置時,只要需要將新的路徑傳入extractall()函式中即可。例如下面的程式碼,我們將新的路徑(url)傳入extractall()函式中,檔案解壓縮後就會被存到新的file資料夾裡面。這裡需注意的是,新路徑的資料夾必須已經被建立,若指定一個尚未建立的資料夾,就會出現錯誤。

import zipfile# 檔案名稱
file='abc.zip'
url = '/file/'
ZIP = zipfile.ZipFile(file)
ZIP.extractall(url)
ZIP.colse()

局部解壓縮

如果不想要將所有已壓縮的檔案全部解壓縮出來,只想從中取出單獨的一個檔案來解壓縮時,可以改用extrat()函式。例如:我們想要單獨將ziped_file壓縮檔案裡面符合副檔名為csv的檔案單獨解壓縮時,可以使用下面的方式處理。為了方便起見,這裡使用了with 來建立一個zipfile物件:

import zipfile# 檔案名稱
ziped_file='abc.zip'
with zipfile.ZipFile(f'{ziped_file}','r') as ZIP:
list_files = ZIP.namelist()
for file_name in list_files:
if file_name.endswith('.csv'):
ZIP.extract(file_name)

同理可知,使用for迴圈加上extrat()函式,也可以將壓縮檔裡面的所有檔案解壓縮出來。

import zipfile# 檔案名稱
ziped_file='abc.zip'
ZIP = zipfile.ZipFile(ziped_file)for name in ZIP.namelist():
ZIP.extract(name)
ZIP.colse()

或使用with的語法來進行,如此就不需要在程式碼最後面使用close關閉:

import zipfile# 檔案名稱
file='abc.zip'
with zipfile.ZipFile(file, 'r') as zf:
for name in zf.namelist():
zf.extract(name)

建立ZIP壓縮檔

ZIP壓縮檔的建立,一樣需要先有ZipFile物件。這裡與前面較為不一樣的地方是,ZipFile物件必須是以「寫入」模式開啟。因此我們除了傳入檔案給物件外,尚且需要傳入 w 作為參數。

import zipfile# 建立壓縮檔案
with zipfile.ZipFile('abc.zip', mode='w') as zf:
...略...

ZipFile物件建立後,即可將檔案加入壓縮檔打包。此時需使用 write() 函式並添加要加入的檔案名稱。

以上面的程式碼來說,我們可以在with後面加上:

import zipfile# 建立壓縮檔案
with zipfile.ZipFile('abc.zip', mode='w') as zf:
# 將要壓縮的檔案加入
zf.write("file_a.pdf")
zf.write("file_b.pdf")
zf.write("file_c.pdf")

此外,我們也可以對 write() 加上 compress_type 參數來指定壓縮類型方式。這個參數會告訴電腦要以哪一種演算法來對檔案進行壓縮。以下面例子中的 ZIP_DEFLATED 來說,是一種比較常見的壓縮方式。

zf.write("file_a.pdf", compress_type=zipfile.ZIP_DEFLATED)

添加檔案到既有ZIP壓縮檔中

如前面的程式碼,我們使用mode='w'的方式將檔案寫入壓縮檔案中。這樣子的模式會覆蓋並刪掉原本在ZIP檔案中的內容。如果您希望的是將某的檔案「添加」到既有的ZIP壓縮檔之中,就需要將模式改為 a(亦即 mode='a' )。這樣子才會用「添加」的方式將檔案加入ZIP中。

import zipfile# 建立壓縮檔案
with zipfile.ZipFile('zipfile.zip', mode='a') as zf:
# 將要添加的檔案加入zipfile.zip壓縮檔中
zf.write("file_d.pdf")

實作:解壓縮程式碼

接下來我們就可以透過上面的說明,來實際撰寫一個解壓縮檔案程式。只要將想要解壓縮的檔案放在指定的source資料夾中,執行該程式碼,就會自動將程式解壓縮完成。以下是本次實作程式碼的說明:

1. 特定的壓縮檔位置

這裡設定一開始都將壓縮檔放在工作目錄中的source資料夾。

# 工作目錄
project_dir = '/Users/to/prokect_path'
#壓縮檔案位置
src_file = f'{project_dir}/source/{zip_file_name}'
#暫時資料夾
temp_des_dir = f'{project_dir}/temp_des/'
des_dir = f'{project_dir}/des/'

2. 避免中文亂碼

我們在先前的解壓縮說明中,並沒有將「非英數檔名」壓縮檔而解壓縮時所產生的亂碼考慮在內。在此我們透過encode與decode的方式( .encode('cp437').decode('big5')),為檔名重新給定正確編碼(big5)的名稱。

3. 刪除暫存檔

另外,在這裡使用了暫存資料夾儲存程式執行中間的壓縮檔案,當整個解壓縮過程完畢之後,我們即透過shutil套件裡面的rmtree順便把暫存區的壓縮檔案給刪除。

以下為實作解壓縮檔的完整程式碼:

from pathlib import Path
from zipfile import ZipFile
import shutil, os
zip_file_name='zipedfile.zip'
# 專案路徑
project_dir = '/Users/to/prokect_path'
temp_des_dir = f'{project_dir}/temp_des/'
des_dir = f'{project_dir}/des/'
src_file = f'{project_dir}/source/{zip_file_name}'
# zip物件
with ZipFile(src_file,'r') as zf:
for file_name in zf.namelist():
print(file_name)
unzip = zf.extract(file_name, temp_des_dir)

extracted_path = Path(unzip)
#判斷Des資料夾是否存在
isExist = os.path.exists(des_dir)
if not isExist:
os.mkdir(des_dir)
print("建立Des資料夾")

new_path = Path(des_dir, file_name.encode('cp437').decode('big5'))
rename_path = extracted_path.rename(new_path)
try:
print('解壓縮完成...刪除暫存資料夾')
# remove temp_des_dir
shutil.rmtree(Path(temp_des_dir))
except Exception as e:
raise e

結語

Python的zipfile模組可以用來壓縮ZIP檔案以及解壓縮ZIP檔案。如果能夠熟悉這類型的模組,就可以透過它們的使用拼組成一些自動化的系統。在各方面幫助我們,節省日常生活中不必要的瑣碎工作。

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。