この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
データアナリティクス事業本部のkobayashiです。
今は昔アーカイバとして頻繁に使われていましたLZHファイルですが、脆弱性の問題から使用が非推奨となり現在ではほぼ使用されていないです。ただ今回LZHのファイルをPythonで展開する機会がありPythonでLZHファイルを扱う方法を調べましたのでその内容をまとめます。
環境
- macOS 10.15.7
- Python 3.7.12
PythonでのLZH解凍方法
LZHは現在ほぼ使われていない圧縮形式のため展開する手段もあまり見つからなかったので当初はlhaをインストールしてsubprocessから使う方法を考えていましたが継続して調査していたところ Lhafile というPythonのモジュールが一つだけ見つかりました。
上記の様な経緯から以下の二つの方法を紹介したいと思います。
- subprocessからlhaを呼び出す
- Lhafileモジュールで操作する
なお今回扱うLZHファイルの中身ですが以下の様な郵便番号のCSVファイルが圧縮されているファイルとなります。
zipcode.lzh
├── 08IBARAK.CSV
├── 09TOCHIG.CSV
├── 10GUMMA.CSV
├── 11SAITAM.CSV
├── 12CHIBA.CSV
├── 13TOKYO.CSV
└── 14KANAGA.CSV
subprocessからlhaを呼び出す
Pythonのsubprocessを使う方法は弊社ブログに詳しい記事がありますのでそちらをご確認ください。
subprocessからlhaを呼び出す方法ですが、lhaをインストールする必要がありますのでbrewではじめにインストールしておきます。またバージョンとオプションも確認しておきます。
$ brew install lha
$ lha --version
LHa for UNIX version 1.14i-ac20050924p1 (i686-apple-darwin19.6.0)
$ lha --help
LHarc for UNIX V 1.02 Copyright(C) 1989 Y.Tagawa
LHx for MSDOS V C2.01 Copyright(C) 1990 H.Yoshizaki
LHx(arc) for OSK V 2.01 Modified 1990 Momozou
LHa for UNIX V 1.00 Copyright(C) 1992 Masaru Oki
LHa for UNIX V 1.14 Modified 1995 Nobutaka Watazaki
LHa for UNIX V 1.14i Modified 2000 Tsugio Okamoto
Autoconfiscated 2001-2005 Koji Arai
usage: lha [-]<commands>[<options>] [-<options> ...] archive_file [file...]
commands: [axelvudmcpt]
options: [q[012]vnfto[567]dizg012e[w=<dir>|x=<pattern>]]
long options: --system-kanji-code={euc,sjis,utf8,cap}
--archive-kanji-code={euc,sjis,utf8,cap}
--extract-broken-archive
--help
--version
commands: options:
a Add(or replace) to archive q{num} quiet (num:quiet mode)
x,e EXtract from archive v verbose
l,v List / Verbose List n not execute
u Update newer files to archive f force (over write at extract)
d Delete from archive t FILES are TEXT file
m Move to archive (means 'ad') o[567] compression method (a/u/c)
c re-Construct new archive d delete FILES after (a/u/c)
p Print to STDOUT from archive i ignore directory path (x/e)
t Test file CRC in archive z files not compress (a/u/c)
g Generic format (for compatibility)
or not convert case when extracting
0/1/2 header level (a/u/c)
e TEXT code convert from/to EUC
w=<dir> specify extract directory (x/e)
x=<pattern> eXclude files (a/u/c)
PythonでLZHを展開したいのですが、その際に「上書き」と「サブディレクトリに展開」を行いたいので以下のコマンドを使います。
$ lha x -f -w={サブディレクトリ名} {展開するLZHファイル名}
これをsubprocessで呼び出すPythonスクリプトを書くと以下の様になります。
後々にPythonスクリプトでエラーハンドリング等を行いため標準出力、標準エラー出力を取得できるようにしてあります。
import subprocess
from subprocess import PIPE
extract_dir = "zipcodes" # サブディレクトリ名
target_file = "zipcode.lzh" # 展開するLZHファイル名
proc = subprocess.run(
"lha x -f -w={} {}".format(extract_dir, target_file),
shell=True,
stdout=PIPE,
stderr=PIPE,
text=True,
)
print("returncode: {}".format(proc.returncode))
print("stdout: {}".format(proc.stdout))
print("stderr: {}".format(proc.stderr))
実行結果
returncode: 0
stdout:
zipcodes/08IBARAK.CSV - Melting : .........................
zipcodes/08IBARAK.CSV - Melting : ooooooooooooooooooooooooo
zipcodes/08IBARAK.CSV - Melted
zipcodes/09TOCHIG.CSV - Melting : ................................
zipcodes/09TOCHIG.CSV - Melting : oooooooooooooooooooooooooooooooo
zipcodes/09TOCHIG.CSV - Melted
zipcodes/10GUMMA.CSV - Melting : ............................
zipcodes/10GUMMA.CSV - Melting : oooooooooooooooooooooooooooo
zipcodes/10GUMMA.CSV - Melted
zipcodes/11SAITAM.CSV - Melting : ..........................
zipcodes/11SAITAM.CSV - Melting : oooooooooooooooooooooooooo
zipcodes/11SAITAM.CSV - Melted
zipcodes/12CHIBA.CSV - Melting : ...............................
zipcodes/12CHIBA.CSV - Melting : ooooooooooooooooooooooooooooooo
zipcodes/12CHIBA.CSV - Melted
zipcodes/13TOKYO.CSV - Melting : ................................
zipcodes/13TOKYO.CSV - Melting : oooooooooooooooooooooooooooooooo
zipcodes/13TOKYO.CSV - Melted
zipcodes/14KANAGA.CSV - Melting : .......................
zipcodes/14KANAGA.CSV - Melting : ooooooooooooooooooooooo
zipcodes/14KANAGA.CSV - Melted
stderr:
Lhafileモジュールで操作する
次にPythonモジュールの Lhafile を使ってみます。
GitHub - FrodeSolheim/python-lhafile: LHA archive support for Python
$ pip install lhafile
$ pip list |grep lhafile
lhafile 0.3.0
Lhafileの使い方は、lhafile.Lhafile()
でLZHファイルを読み込みます。あとは組み込み関数のopen()
と同様にf.read()
でLZHファイル内のファイルを読み込んで操作します。
import lhafile
import os
f = lhafile.Lhafile("zipcode.lzh")
extract_dir = "zipcodes" # サブディレクトリ名
os.makedirs(extract_dir, exist_ok=True)
for info in f.infolist():
fname = info.filename
with open("{}/{}".format(extract_dir, fname), "wb") as tf:
tf.write(f.read(info.filename))
上記のスクリプトを実行した結果LZHファイルの中身がzipcodes
ディレクトリに展開されます。
zipcodes/
├── 08IBARAK.CSV
├── 09TOCHIG.CSV
├── 10GUMMA.CSV
├── 11SAITAM.CSV
├── 12CHIBA.CSV
├── 13TOKYO.CSV
└── 14KANAGA.CSV
また応用的な使い方として、展開されたファイルの中身を直接pandas.DataFrameとして扱うこともできます。
import lhafile
import pandas as pd
from io import BytesIO
f = lhafile.Lhafile("zipcode.lzh")
for info in f.infolist():
fname = info.filename
df = pd.read_csv(BytesIO(f.read(fname)))
print(df)
実行結果
14101 230 2300000 カナガワケン ヨコハマシツルミク ... 0.1 0.2 0.3 0.4 0.5
0 14101 230 2300033 カナガワケン ヨコハマシツルミク ... 0 1 0 0 0
1 14101 230 2300035 カナガワケン ヨコハマシツルミク ... 0 1 0 0 0
2 14101 230 2300021 カナガワケン ヨコハマシツルミク ... 0 0 0 0 0
3 14101 230 2300024 カナガワケン ヨコハマシツルミク ... 0 0 0 0 0
4 14101 230 2300022 カナガワケン ヨコハマシツルミク ... 0 0 0 0 0
... ... ... ... ... ... ... .. .. .. .. ...
2293 14401 24303 2430308 カナガワケン アイコウグンアイカワマチ ... 0 0 0 0 0
2294 14402 24301 2430100 カナガワケン アイコウグンキヨカワムラ ... 0 0 0 0 0
2295 14402 257 2570061 カナガワケン アイコウグンキヨカワムラ ... 0 0 0 0 0
2296 14402 24301 2430112 カナガワケン アイコウグンキヨカワムラ ... 0 0 0 0 0
2297 14402 24301 2430111 カナガワケン アイコウグンキヨカワムラ ... 0 0 0 0 0
[2298 rows x 15 columns]
まとめ
今ではほぼ見なくなったLZHで圧縮されたファイルをPythonで展開してみました。需要は少ないと思いますがどなたかの参考になれば幸いです。
最後まで読んで頂いてありがとうございました。