zipを展開せずに直接読み込む方法【Python, XBRL】

Pythonでzipを解凍せずに直接読み込む方法です。

EDINETもTDnetもEDGARも、XBRLは『zipファイル』で公開されています。

zipファイルの中身を読み込むときは、いったんHDDかSSDに解凍(展開)するのが一般的ですが、zipから直接読み込むこともできます。

その方法を紹介します。

zipの中身をSSDとかに解凍しなくていいので、大量のXBRLを読み込むときに省スペースで済みます。とても便利です。

標準の zipfile モジュールを使う

Pythonでzipファイルを読み込むには、Python標準の zipfile モジュールを使います。

これでzipを開くと、中身のファイルリストを取得するメソッド「.infolist()」と、データを読み込むメソッド「.read()」が使用できます。これらを使います。

zipfile モジュールのマニュアル

zipfile モジュールのマニュアルへのリンクです。

zipファイルを開く
class zipfile.ZipFile(file, mode='r', compression=ZIP_STORED, allowZip64=True)

中身のファイルリストを取得
ZipFile.infolist()

名前を指定して読み込む
ZipFile.read(name, pwd=None)

zipを読み込む

Pythonでzipを読み込むコード例です。

zipを解凍せずに中身を見るには、zipfileモジュールのZipFile()を使います。

読み込み手順

拡張子「.xbrl」のファイルだけを読み込んで辞書に入れる例です。

zipfile.ZipFile()でzipファイルを開いてから、zip_data.infolist()でファイルリストを取得します。

ファイルリストの要素には info.filename というメンバ変数がありますので、これをzip_data.read()に渡すとデータが読み込めます。

info.filename は、実際には以下のように「フォルダ構造を含んだファイルパス」です。

'S100AM8K/XBRL/PublicDoc/jpcrp030000-asr-001_E00518-000_2017-03-31_01_2017-06-26.xbrl'

なので、ファイル名を使って読み込む・読み込まないを判定するときは、このファイルパスを意識して判定部分を作ることになります。

スポンサーリンク

コード例

まず、zipfile モジュールを使う部分です。

import zipfile
from collections import OrderedDict
import traceback
def read_file_from_zip(zip_file, re_match):
    """zipからファイル名を指定して読み込む"""
    file_datas = OrderedDict()

    try:
        with zipfile.ZipFile(zip_file, 'r') as zip_data:
            # ファイルリスト取得
            infos = zip_data.infolist()

            for info in infos:
                # ファイルパスでスキップ判定
                if re_match(info.filename) is None:
                    continue

                # zipからファイルデータを読み込む
                file_data = zip_data.read(info.filename)

                # ファイルパスをキーにして辞書に入れる
                file_datas.update({info.filename: file_data})

    except zipfile.BadZipFile:
        print(traceback.format_exc())

    return file_datas

次に、この関数を使う部分です。メイン関数です。

zipから読み込んだデータは、そのまま lxml.etree.fromstring() に渡すことができます。データの中身はバイナリデータです。

import re
from lxml.etree import fromstring as etree_fromstring

def main():
    """メイン関数"""
    # zipファイルの場所
    zip_file = r'*****\S100AM8K.zip'

    # 拡張子でファイルを選択してみる
    re_match = re.compile('^.*?\.xbrl$').match

    # zipからファイルデータを読み込む
    files = read_file_from_zip(zip_file, re_match)

    # 読み込んだXBRLファイルを1つずつ解析
    for (file, data) in files.items():
        # ファイル名
        print('file: %s' % file)

        # XML解析 lxml.etree
        root = etree_fromstring(data)
        print('root.tag: %s' % root.tag)

        print('')
    return

あと、メイン関数を呼び出すところです。

if __name__ == '__main__':
    main()

zipから読み込んだXMLデータをBeautifulSoupで読み込む場合です。バイナリデータをそのまま渡せば解析してくれます。

単に文字列に変換したい場合は、適当なエンコーディングを指定して「.decode()」のメソッドを呼び出します。

バイトオーダーマーク(BOM)付きの UTF-8 をデコードするときは、'utf-8-sig' を指定します。'utf-8'でデコードすると、先頭にパイトオーダーマークの文字列が残ってしまいました。

from bs4 import BeautifulSoup

# XML解析 BeautifulSoup(HTMLパーサー)
soup = BeautifulSoup(data, features='lxml')

# XML解析 BeautifulSoup(XMLパーサー)
soup = BeautifulSoup(data, features='xml')

# バイナリをデコードして文字列に変換
text = data.decode('utf-8-sig')

zipfile.badzipfile エラー

zipファイルを読み込んでいると、稀に zipfile.badzipfile エラーに遭遇します。

普通はzipファイルが壊れている時に出ます。

データは諦めるしかないのか?

実際のところ、あくまで『Pythonのzipfileで読めない』というだけですので、Python以外の解凍ソフトで読み込んだら、部分的に解凍できたという場合も結構ありました。

それから、たまにですが、何度ダウンロードしても読み込みに失敗するzipファイルも見かけます。

サーバーのzipファイルが壊れたまま放置されてるケースですね。米国のEDGAE XBRLのzipで時々ありました。

とりあえず、zipfile.badzipfileが出たらログに記録だけして、読めないzipはスキップするようにしてます。

処理が終わったらログを見て、読めなかったzipを再取得したりする感じですね。

基本的にそういう対応になると思います。

タイトルとURLをコピーしました