EDINETに提出された新規上場会社の有価証券届出書（新規公開時）のXBRLファイルを読み込む

## 新規上場会社のEDINET提出書類をトレースする
　東証の新規上場会社は、営業日の15:30頃に[東証HP](https://www.jpx.co.jp/listing/stocks/new/index.html)にて発表されるが、その新規上場会社の有価証券届出書（新規上場時）の書類はEDINETに大体15時ちょい過ぎくらいのタイミングで検索可能となっている。
　前々から疑問を持っていて、正体不明の謎のツイッターユーザー「岡三マン」は、なぜ15:30に東証HPで発表される上場申請を承認された新規会社について、およそ30分前に情報をつかんでツイートしているのだろうか？と思っていたのだが。どうやら岡三マンはEDINETのおそらくこの新規上場会社の提出書類をフックにしてツイートしているのではないかと思われる。
![6.png](https://qiita-image-store.s3.amazonaws.com/0/91974/1cbaf709-6d75-695b-639a-cd62a8acb905.png)
※今日発表された新規会社。岡三マンは日本時間の15時01分にツイート。

　東証の上場承認がされた会社をトレースする場合、例えば15:30以降に[東証HP](https://www.jpx.co.jp/listing/stocks/new/index.html)を参照してpandasでread_html()でテーブル表を読み込むか、あるいは15:01頃にtwitter_APIを使って岡三マン（okasanman）のツイート一覧を読み込むかの方法が考えられるが、今回は<font color=Red>EDINETの提出書類から、有価証券届出書（新規公開時）を捕まえて、そのXBRLファイル（ZIPファイル）をダウンロードし、ZIPファイルを解凍した上で、XBRLファイルを読み込む方法</font>を以下のようなpythonコードを記述してみた。

注：EDINETは来年APIを公開するらしいので、とりあえず以下のソースでは有報キャッチャーを参照することにした。EDINETのAPIが利用可能になったら本体EDINETを参照する予定。なお有報キャッチャーは15:0Xに提出された有価証券届出書（新規上場時）のファイルが15:10頃には検索できたので、岡三マンとほぼ同等のスピードで、該当提出書類を検索できるのではないかと思われ...

```python:ipo_xbrl.py

!pip install beautifulsoup4
!pip install html5lib

import pandas as pd
import numpy as np
import requests
from bs4 import BeautifulSoup
from datetime import datetime

import xml.etree.ElementTree as ET

import os
import zipfile
import re 
from collections import OrderedDict

def fn_srch():
    prm_q = '有価証券届出書+新規公開 '
    prm_t = '1' #直近7日間で検索    

    #有報キャッチャーを参照 
    for j in range(3):
        url = 'https://ufocatch.com/Xsearch.aspx?q='+str(prm_q) +'&c=ALL&p='+str(j)+'&fe=&fc=&t='+str(prm_t) 
        r = requests.get(url)

        print(j) 
        soup = BeautifulSoup( r.text,'html.parser')

        tbl = soup.find_all("table")
        #print(tbl)
        for i in tbl:
            td = i.find("td")
            a ,img  = i.find("a") ,  i.find("img")     
            src = img["alt"] if img is not None else 'hoge'
            lnk = a.get('href') if src.find('訂正有価証券届出書')!=0 and src.find('有価証券届出書（新規公開時）')==0 else 'edinet/'
            tx =str(lnk).split('edinet/')[1] 
            fn = fn_xbrl(tx) if tx !=''else 'hoge'

def fn_xbrl(tx):
    #XBRLダウンロード
    url = 'https://resource.ufocatch.com/data/edinet/'+tx
    fn = str(tx) +".zip"
    os.system("wget -O " + str(fn) + " " + str(url))

    # ZIP解凍
    with zipfile.ZipFile( str(fn), 'r' )  as myzip:
      infos = myzip.infolist()
      for info in infos:
          base, ext = os.path.splitext(info.filename)
          if  str(base).find('Public')>0 and ext == '.xbrl':
              myzip.extract(info.filename)
              # XBRLパース 
              print('■' + info.filename)
              dict = fn_parse(info.filename)
              ReleaseDate = print(dict["FilingDateCoverPage"]) #提出日を取得
                                                               #当日分はtwitterやslack、gmail等に通知すればよろしい
              #取得項目の表示 (辞書型)
              for k, v in dict.items():
                  print(k, v)

def fn_parse(xbrl_path):

    # 変数宣言
    lst = []  #取得項目リスト

    ### タクソノミーURLの取得(using beautiful Soup)
    ff = open( xbrl_path , "r" ,encoding="utf-8" ).read() 
    soup = BeautifulSoup( ff ,"html.parser")

    x = soup.find_all("xbrli:xbrl" )
    x = str(x)[0:2000] 
    x = x.rsplit("xmlns")

    # 該当attr（crpとdei）の取得
    crp  =['{' + i.replace( i[0:11] ,"").rstrip(' ').replace('"',"") + '}' for i in x[1:10] if i[0:11] == ':jpcrp_cor=']
    dei  =['{' + i.replace( i[0:11] ,"").rstrip(' ').replace('"',"") + '}' for i in x[1:10] if i[0:11] == ':jpdei_cor=']
    crp,dei = crp[0] ,dei[0]

    #print(crp)
    #print(dei)

    ### 取得項目のリスト化
    #1.EDinetCode/証券コード/英文社名/日本語社名/直近本決算の会計基準/直近本決算_期初/直近本決算_期末
    lst1= ['EDINETCodeDEI','SecurityCodeDEI','FilerNameInJapaneseDEI','FilerNameInEnglishDEI','AccountingStandardsDEI']  
    lst1= lst1 + ['CurrentFiscalYearEndDateDEI','CurrentFiscalYearStartDateDEI']
    lst1 =[ str(dei)+str(i)  for i in lst1]
    #print(lst1)

    #2.提出者/提出者の連絡先住所/提出者の連絡先TEL/特別記載事項/提出日
    lst2= ['TitleAndNameOfRepresentativeCoverPage','NearestPlaceOfContactCoverPage','TelephoneNumberNearestPlaceOfContactCoverPage']
    lst2= lst2 + ['SpecialDisclosureAboutPublicOfferingOrSecondaryDistributionTextBlock','FilingDateCoverPage']
    lst2 =[ str(crp)+str(i) for i in lst2]
    #print(lst2)
    lst = lst1 + lst2 
    #print(lst3)


    # XBRLの項目値取得
    dict = get_vle( xbrl_path ,lst, crp , dei)
    return dict

def get_vle( xbrl_path ,lst, crp , dei):
    dict = OrderedDict() # 格納用の辞書

    # XML項目の取得(using elementTree)
    tree = ET.parse( xbrl_path ) 
    root = tree.getroot() 

    print(lst)
    for c1 in root:
        for i in lst :
            if c1.tag == i:

               ### 値の取得 
               tx = str(c1.text)

               #特別事項の記述の部分から上場市場名を抽出する
               if c1.tag == crp + 'SpecialDisclosureAboutPublicOfferingOrSecondaryDistributionTextBlock':
                  spc =  c1.text.split("</p>") #</p>を区切り文字にして配列化

                  tx = str(spc[0:4])
                  for j in spc:
                      #上場市場についての記載文章を特定
                      if j.find("上場")  > -1 and j.find("予定") > -1 and j.find("主幹事")> -1: 
                          if j.find("株式について、")  > -1: 
                              tx = '...' + str( j.split('株式について、')[1])
                          else:
                              tx = str(j)
                              hr = j.split('">')
                              for q in hr:
                                  if q.find("上場")  > -1 and q.find("予定") > -1 and q.find("主幹事")> -1: 
                                     tx = str(q)



               ### 取得した値を辞書に格納
               a = str(i).replace(dei,'').replace(crp,'')
               dict[a] = tx


    # 戻り値を返す
    return dict

# main
if __name__ == '__main__':

      fn_srch() 
```

※実行結果↓
![1.png](https://qiita-image-store.s3.amazonaws.com/0/91974/b05f2166-bf4b-64f1-86fa-f7fbaa2b0d49.png)

### 上記スクリプトの補足説明
・ファンクション**fn_srch()**で有報キャッチャーを検索
・有報キャッチャーにて「'有価証券届出書+新規公開 '」で検索。あと検索期間は「t=1（直近7日間）」を指定。
・「'有価証券届出書+新規公開 '」で検索すると、「訂正有価証券届出書（新規公開時）」も検索ヒットしてしまうので、あとで提出書類に「訂正」の文言がある場合はスルーするようにして、「有価証券届出書（新規公開時）」だけを処理対象としている。
・該当の書類の場合は、ファンクション**fn_xbrl()**を実行
・**fn_xbrl()**で該当提出書類のZIPファイルをダウンロードして、ZIPファイルを解凍して、XBRLファイルを特定している。
・ファンクション**fn_parse()**で、XBRLファイルで検索する項目リストを準備している
・XBRLファイルで読み込む項目は、タクソノミーURLで指定された名前空間の文字列を持っている模様。XBRLのパースはほぼド素人なので詳しくないが、例えばEDINETコードの場合、項目名は「<font color=blue>EDINETCodeDEI</font>」だが、その頭に「<font color=blue>{http ://disclosure.edinet-fsa.go.jp/taxonomy/jpdei/2013-08-31/jpdei_cor}</font>」みたいな文言が付いていて、項目によってタクソノミーで定義しているファイル名(jpdei_corとかjpcrp_corとか)が異なるようなので、まずBeautifulSoupでタクソノミー定義ファイルの記述項目の部分を読み込んで、ファイル名を変数に入れて、読み込むlist一覧に織り込むようにしている。
・XBRLファイルの読み込みは、ファンクション**get_vle()**で実行している。
・**get_vle()**では、力技？でXBRLファイルをelementTreeを使ってXMLとして読み込んで項目取得をしている。 
※XMLの読み込みは[ここ](https://qiita.com/Fortinbras/items/2f4d53ccfff02b94e1cd)を参考に。
・XBRLファイルはHTMLの記述の部分とXML記述の部分のハイブリッドファイルというイメージでOK？
なのでタクソノミーの指定ファイルの記述（XMLの名前空間の情報）などHTMLの記述部分についてはBeautifuSoupを使って取得し、XMLでの記述部分については、elementTreeを使って読み込みを実施している。
・提出書類的には、EDINETコードは必須のようだが、証券コード（<font color=blue>SecurityCodeDEI</font>）は必須ではなさそうである。今回の例に挙げた会社は、証券コードの記載があるが、数日間観察した限りだと、証券コードの記載が空欄の会社もそれなりにある模様。
・例の15時の岡三マンのtwitterツイートをよく見るとどういう会社が新規上場するとしかツイートしておらず、証券コードの記載がないのは、さもありなむ。EDINET的には提出者のEDINETコードはマストであっても、証券コードはマストでないからな。
・証券コードが知りたい場合は、やはり東証HPの15:30の正式な発表を待つ必要がある。
・どの市場に上場するかどうかの情報は、提出書類の「募集又は売出しに関する特別記載事項」のページに記載があるのでそちらを参照。
・「募集又は売出しに関する特別記載事項」（<font color=blue>SpecialDisclosureAboutPublicOfferingOrSecondaryDistributionTextBlock</font>）の文言から「上場」と「予定」と「主幹事」の３ワードが含まれた段落（pタグ）をピックアップしている。（大体の会社は、どこの証券会社を主幹事にしてどこに上場予定であるという書き方をしているので。もちろん例外はある）
・ただし「募集又は売出しに関する特別記載事項」の文章、東証1部2部マザーズといった市場部までの記載はない会社のほうが圧倒的に多く、またいつ上場するかの記載はほぼないので、どの市場部にいつ上場するのかどうかはやはり15:30の東証HPの発表を参照するしかないようだ。
・pタグをsplitで配列化したものを順次ループで中の文言をfind()で検索して、if分岐で該当項目かどうかを調べている
・提出日時を取得しているので、<font color=red>当日分の場合のみ岡三マンみたいにtwitterに投稿するもよし、slackなどに自動投稿するもよし、GoogleのAPIを使ってGmailでメール送信するもよし</font>。あとはいかようにも。
・Google Colabで15時数分にタイマー実行する場合は、[この記事](https://qiita.com/Fortinbras/items/4cfa9269af2ab8d1d4d5)を参照


## ZIPファイルのダウンロードと解凍について

```python:case1.py 
    #XBRLダウンロード
    url = 'https://resource.ufocatch.com/data/edinet/'+tx
    fn = str(tx) +".zip"
    os.system("wget -O " + str(fn) + " " + str(url))
```
・os.system()を使って wgetでZIPファイルをダウンロードしている。
・Google ColaboratoryはLinuxコマンドを頭に！をつけて実行可能なので「**!wget**」コマンドが利用可能だが、ダウンロード先ファイルのディレクトリ名が長いので、pythonの変数に格納してos.system()を使ってpythonプログラムからwgetを実行するようにした（← 個人的な記述の感覚の問題）

![3.png](https://qiita-image-store.s3.amazonaws.com/0/91974/6771fc97-e33d-737e-c4c6-4984dc6735dc.png)
・ZIP解凍は内容全部を解凍するほかに、<font color=red>一部分だけ、特定のファイルのみを解凍</font>することも可能。
・上記ソースでは、myzip.infolist()でZIPファイルの収録内容一覧を取得して、find関数で該当ファイルのみを検索してmyzip.extract()で解凍している

## ワンライナーについて
### if文のワンライナー
```python:case2.py 
    ### 旧 ### 
    lst.append(dei + 'EDINETCodeDEI')
    lst.append(dei + 'SecurityCodeDEI')
    lst.append(dei + 'FilerNameInJapaneseDEI') #日本語社名
    lst.append(dei + 'FilerNameInEnglishDEI')  #英文社名
    lst.append(dei + 'AccountingStandardsDEI') #（直近本決算の）会計基準
            
    lst.append(crp + 'TitleAndNameOfRepresentativeCoverPage') #代表者
    lst.append(crp + 'NearestPlaceOfContactCoverPage')        #最寄の連絡場所
    lst.append(crp + 'TelephoneNumberNearestPlaceOfContactCoverPage') #連絡先TEL
    lst.append(crp + 'SpecialDisclosureAboutPublicOfferingOrSecondaryDistributionTextBlock') #特別記載事項
    lst.append(crp + 'FilingDateCoverPage') #提出日

    #---------------------
    lst.append(dei + 'CurrentFiscalYearEndDateDEI')   #決算期初
    lst.append(dei + 'CurrentFiscalYearStartDateDEI') #決算期末
    
    ### 現行 ### 
    lst1= ['EDINETCodeDEI','SecurityCodeDEI','FilerNameInJapaneseDEI','FilerNameInEnglishDEI','AccountingStandardsDEI']  
    lst1= lst1 + ['CurrentFiscalYearEndDateDEI','CurrentFiscalYearStartDateDEI']
    lst1 =[ str(dei)+str(i)  for i in lst1]
    
    lst2= ['TitleAndNameOfRepresentativeCoverPage','NearestPlaceOfContactCoverPage','TelephoneNumberNearestPlaceOfContactCoverPage']
    lst2= lst2 + ['SpecialDisclosureAboutPublicOfferingOrSecondaryDistributionTextBlock','FilingDateCoverPage']
    lst2 =[ str(crp)+str(i) for i in lst2]
    #print(lst2)
    lst = lst1 + lst2 
    #print(lst3)
    
    print(lst)
```
・pythonのワンライナー表記は、自分はまだpython初心者なので難しいですね。
・最初は空のリスト（lst)に1行ずつ項目名の頭にcrpもしくはdeiの文言を付与してappend()で追加していたのだが、forループの内包表現で1行で記述可能（crpとdeiで合計２行で記述可能）だとあとで気付きましたです、はい。日々是勉強です。

### forループとif文のワンライナー
```python:case3.py 
    # 該当attrの取得（crpとdei）
    ### 旧 ### 
    for i in x[1:10]:
        if i[0:11] == ':jpcrp_cor=':
           crp  = '{' + i.replace( i[0:11] ,"").rstrip(' ').replace('"',"") + '}'
        if i[0:11] == ':jpdei_cor=':
           dei = '{' + i.replace( i[0:11] ,"").rstrip(' ').replace('"',"") + '}' 

    ### 現行 ### 
    crp  =['{' + i.replace( i[0:11] ,"").rstrip(' ').replace('"',"") + '}' for i in x[1:10] if i[0:11] == ':jpcrp_cor=']
    dei  =['{' + i.replace( i[0:11] ,"").rstrip(' ').replace('"',"") + '}' for i in x[1:10] if i[0:11] == ':jpdei_cor=']
    crp,dei = crp[0] ,dei[0]

    print(crp)
    print(dei)
```
・上記ではタクソノミーの定義ファイルのURLの記述が書かれているHTMLの記述箇所を文字列をループで逐次判定して該当文言があった場合は変数格納する的な処理をする際に、forループとif分岐と変数格納を記述しているが、これも１行ワンライナーで記述可能。
・タクソノミーの定義ファイルのURLというかバージョンはたまに変わる（バージョンアップする）場合があるので、面倒でもBeautifulSoupでURLを特定している。

## 余談
 有報キャッチャーで検索できなかったのだが、ソフトバンクグループ（9984）の通信子会社（旧ボーダーフォンの現ソフトバンク）が新規上場するらしい。
![7.png](https://qiita-image-store.s3.amazonaws.com/0/91974/2fce578b-05b2-dba2-47f2-32dfab9cec2a.png)

確かにEDINETに提出される有価証券届出書（新規公開時）の書類は、REITやPRO銘柄、ETFやETNは提出されないし、あるいは組織再編にともなうテクニカル上場の場合は提出書類が別名のものがEDINETに提出されるので、それら銘柄は対象外ではあるのだが。ソフトバンク子会社の新規上場（9434）が補足できなかったのは謎。
※追記
数時間後にソフトバンクが参照できた。有報キャッチャーも利用可能になるのはタイムラグがあるのかな？
（まあこのソフトバンクは元ボーダーフォンで再上場会社というのはあるのかも）