禁止されているYahooファイナンスのWebスクレイピング【銘柄コード抽出】

禁止されているYahooファイナンスのスクレイピング【銘柄コード抽出】 プログラミング

Yahooファイナンスのスクレイピングシリーズ、第2弾です。
今回の第2弾では、実際にスクレイピングをしていきます。

具体的には、銘柄コードをスクレイピングで取得します。
実は、このような銘柄コードのリスト作成がスクレイピングの肝なのです。
このリストを作成できれば、あとはもう作業です。

本記事の内容

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

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

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

今回から、実際にスクレイピングを行っていきます。
前回の第1弾は、Yahooファイナンスをスクレイピングする上での準備を説明しました。

第1弾

第1弾の記事は、スクレイピングをする上での心得を書いてます。
何度でも言いますが、スクレイピングは危険な技術になり得ます。

やり方次第でDos攻撃のように、相手方サイトへの嫌がらせになりかねません。
最悪、威力業務妨害で逮捕される可能性すらあります。

だからこそ、何度も当ブログ内でスクレイピングの危険性を述べています。
同時に、スクレイピングの正当性(違法ではない)も主張しています。

結局、知らないから恐れるのです。
正体がわからないから、恐れの対象になってしまいます。
スクレイピングの正体を知れば、その危険性を認識して恐れないようになれます。

あとは、Yahooファイナンスをスクレイピングするための準備に関して説明しています。
そのため、準備が整っていない場合は、スクレイピングするための環境を整えてください。

では、スクレイピングするための準備が整ったら、次へ進みましょう。

銘柄一覧ページのスクレイピング仕様

Yahooファイナンスのトップページ
https://stocks.finance.yahoo.co.jp/

ざっと見たところ、以下の3点で考える必要があります。

  • 銘柄一覧ページの特定
  • 改ページ処理の対応
  • 各行のHTML構造

それぞれを説明します。

銘柄一覧ページの特定

トップページを見たところ、業種別・50音で銘柄一覧に遷移できます。

業種別のリンク一覧

50音のリンク一覧

業種別のリンク一覧から、「水産・農林業」をクリックした場合のURL
https://stocks.finance.yahoo.co.jp/stocks/qi/?ids=0050

50音のリンク一覧から、「あ」をクリックした場合のURL
https://stocks.finance.yahoo.co.jp/stocks/qi/?js=%E3%81%82

このURLを見ると、ids・jsで絞り込み条件を切り替えているだけですね。
ということは、何もない場合は絞り込み条件なしになるはず。
つまり、全銘柄の一覧が表示されることになるはず。

なにもつけないURL
https://stocks.finance.yahoo.co.jp/stocks/qi/

上記URLにアクセスすると、以下の銘柄数となります。

これが本当に全銘柄なのかどうかの確証はありません。
しかし、少なくとも業種別(3330件)と50音(3826件)からアクセス可能な件数よりは多いです。
(※2021年2月11日時点の数値)

正直、このようなデータ設計はとても気持ち悪いです。
件数は、過不足なしが理想と言えます。

ちなみに、「なにもつけないURL」では次のような銘柄が含まれています。
当然、以下の銘柄にはカテゴリーである業種がありません。

そのような銘柄を許すデータの持ち方をしているとも言えます。
この部分でも、個人的には「どうかなー」と感じます。

「その他」や「業種なし」などのカテゴリーに放り込めばいいと思うのです。
そうすれば、業種別(3330件)も過不足なしで3849件になります。

とりあえず、現時点ではこれ以上は追いません。
「なにもつけないURL」が銘柄コードを最も多く取得できます。

数を削る選別なんて、あとからでも何とでもなります。
まずは、より多くを取得できる方を選択するべきです。

よって、「なにもつけないURL」から銘柄コードを抽出していきましょう。

改ページ処理の対応

各ページのURL自体は、単純です。
次のような形式で各ページURLを自動的に作成することが可能です。

https://stocks.finance.yahoo.co.jp/stocks/qi/?&p=●

「●」はページ数となります。
この場合の最終ページは、193ページです。

193という数値は、次の計算式より求められます。
3849件 ÷ 20件 = 193ページ(192.45)

ただ、本ブログではこのURLを自動作成する方法を採用しません。
これは、人間が行う処理ではなくプログラム的な動きそのものです。

短時間に大量なアクセスを行うのは、危険でしたよね?
危険を避けるためにも、人間的な動作を目指すべきです。

よって、人間が行うやり方で改ページを行っていきましょう。
具体的には、「次へ」リンクをクリックするのです。

もちろん、クリックはプログラムで行います。

「次へ」をクリック→スクレイピング→「次へ」をクリック→スクレイピング→・・・

これを繰り返すのです。
この動きを取れば、人間的な動きになります。

そして、相手側サイトからもボット的な動きだと感知されにくくなります。

各行のHTML構造

各行のHTML構造を確認しておきます。

正直、古い書き方です。

令和の時代になっても、tableタグが現役であることにびっくりです。
それに、少しスクレイピングがやりにくい構造となっています。

でも、今回は銘柄コードだけの抽出だけとなります。
そのため、class名「yjM」を利用してスクレイピングできそうです。

銘柄一覧ページから銘柄コードを抽出する

銘柄一覧ページから銘柄コードを抽出するコードは、以下。
現時点(2021年2月11日)では元気に動いています。

