PythonでYahooファイナンスをスクレイピングする【企業情報抽出】

PythonでYahooファイナンスをスクレイピングする【企業情報抽出】 プログラミング

Yahooファイナンスのスクレイピングをしていきましょう。
Pythonを使ってできる限り簡単にスクレイピングを行います。

今回は、企業情報ページのスクレイピングです。
企業情報ページをスクレイピングすることにより、銘柄の企業情報を抽出できます。

本記事の内容

  • ここまでの流れ【Yahooファイナンスのスクレイピング】
  • 企業情報ページのスクレイピング仕様
  • 企業情報ページから企業情報を抽出する

それでは、上記に沿って解説していきます。

ここまでの流れ【Yahooファイナンスのスクレイピング】

Yahooファイナンスのスクレイピングに関しては、段階を踏んで解説しています。
今回は、企業情報ページのスクレイピングがメインです。

下記で過去の同シリーズを記載します。

第1弾

第2弾

第1弾の記事は、スクレイピングをする上での心得を説明しています。
スクレイピングを行う上では、絶対に理解しておくべき内容となります。

理解していないと、最悪の場合は犯罪者になってしまいます。
だから、スクレイピングを行う人は絶対に理解しておいてください。

第2弾の記事では、Yahooファイナンスに登録されている銘柄(企業)コードをスクレイピングしています。
記事では、その方法を詳しく解説しています。
全部で3849件の銘柄コードを取得できました。

そして、今回は第3弾となります。
第3弾となる本記事では、取得した銘柄コードをもとにしてスクレイピングを行います。

そのため、第1弾、第2弾の内容はクリアできている前提で話を進めます。
では、企業情報ページのスクレイピング仕様を解説していきます。

企業情報ページのスクレイピング仕様

大きく3つに分けて解説していきます。

  • 企業情報ページのURL作成
  • 企業情報の抽出
  • スクレイピングプログラムの起動

それぞれを下記で説明します。

企業情報ページのURL作成

最初に、企業情報ページのURLについて説明しておきます。
銘柄コードのリストから、自動的に各銘柄ごとの企業情報ページのURLを作成します。

そのときの形式は、以下となります。
「 https://stocks.finance.yahoo.co.jp/stocks/profile/?code=●」

●は、銘柄コードです。
上記形式のURLで企業情報ページにアクセス可能となります。

例えば、株式会社 極洋(1301)の場合は次のURLです。
https://stocks.finance.yahoo.co.jp/stocks/profile/?code=1301

企業情報の抽出

企業名と次の情報を抽出します。

スクレイピングの難易度的には、普通です。
ただ、tableタグで構成されている部分は、若干面倒になります。

一般的には、class名指定で情報は抽出可能です。
しかし、今回のtableタグ内のデータはclass名指定で対応できません。

そのため、泥臭いやり方でスクレイピングを行っています。
詳しくは、後ほど記載するサンプルコードをご覧ください。

と言っても、基本的にスクレイピングは泥臭い作業です。
決してスマートなモノではありません。

スクレイピングプログラムの起動

プログラム(スクリプトpy)では、一つの銘柄の企業情報を取得します。
そのため、3849件全部の企業情報をスクレイピングする場合、3849回プログラムを起動させることになります。

銘柄コードを引数として、該当プログラムをコールする形になります。
本当は、このようなところまで説明する必要はないかもしれません。

ただ、あえて説明しているのは理由があります。
スクレイピングは、外部要因でプログラムが停止することがありえます。

ネットワークの不調はもちろん、相手側サイトからアクセス禁止にされるなど。
そのため、止まることを想定しておいた方が無難です。

また、自分でも止めやすい形にしておいた方が安心です。
一度動いたら止まらないプログラムなんて怖すぎます。
それこそ、Dos攻撃なんてこそになりかねません。

そのようなことを注意喚起したいこともあります。
よって、「スクレイピングプログラムの起動」を説明しています。

企業情報ページから企業情報を抽出する

企業情報ページから企業情報を抽出するコードは、以下。
現時点(2021年2月12日)では元気バリバリ動いています。

サンプルコード

import sys
import bs4
import traceback
import re
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

# ドライバーのフルパス
CHROMEDRIVER = chromedriver.exeのパス"


# ドライバー準備
def get_driver():
    # ヘッドレスモードでブラウザを起動
    options = Options()
    options.add_argument('--headless')

    # ブラウザーを起動
    driver = webdriver.Chrome(CHROMEDRIVER, options=options)

    return driver


# 対象ページのソース取得
def get_source_from_page(driver, page):
    try:
        # ターゲット
        driver.get(page)
        driver.implicitly_wait(10)  # 見つからないときは、10秒まで待つ
        page_source = driver.page_source

        return page_source

    except Exception as e:

        print("Exception\n" + traceback.format_exc())

        return None


