倒産企業情報の取得

# 概要

倒産企業の情報といえば，[東京商工リサーチ](https://www.tsr-net.co.jp/)や[帝国データバンク](https://www.tdb.co.jp/index.html)が有名です．東京商工リサーチのWebサイトで提供されている企業倒産の内容や背景・経緯に関する情報をスクレイピングにより取得して，下図のようなデータフレームにおさめてみたいと思います．

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725285/18adf520-50ea-e654-57e9-d7615d437127.jpeg" width=80%>

https://www.tsr-net.co.jp/

https://www.tdb.co.jp/index.html


財務データを含む倒産企業や非倒産企業の情報を活用して，倒産確率の推定や倒産・非倒産の２値分類などが行われているようです．



:::note
必要なライブラリ
pip install beautifulsoup4
pip install lxml
:::

# 1. 取得するデータについて
東京商工リサーチのwebページ，具体的には，[「こうして倒産した」](https://www.tsr-net.co.jp/news/process/index.html)から情報を抽出します．図のように2005年から月別に代表的な倒産企業が掲載されています．

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725285/b61cea8d-cbd0-2313-d05d-657fe3b56e47.jpeg" width=80%>

この記事では，2023年の１月〜12月のデータを収集してみましょう．


# 2. URL
実際にクリックするとわかるのですが．2023年の１月〜12月の部分に相当するURLは，2023と月を並べた構成になっていることに気がつきます．この構成を利用してスクレイピングするURLを指定します．

~~~python: URLの規則
url_2023 = [
    "https://www.tsr-net.co.jp/news/process/2023/1/index.html",
    "https://www.tsr-net.co.jp/news/process/2023/2/index.html",
    ...中略...
    "https://www.tsr-net.co.jp/news/process/2023/12/index.html"
]
~~~




# 3. HTMLの確認
抽出対象のURLの構造を簡単に確認します．試しに，2023年１月度のURLをスクレイピングしてみます．図のような形のページになっています．赤色の枠で囲まれた部分をクリックすると詳細な情報が表示される仕組みになっています．requestsとBeautifuSoupのライブラリを利用してページのソースコードを取得してみましょう．

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725285/06f2b8e8-e43b-a897-6208-949440faf904.jpeg" width=45%>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725285/ad5f352c-dade-f2e8-e0f1-c318a7984fea.jpeg" width=45%>

下記のコードは2023年の1月度のHTMLを取得しています．

~~~python: URLの読み込み2023年1月度
import requests
from bs4 import BeautifulSoup

url = "https://www.tsr-net.co.jp/news/process/2023/1/index.html"
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, "lxml")
~~~

少々長いですが，大枠を掴むためにsoupの一部を掲載してみます．図の赤色の枠をクリックすると表示される詳細データも上記のコードでしっかり取得されています．

HTML内に\<!--開始： こうして倒産した記事一覧 --\>というコメント行が見つかります．優しい設計で少し嬉しいです．眺めていると企業毎に情報がまとまっていることがわかります．
```<div class="bankruptcy_list_reason_child">```タグによって，企業毎に情報が整理されています．


~~~html
<!DOCTYPE HTML>
<html lang="ja">
<head prefix="og: https://ogp.me/ns#">
<title>1月度 | 2023年（令和5年） | こうして倒産した | 倒産・注目企業情報 | 東京商工リサーチ</title>

~~~　中略 ~~~

<!--開始： こうして倒産した記事一覧 -->
<div class="bankruptcy_list_reason_wrap">

<div class="bankruptcy_list_reason_child">
<div class="bankruptcy_list_reason_title_wrap">
<div class="bankruptcy_list_reason_title">
<div class="bankruptcy_list_reason_title_text">◯◯◯◯（株）</div>
<div class="bankruptcy_list_reason_title_text_tags">
<ul class="tags">
<li class="tag tag_prefecture">千葉</li>
<li class="tag tag_industry">不動産業、物品賃貸業</li>
</ul>
</div></div></div>
<div class="bankruptcy_list_reason_debt_wrap">
<div class="bankruptcy_list_reason_debt">
<div class="bankruptcy_list_reason_debt_text">負債総額</div>
<div class="bankruptcy_list_reason_debt_price">**億****万円</div>
</div></div>
<div class="bankruptcy_list_reason_arrow_wrap js_reason_arrow">
<div class="bankruptcy_list_reason_arrow">
<div class="bankruptcy_list_reason_arrow_img">
<img src="/common/img/arrow_down_blue.svg"/>
</div></div></div>
<div class="bankruptcy_list_reason_detail_wrap js_reason_detail">
<div class="bankruptcy_list_reason_detail">
<p>　◯◯◯◯（株）（TSR企業コード:xxxx****、法人番号:xxxx****xxxx、設立1979（昭和54）年2月、資本金2000万円、資本金2000万円、◯◯◯社長）は12月23日、千葉地裁より破産開始決定を受けた。...\</p>
</div></div></div>

<div class="bankruptcy_list_reason_child">
<div class="bankruptcy_list_reason_title_wrap">
<div class="bankruptcy_list_reason_title">
<div class="bankruptcy_list_reason_title_text">△△△（株）</div>
<div class="bankruptcy_list_reason_title_text_tags">
<ul class="tags">
<li class="tag tag_prefecture">東京</li>
<li class="tag tag_industry">総合印刷業</li>
</ul>

~~~　後略 ~~~
~~~

# 4. 情報の抽出
## 4.1 全体像
取得する情報を緑色で囲ってみました．

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3725285/ad5f352c-dade-f2e8-e0f1-c318a7984fea.jpeg" width=80%>

今回は枠で囲った5種類のデータを集めてみたいと思います．

:::note
1. 社名
1. 地域
1. 産業
1. 負債総額
1. 記事・内容
:::

抽出する個別の企業に関する情報は，HTMLの中身を確認すると，divタグ，class属性の値がbankruptcy_list_reason_childで囲われていて，順番に記載されているのがわかります．

::: note
個別企業の情報の区切りを表すタグ
\<div class="bankruptcy_list_reason_child"\>
:::

### 手順

1. 企業毎のリストを作成
1. リストの要素ごとに，社名や負債総額などの詳細なデータを抽出

という手順でスクレイピングしていきます．

1番目の企業毎のリストは，dviタグ，class属性の値がbankruptcy_list_reason_childを使って順番に検索（**find_all**）することでリスト化できます．変数名soupを取得したHTML（```soup = BeautifulSoup(response.text, "lxml")```）とすると，

~~~python
review_elements = soup.find_all("div", class_="bankruptcy_list_reason_child")
~~~
という形で，リストを取得できます．2番目はreview_elementsの要素ごとに，詳細なデータを取得していきます．以下では，review_elementsの代表的な要素を**review_element**として表記します．イメージとしては，

~~~python
# 企業毎のリストを作成
review_elements = soup.find_all("div", class_="bankruptcy_list_reason_child")

# 個別企業の詳細情報へアクセス
for review_element in review_elements:
    review_elementを利用して個別企業の詳細情報抽出
~~~
という形になります．以下，個別企業の情報の抽出方法について見ていきます．


## 4.2 社名
社名の部分はHTMLのコード
~~~
<div class="bankruptcy_list_reason_title_text">◯◯◯◯（株）</div>
~~~
より，divタグ，class属性の値がbankruptcy_list_reason_title_textという形をしています．class属性の値でfindした後，テキスト部分を抽出すればOKです．リストであるreview_elementsの代表的な要素を**review_element**とすると，社名は

~~~python: 社名
name_element = review_element.find("div",  class_="bankruptcy_list_reason_title_text")
name = name_element.get_text(strip=True) if name_element else None
~~~
によって取得できます．社名のタグ・class属性が無い場合，つまり，name_elementがNoneの場合は，nameの値をNoneとなるように調整してあります．以下同様の調整を追加してあります．


## 4.3 地域
地域の部分はHTMLのコード
~~~
<li class="tag tag_prefecture">千葉</li>
~~~
より，liタグ，class属性の値がtag tag_prefectureという形をしています．class属性の値でfindした後，テキスト部分を抽出すればOKです．具体的には，

~~~python: 地域
prefecture_element = review_element.find("li", class_="tag tag_prefecture")
prefecture = prefecture_element.get_text(strip=True) if prefecture_element else None
~~~
によって取得できます．

## 4.4 産業・業界名
産業・業界名に相当する部分はHTMLの
~~~
<li class="tag tag_industry">不動産業、物品賃貸業</li>
~~~
より，liタグ，class属性の値がtag tag_industryという形をしています．class属性の値でfindした後，テキスト部分を抽出すればOKです．具体的には，

~~~python: 産業・業界名
industry_element = review_element.find("li", class_="tag tag_industry")
industry = industry_element.get_text(strip=True) if industry_element else None
~~~
によって取得できます．

## 4.5 負債総額
負債総額に相当する部分はHTMLの
~~~
<div class="bankruptcy_list_reason_debt_price">**億****万円</div>
~~~
より，divタグ，class属性の値がbankruptcy_list_reason_debt_priceという形をしています．findした後，テキスト部分を抽出すればよいので，コードは次のような形になります．

~~~python: 負債総額
liability_element = review_element.find("div",class_="bankruptcy_list_reason_debt_price")
liability = liability_element.get_text(strip=True) if liability_element else None
~~~

## 4.6 詳細記事    
詳細な内容については，該当する部分のHTMLコードは，
~~~
<div class="bankruptcy_list_reason_detail">
<p>　◯◯◯◯（株）（TSR企業コード:xxxx****、法人番号:xxxx****xxxx、設立1979（昭和54）年2月、資本金2000万円、
~~~
という形になっています．dviタグ，class属性の値がbankruptcy_list_reason_detailとなっているテキスト部分が抽出部分になります．class属性の値bankruptcy_list_reason_detailでfindしてテキスト部分の抽出すればOKとなります．具体的には，次のようになります．


~~~python: 詳細記事
content_element = review_element.find("div", class_="bankruptcy_list_reason_detail")
content = content_element.get_text(strip=True) if content_element else None
~~~
```get_text(strip=True)```によってタグの内側にあるテキストを取得して，テキストの前後にある余分な空白や改行などを削除しています．


## 4.7 まとめ
これまでのコードをまとめると，次のようになります．

~~~python: 情報抽出まで
import requests
from bs4 import BeautifulSoup
import pandas as pd

# 2023年のURL　規則性があるので，手動でもOK
url_2023 = []
base_url =  "https://www.tsr-net.co.jp/news/process/2023/"
for month in range(1,13):
    url = f"{base_url}{month}/"
    url_2023.append(url)

headers = {"User-Agent": "Mozilla/5.0"}

# 個別データを入れるリスト
names = []       # 社名
prefectures = [] # 地域
industries = []  # 産業
liabilities = [] # 負債総額
contents = []    # 内容

for url in url_2023:    
    response = requests.get(url, headers=headers)
    soup = BeautifulSoup(response.text, "lxml")

    # 企業毎のリストを準備
    review_elements = soup.find_all("div", class_="bankruptcy_list_reason_child")


    # 企業毎のリストの要素から情報を抽出
    for review_element in review_elements:
        # name 社名
        name_element = review_element.find("div",  class_="bankruptcy_list_reason_title_text")
        name = name_element.get_text(strip=True) if name_element else None

        # prefecture　地域
        prefecture_element = review_element.find("li", class_="tag tag_prefecture")
        prefecture = prefecture_element.get_text(strip=True) if prefecture_element else None

        # industry　産業・業界名
        industry_element = review_element.find("li", class_="tag tag_industry")
        industry = industry_element.get_text(strip=True) if industry_element else None
        
        # liability　負債総額
        liability_element = review_element.find("div", class_="bankruptcy_list_reason_debt_price")
        liability = liability_element.get_text(strip=True) if liability_element else None

        # content　内容
        content_element = review_element.find("div", class_="bankruptcy_list_reason_detail")
        content = content_element.get_text(strip=True) if content_element else None

     
        names.append(name)
        prefectures.append(prefecture)
        industries.append(industry)
        liabilities.append(liability)
        contents.append(content)
~~~

## 4.8 データフレームへ変換
取得したリスト化されたデータをpandasデータフレームに変換すれば完成となります．列名を適宜与えて，データフレームを作成，CSVファイルとして保存したコードが次の部分です．

~~~python
# リストをPandasのDataFrameに変換
df = pd.DataFrame({
    'name'  : names,
    'prefecture': prefectures,
    'industry' : industries,
    'liability' : liabilities,
    'content' : contents
})
# csvファイルとして保存
df.to_csv("data.csv", index=False)   
~~~

# 5. コード一覧
これまで説明した内容をすべてまとめたものが次のコードとなります．1ページ取得するごとにお休みを追加しました．

~~~python
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time

# 2023年のURL　規則性があるので，手動でもOK
url_2023 = []
base_url =  "https://www.tsr-net.co.jp/news/process/2023/"
for month in range(1,13):
    url = f"{base_url}{month}/"
    url_2023.append(url)

headers = {"User-Agent": "Mozilla/5.0"}

# 個別データを入れるリスト
names = []       # 社名
prefectures = [] # 地域
industries = []  # 産業
liabilities = [] # 負債総額
contents = []    # 内容

for url in url_2023:    
    response = requests.get(url, headers=headers)
    soup = BeautifulSoup(response.text, "lxml")

    # 企業毎のリストを準備
    review_elements = soup.find_all("div", class_="bankruptcy_list_reason_child")


    # 企業毎のリストの要素から情報を抽出
    for review_element in review_elements:
        # name 社名
        name_element = review_element.find("div",  class_="bankruptcy_list_reason_title_text")
        name = name_element.get_text(strip=True) if name_element else None

        # prefecture　地域
        prefecture_element = review_element.find("li", class_="tag tag_prefecture")
        prefecture = prefecture_element.get_text(strip=True) if prefecture_element else None

        # industry　産業・業界名
        industry_element = review_element.find("li", class_="tag tag_industry")
        industry = industry_element.get_text(strip=True) if industry_element else None
        
        # liability　負債総額
        liability_element = review_element.find("div", class_="bankruptcy_list_reason_debt_price")
        liability = liability_element.get_text(strip=True) if liability_element else None

        # content　内容
        content_element = review_element.find("div", class_="bankruptcy_list_reason_detail")
        content = content_element.get_text(strip=True) if content_element else None

     
        names.append(name)
        prefectures.append(prefecture)
        industries.append(industry)
        liabilities.append(liability)
        contents.append(content)

    print(f"{url}の読込終了")
    time.sleep(2)



# リストをPandasのDataFrameに変換
df = pd.DataFrame({
    'name'  : names,
    'prefecture': prefectures,
    'industry' : industries,
    'liability' : liabilities,
    'content' : contents
})
# csvファイルとして保存
df.to_csv("data.csv", index=False)   

~~~



# その他
スクレイピングについて，映画レビューの取得記事を紹介しています．一見してもらえると励みになります:grin:


https://qiita.com/AzukiImo/items/3356af25fe3e7d496e75

財務に関するデータも取得できると面白い分析ができると思いますが，ここまでにします．