サンプルコード

import bs4
import traceback
import re
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains

# ドライバーのフルパス
CHROMEDRIVER = "chromedriver.exeのパス"
# 改ページ(最大)
PAGE_MAX = 2
# 遷移間隔(秒)
INTERVAL_TIME = 3

# ドライバー準備
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')
    # print(soup)
    try:
        info = []
        table = soup.find("table", class_="yjS")

        if table:
            elems = table.find_all("tr")
            for elem in elems:
                td_tag = elem.find(class_="yjM")

                if td_tag:
                    a_tag = elem.find("a")

                    if a_tag:
                        href = a_tag.attrs['href']
                        match = re.findall("\/stocks\/detail\/\?code=(.*)$", href)

                        if len(match) > 0:
                            item_id = match[0]
                            info.append(item_id)

        return info

    except Exception as e:

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

        return None


# 次のページへ遷移
def next_btn_click(driver):
    try:
        # 次へボタン
        elem_btn = WebDriverWait(driver, 10).until(
            EC.visibility_of_element_located((By.CLASS_NAME, 'listNext'))
        )

        # クリック処理
        actions = ActionChains(driver)
        actions.move_to_element(elem_btn)
        actions.click(elem_btn)
        actions.perform()

        # 間隔を設ける(秒単位)
        time.sleep(INTERVAL_TIME)

        return True

    except Exception as e:

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

        return False


if __name__ == "__main__":
    # 対象ページURL
    page = "https://stocks.finance.yahoo.co.jp/stocks/qi/"

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

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

    # ページカウンター制御
    page_counter = 0

    while result_flg:
        page_counter = page_counter + 1

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

        # データ保存
        print(data)

        # 改ページ処理を抜ける
        if page_counter == PAGE_MAX:
            break

        # 改ページ処理
        result_flg = next_btn_click(driver)
        source = driver.page_source

    # 閉じる
    driver.quit()

プログラム詳細は、「銘柄一覧ページのスクレイピング仕様」とコメントをご覧ください。
また、Seleniumが関わる部分は、関数化しています。

そのため、Seleniumに関してはあまり理解する必要がありません。
つまり、Pythonだけの知識でなんとかなるコードと言えます。

では、何か所かピックアップしてプログラムの説明をしておきます。
まずは、以下。

    # 対象ページURL
    page = "https://stocks.finance.yahoo.co.jp/stocks/qi/"

ここに記述したページが起点となります。
このURLは、「銘柄一覧ページのスクレイピング仕様」で決定したURLです。

あとは、以下のコード部分でしょうかね。

# ドライバーのフルパス
CHROMEDRIVER = "chromedriver.exeのパス"
# 改ページ(最大)
PAGE_MAX = 2
# 遷移間隔(秒)
INTERVAL_TIME = 3

CHROMEDRIVERに関しては、わからない場合は次の記事をご覧ください。

PAGE_MAXとINTERVAL_TIMEに関しては、次の記事で説明しています。

簡単に言うと、スクレイピングで暴走しないための設定です。
現状の設定だと、最大で2ページまでしかスクレイピングしません。

また、改ページの際に3秒間の待機をしています。
そのため、このプログラムを何も知らない人が動かしても、何も問題は起こりません。

スクレイピングは、危険な技術でもあります。
だから、動かす場合は、自己責任で動かしてくださいというメッセージでもあります。

実行結果

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

['1301', '1332', '1333', '1352', '1375', '1376', '1377', '1379', '1380', '1381', '1382', '1383', '1384', '1400', '1401', '1407', '1413', '1414', '1417', '1418']
['1419', '1420', '1429', '1430', '1431', '1433', '1434', '1435', '1436', '1438', '1439', '1443', '1444', '1446', '1447', '1448', '1449', '1450', '1451', '1491']

全部で40件の銘柄コードを表示しています。
ページで言えば、2ページ分です。

まとめ

コードの補足をしておきます。
実行結果では、銘柄コードが表示されています。

プログラムで言えば、次の箇所ですね。

        # データ保存
        print(data)

ここの部分は、自由にコーディングしてください。
ファイルに保存するもよし、DBに保存するもよしです。

各自の好きな形で銘柄コードを保存してください。
個人的には、MongoDBに保存しています。

WindowsへのMongoDBのインストールは、次の記事を参考にしてください。

MySQLやMariaDBに保存したいという場合は、次の記事が参考になります。

最後に、私自身が実際に動かす場合の設定を記載しておきます。

# 改ページ(最大)
PAGE_MAX = 99999
# 遷移間隔(秒)
INTERVAL_TIME = 1

PAGE_MAXを99999に設定しても、193ページでプログラムは終了します。
また。遷移間隔は1です。

1秒は、人間が行える動作の範疇だと思います。
実際、1秒設定にしてボット判定(アクセス禁止)を受けたことはありません。

酷いプログラムだと、1ミリ秒単位でアクセスを繰り返します。
そのような場合は、IP単位でアクセス禁止の処分を受ける可能性もあります。

その処分は、数か月に及ぶこともあり得ます。
固定IPなら絶望ですが、動的IPならなんとかセーフでしょう。

でも、それはDoS攻撃と何ら変わりません。
最悪、威力業務妨害となりえます。

よって、INTERVAL_TIMEはどれだけ短くても1秒を厳守しましょう。

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