11/5追記:PDF解析に使うPDFMinerの使い方、一部修正
PDFMiner使い、東証株式相場表(日報、PDF)からデータ抽出し日別CSVにする
一度公開したものの、拾いきれてないデータがあったり、エラーが出だしたりして、スクリプト公開やめてました。
その後、必死の努力(効果を示すものではありません。爺個人の感想です)の結果、エラーを二つまで減らすことができました。これ以上、爺には無理(こういうのは、爺の判断早目)、ということで、「恥ずかしながらのスクリプト」を改めて公開することにしました。
二つのエラーは「東証株式相場表:PDFMinerで解析しMySQLへの流れ(5)まだエラーが二つ」に載せました。お判りのかた、ご教示くださいませ。
フォルダ位置、ファイル構成などは「東証株式相場表:PDFMinerで解析しMySQLへの流れ(1)」掲載の画像をご参照ください。
恥ずかしながらのPyhtonスクリプト
爺の錯誤が一杯ふくまれているかと。ご寛容・ご容赦のほど。自己責任でよろしくお願いします。お気づきのことがありましたら、コメントなどでお知らせください。
# PDFMinerで、東証株式相場表(日報、PDF)を解析し、CSVにします
# 前提:解析対象PDFは指定フォルダへDL済
# 前提:CSV保管用フォルダ作成済
#正規表現、CSV、IO、time
import csv
import re
import time
from io import StringIO
#PDF解析:PDFMiner
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdfinterp import PDFPageInterpreter, PDFResourceManager
from pdfminer.pdfpage import PDFPage
from pdfminer.pdfparser import PDFParser
#フォルダ内ファイル処理用
import glob
#関数-------------------------------------
def text_replace(txt): #不要文字削除、銘柄コード先頭文字位置取得
d_pos = []
iter_pos = []
text_page = txt.replace('千株[thous.shs.]\n千口/千個[thous.units.]\n','')
text_page = text_page.replace('\t\t ','\t')
#単頁内最初の銘柄コード以前のテキストは、正規表現で結果的に無視される
#銘柄コードの文字始終位置、合致文字列の一日分を納める
iter_pos = re.finditer(r'\t[0-9]{4}\t{1,3}.', text_page)
for def_i in iter_pos: #銘柄コード先頭文字位置
d_pos.append(def_i.start()+1)
return (text_page, d_pos) #ある程度整ったテキスト・データ、銘柄毎の先頭文字位置(銘柄コード)
def delComma_str2num(txt): #加重平均と売買株数用 整形と数値化
txt = txt.replace(',','') if re.search(r'\d\,\d',txt) else txt #三桁区切りコンマ削除
r_txt = float(txt) if re.match(r'^[0-9]', txt) else txt
return r_txt
#本体-------------------------------------
pdfs = glob.glob('pdf/*.pdf')
pdfs = sorted(pdfs) #日付順整列
time_s = time.time() #時計計測開始
print('経過時間計測開始:')
proM = 'TOKYO PRO Market銘柄'
for a_pdf in pdfs: #日別PDF=a_pdf≒400ページ
#東証株相場表のファイルをopenする
fp = open(a_pdf, 'rb')
print(a_pdf[4:12], '→', end = '')
#date型変換
m_date = [int(a_pdf[4:8]),int(a_pdf[8:10]),int(a_pdf[10:12])]
# 出力先をPythonコンソールするためにIOストリーム取得
outfp = StringIO()
# PDFParserオブジェクト取得
parser = PDFParser(fp)
# PDFDocumentオブジェクト取得
doc = PDFDocument(parser)
rmgr = PDFResourceManager() # PDFResourceManagerオブジェクト取得
lprms = LAParams(boxes_flow=None) # LAParamsオブジェクト取得 ※パラメタFalseだと狂う
device = TextConverter(rmgr, outfp, laparams=lprms) # TextConverterオブジェクト取得
iprtr = PDFPageInterpreter(rmgr, device) # PDFPageInterpreterオブジェクト取得
# 銘柄コードをINDEXとして使用するためslice位置取得のため各ページの銘柄コード文字位置取得
print('経過時間計測中 → ', end ='')
m_txt = [] #単頁データテキスト
m_pos = [] #単頁内の銘柄コード先頭文字位置
for pdf_page in PDFPage.get_pages(fp): #単日PDFを各ページ毎に処理する
text_page = '' #PDFからのテキスト収納用、頁ごと
iprtr.process_page(pdf_page)
text_page = (outfp.getvalue()).replace('\n\n','\t')
#「TOKYO PRO Market銘柄」をPDF内で見つける
if re.search(proM,text_page): #処理対象ページがTOKYO PRO Market銘柄」を含む場合の処理
s_pos = re.search(proM,text_page).start() #警告エラー出るが無視
text_page = text_page[:s_pos-1] #頁内「TOKYO PRO Market銘柄」から後ろを消去
m_txt, m_pos = text_replace(text_page) # 関数戻り値 二つ
break
else:
m_txt, m_pos = text_replace(text_page) # 関数戻り値 二つ
#関数戻り値で、単頁テキスト(m_txt)、単頁内銘柄コード先頭文字位置(m_pos)を取得
outfp.close() # I/O閉じる
device.close() # TextConverter閉じる
fp.close() # fp閉じる
time_m = time.time()
print('経過', time_m-time_s,'秒 PDF解析完了')
#銘柄情報一連格納の「text_page」をsliceし銘柄別に配列に納める
datas = [] #データ用リスト収納用
for a_pos in m_pos[::-1]: #逆順にスライス ※前から実行し文字削除すると文字位置が狂う
a_slice = m_txt[a_pos:]
a_slice = a_slice.replace('\n','') # type: ignore
#一銘柄内の個別項目を配列収納
a_split = a_slice.split('\t') #データ収納
#銘柄コードと売買単位の間に1文字表示(第2項目になっている)が時々ある→削除
a_split if a_split[1].isdecimal() else a_split.pop(1) #0始まりの第2項目(a_split[1])削除
#不揃い項目がある1行データ排除-------------------------
if len(a_split) < 17:
continue
#必要項目抽出→1行データとして格納----------------------
if a_split[1].startswith('1') : #第2項目が取引単位(先頭が「1」)を目印
another_p = []
another_p.append(int(a_pdf[4:12])) #市場日
another_p.append(int(a_split[0])) #&銘柄コード
another_p.append(int(a_split[1])) #取引単位
for i in a_split[3:11]: #前場始値~後場終値
i = i.replace(',','') if re.search(r'\d\,\d',i) else i #三桁区切りコンマ削除
i = float(i) if re.match(r'^[0-9]', i) else i #数字はfloat変換
another_p.append(i)
# この間、気配、前日比スキップ
another_p.append(delComma_str2num(a_split[13])) #加重平均 関数往復
another_p.append(delComma_str2num(a_split[14])) #売買株数 関数往復
# この後、売買金額スキップ
datas.append(another_p) #1銘柄分追記
m_txt = m_txt[:a_pos-len(m_txt)] #格納済み分(この時点の末尾1銘柄分)を削除し次のforへ。
m_pos.clear() #不要になった銘柄コード位置リスト初期化=処理対象の一日分
datas = sorted(datas, key = lambda x: x[1]) #銘柄コード昇順にする
# ↑key設定で第2項目が銘柄コードなので、x[1](0始まり)
#CSV書きだし 日別
# f = open('csv/' + a_pdf[4:12] + '.csv','w',encoding = 'CP932',newline = '') #shift_JIS用
f = open('csv/' + a_pdf[4:12] + '.csv','w',encoding ='utf_8', newline = '')
writer = csv.writer(f,delimiter = ',',quoting = csv.QUOTE_NONNUMERIC)
writer.writerows(datas)
f.close()
time_e = time.time() #時計計測終了
print(time_e-time_s,'秒 CSV書きだし\n')
print('::::::::::::::::::::::::::::::::::::::::::CSV書き出し、全体終了です')
MySQLへ放り込むまで三つのステップ
HTMLのように、すぐ結果を表示できるわけではありません。Python使用環境を整えたり、XAMPPインストール、MySQLのテーブル設定などが済まないと…ほとんど何も起こらないかと…ええ。
東証株式相場表をDLし、PDFMinerでCSV化し、「LOAD DATA INFILE」でMySQLへ放り込む、というのが現在の立ち位置です。
蓄積データを活用するのはこれからの課題です。