# ソースからスクレイピングする
def get_data_from_source(src):
    # スクレイピングする
    soup = bs4.BeautifulSoup(src, features='lxml')

    try:
        info = {}
        info["name"] = None
        info["feature"] = None
        info["consolidated_business"] = None
        info["main_office"] = None
        info["station"] = None
        info["tel"] = None
        info["category"] = None
        info["english_name"] = None
        info["representative"] = None
        info["date_of_incorporation"] = None
        info["market"] = None
        info["date_of_listing"] = None
        info["balance_sheet_date"] = None
        info["share_unit_number"] = None
        info["non_number_of_employees"] = None
        info["number_of_employees"] = None
        info["average_age"] = None
        info["average_annual_income"] = None

        # メイン要素
        main_elem = soup.find(id="main")

        if main_elem:
            # 銘柄名
            name = main_elem.find(class_="symbol").text
            if name:
                info["name"] = name

            # 項目部分(特色、連結事業、・・・)

            tr_elems = main_elem.find_all("tr")

            for elem in tr_elems:
                heading_name = elem.find("th").text

                if heading_name == "特色":
                    td_text = elem.find("td").text
                    info["feature"] = td_text
                elif heading_name == "連結事業":
                    td_text = elem.find("td").text
                    info["consolidated_business"] = td_text
                elif heading_name == "本社所在地":
                    td_text = elem.find("td").text
                    td_text = td_text.replace("[周辺地図]", '')
                    info["main_office"] = td_text
                elif heading_name == "最寄り駅":
                    td_text = elem.find("td").text
                    td_text = td_text.replace("~", '')
                    info["station"] = my_trim(td_text)
                elif heading_name == "電話番号":
                    td_text = elem.find("td").text
                    info["tel"] = td_text
                elif heading_name == "業種分類":
                    td_text = elem.find("td").text
                    info["category"] = td_text
                elif heading_name == "英文社名":
                    td_text = elem.find("td").text
                    info["english_name"] = td_text
                elif heading_name == "代表者名":
                    td_text = elem.find("td").text
                    info["representative"] = td_text
                elif heading_name == "設立年月日":
                    td_text = elem.find("td").text
                    info["date_of_incorporation"] = td_text
                elif heading_name == "市場名":
                    td_text = elem.find("td").text
                    info["market"] = td_text
                elif heading_name == "上場年月日":
                    td_text = elem.find("td").text
                    info["date_of_listing"] = td_text
                elif heading_name == "決算":
                    td_text = elem.find("td").text
                    info["balance_sheet_date"] = td_text
                elif heading_name == "単元株数":
                    td_text = elem.find("td").text
                    info["share_unit_number"] = extract_num(td_text)
                elif heading_name == "従業員数(単独)":
                    td_text = elem.find("td").text
                    info["non_number_of_employees"] = extract_num(td_text)

                    td_text = elem.find_all("td")[1].text
                    info["number_of_employees"] = extract_num(td_text)

                elif heading_name == "平均年齢":
                    td_text = elem.find("td").text
                    info["average_age"] = extract_num(td_text)

                    td_text = elem.find_all("td")[1].text
                    info["average_annual_income"] = extract_num(td_text)

        return info

    except Exception as e:

        print("Exception\n" + traceback.format_exc())

        return None


# 数値だけ抽出
def extract_num(val):
    num = None
    if val:
        match = re.findall("\d+\.\d+", val)
        if len(match) > 0:
            num = match[0]
        else:
            num = re.sub("\\D", "", val)

    if not num:
        num = 0

    return num

def my_trim(text):
    text = text.replace("\n", "")
    return text.strip()

if __name__ == "__main__":

    # 引数
    args = sys.argv
    # 銘柄コード
    code = "1301"
    if len(args) == 2:
        # 引数があれば、それを使う
        code = args[1]

    # 対象ページURL
    page = "https://stocks.finance.yahoo.co.jp/stocks/profile/?code=" + code

    # ブラウザのdriver取得
    driver = get_driver()

    # ページのソース取得
    source = get_source_from_page(driver, page)

    # ソースからデータ抽出
    data = get_data_from_source(source)

    # データ保存
    print(data)

    # 閉じる
    driver.quit()

プログラム詳細は、「企業情報ページのスクレイピング仕様」とコメントをご覧ください。
基本的には、第2弾と大きくは変わりません。
ただ、第2弾のプログラムと比べると、関数を追加したりしています。

あとは、引数対応を説明しておきます。

引数対応

    # 引数
    args = sys.argv
    # 銘柄コード
    code = "1301"
    if len(args) == 2:
        # 引数があれば、それを使う
        code = args[1]

引数がなければ、銘柄コード1301がデフォルトの銘柄コードとなります。

なお、コマンドラインで入力した引数ついては次の記事でまとめています。
Pythonで引数を利用するケースについてです。

実行結果

サンプルコードを実行した結果は、以下。

{'name': '(株)極洋', 'feature': '水産品の貿易、加工、買い付け主力。すしネタに強み。加工食品は業務用が軸。海外加工比率高い', 'consolidated_business': '【連結事業】水産商事48(1)、冷凍食品32(1)、常温食品7(4)、物流サービス0(11)、鰹・鮪11(1)、他0(-3)【海外】11(2020.3)', 'main_office': '〒107-0052 東京都港区赤坂3−3−5', 'station': '赤坂見附', 'tel': '03-5545-0701', 'category': '水産・農林業', 'english_name': 'KYOKUYO\u3000CO.,LTD.', 'representative': '井上\u3000誠', 'date_of_incorporation': '1937年9月3日', 'market': '東証1部', 'date_of_listing': '1949年5月', 'balance_sheet_date': '3月末日', 'share_unit_number': '100', 'non_number_of_employees': '664', 'number_of_employees': '2307', 'average_age': '40.2', 'average_annual_income': '6970'}

数値にした方がいい箇所は、数値のみを取得するようにしています。
例えば、平均年収や平均年齢などです。

日付関連も8桁数値に変換しようかと考えました。
しかし、かなり以前に上場したような企業であれば、「1949年5月」という表記になっています。

そのため、設立年月日と上場年月日は各自で適当に加工してください。
また、数値がない場合は「0」となります。

まとめ

サンプルコードを見て不明な点があれば、過去の同シリーズをご覧ください。
また、メルカリのスクレイピングシリーズも参考になります。

メルカリのスクレイピング第1弾

メルカリのスクレイピング第2弾

メルカリのスクレイピング第3弾

メルカリのスクレイピング第4弾

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