日記

日々のことと、Python/Django/PHP/Laravel/nodejs などソフトウェア開発のことを書き綴ります

zipfile を使って ZIP ファイルを生成する

データを圧縮するときに ZIP 圧縮をプログラムから行う事は、遭遇することが割と多いと思います。

普通に圧縮だけをしてもつまんないサンプルになってしまうので、仕事でちょっとしたチューニングしたことを織り交ぜつつ、 Python の ZIP 圧縮をしてみたいと思います。

とりあえず圧縮してみる

一番簡単なやり方ということで、ファイルを ZIP 圧縮して出力してみます。

from zipfile import ZipFile, ZIP_DEFLATED

output_file = ZipFile('./archive_file.zip', 'w', ZIP_DEFLATED)
output_file.write('./target.txt', 'data/target.txt')
output_file.close()

このサンプルでは、archive_file.zipというZIPファイルを作成して、その中にtarget.txtを dataディレクトリの下に配置する形で圧縮をしています。

ファイルの場合は、ZipFile#writeを使っていますが、Pythonで定義した文字列を書き込みたい場合は、writestrという別のメソッドがあり、それを使います。

ZIPには含めるけど、圧縮はしない例

GIF/JPEG/PNGなどの画像をZIPにする場合は、どうでしょう? バイナリデータということもあり圧縮率が悪い上にデータが大きくて圧縮処理に時間が掛かります。そんなときは、ZIPファイルに入れるけど、圧縮しないという方法もあります。丁度、tarファイルみたい形ですね。

from zipfile import ZipFile, ZIP_STORED

output_file = ZipFile('./archive_file.zip', 'w', ZIP_STORED)
output_file.write('./target.jpg', 'data/target.jpg')
output_file.close()

手抜きにしか見えないですが、DEFLATEDがSTOREDになっただけ!
これだけですが、圧縮処理は行わずにZIPにJPEGファイルがアーカイブされます。

テキストとバイナリで圧縮処理を振り分けてZIPファイルにしたい

今回のメインはこれです。

じゃ、テキストとバイナリが混ざっているときは、圧縮するしないを切り替えたいけど、どうしたらいいの??
実は処理速度が遅すぎて、ちょっと問題になったので、解決方法が無いのか探してみました。

テキストファイルだけ先に ZIPファイルにして、それを無圧縮で最後に1つにまとめる?? いやいや、それは要件的に無理。自分だったら ZIP ファイルというコンテナを作るときに圧縮の有無をファイル単位で切り替えられるように設計する。だから、きっとできるはず。

ということで、ZIPファイルのフォーマットを調べてみました。(see WikipediaのZIPページ)

むむむ、どうやらファイル単位でヘッダーを持っていて、そこで圧縮方式(圧縮するしないなど)を持っている。やっぱりねー。じゃ、Pythonのzipfileライブラリでは、そこを制御した処理はできないの?と思ってマニュアルを見てみたところ

ZipFile.write(filename[, arcname[, compress_type]])

となっていて、ファイルを指定するときに圧縮方式も指定できるようです。ただ、ここで指定した圧縮方式は、ZipFileのオブジェクトを作る時のコンストラクタで渡した圧縮方式は「上書き」してしまうこと。そのため、同じアーカイブファイルで圧縮方式を切り替えるときは、毎回設定したほうが無難な感じです。

ファイルからアーカイブするときは良いのですが、writestrを使う場合は

ZipFile.writestr(zinfo_or_arcname, bytes)

となっていて、compress_typeがありません。んじゃ、どうやって圧縮方式を設定するのかと言うと、

from zipfile import ZipFile, ZipInfo, ZIP_DELATED, ZIP_STORED
from datetime import datetime

str_info = ZipFile()
current_time = datetime.now()

str_info.filename = 'data/str.txt'
str_info.compress_type = ZIPDEFLATED
str_info.date_time = (current_time.year, current_time.month, current_time.day, current_time.hour, current_time.minute, current_time.second)

output_file = ZipFile('test.zip','w')
output_file.write('./test.txt', './data/text.txt', ZIP_DEFLATED)
output_file.write('./sample.jpg', './data/sample.jpg', ZIP_STORED)
output_file.writestr(str_info, 'zip file archive')
output_file.close()

ZipInfoというクラスがポイントで、このクラスがZIPファイル内のファイルヘッダを表現しています。

まとめ

プログラムからZIPファイルを生成する場合の多くは、どんなデータがZIPファイルに入るのか、予め決まっていることが多いと思います。その状況を利用して、圧縮の有無を切り替えることで、処理速度の最適化が行えると思います。特に大量のデータをさばかなければいけないときや、バイナリデータを含むZIPを生成する必要がある時は、効果的な処理方法かもしれません。