株価とスクレイピング③

目的:株探のWEBページを1ページずつ取得し、各データを取得する

目標
1 日経平均の銘柄・コード一覧から、株探のサイトを1企業ずつスクレイピング
2 データを整形
3 エクセルへ出力

株探のWEBページを1ページずつ取得し、各データを取得する

 株探のサイトのURLは、ベースURL+証券コードの形になっているので、データが欲しい証券コードを準備し、1ページずつHTMLを取得しながら、データを抜き出していく
 今回は日経平均の銘柄の各データを抽出して入力する
 株探のサイトはスクレイピングについて明記されていない
 みんかぶのサイトでも良かったが、更新が株探より遅かったので株探を利用した

プログラム実行順

1,必要なものをインポート

手順1のコード
import pandas as pd
import urllib.request
from bs4 import BeautifulSoup as bs

内容
import pandas as pd   データフレーム(2次元配列)を扱うライブラリ
import urllib.request  urllib.request.urlopen(URL)でURLにアクセス
from bs4 import BeautifulSoup  
 bs4ライブラリからBeautiful Soupをインポート
 HTML文字列(ファイル)を解析することができる

urllib.request.urlopen(URL)でWebからHTMLファイルを取得して、それをBeautiful Soup 4(bs4)に渡すとBeautiful Soupオブジェクトを生成する
そのオブジェクトを検索したり抽出したりすることで、必要なデータを抜き出す(スクレイピング)ことができる

2,証券コード一覧のエクセルデータからコードだけを抜き出しリスト化

手順2のコード
book_df = pd.read_excel(r"エクセルのパス",sheet_name = 0, index_col = 0)
code = book_df["証券コード"].to_list()
base_url = "https://kabutan.jp/stock/?code="

内容

前回、②で作成したデータを使う

 pd.read_excel(r"エクセルのパス",sheet_name = 0, index_col = 0)
 エクセルデータをそのままデータフレームに出力、sheet_name = 0で1枚目のシートを指定
 index_col = 0で先頭行をインデックスに指定、指定しないと新しくインデックスが作成されてしまう
 code = book_df["証券コード"].to_list()
 コード一覧のリストを作成 .list()で、データフレームから証券コードの列を抜き出し、1列のリスト化する
 base_url = "https://kabutan.jp/stock/?code="
https://kabutan.jp/stock/?code=4666 
 のように、4桁のコードを加えるだけで、その企業の株探のサイトURLになる
 base_url + code でURLの完成になる

3,HTMLから欲しいデータを検索して取得する

手順3のコード
#空のリストを作成
zika = []  
kabuka = []
per = []
rimawari = []
uriage = []
keitune = []
dekidaka = []
#for文で1企業ずつ各データを取得する
for i in code:
    url = base_url + str(i)
    data = urllib.request.urlopen(url)
    soup = bs(data, 'html.parser')

    zika.append(soup.find("td",class_="v_zika2").text)
    kabuka.append(soup.find("span",class_="kabuka").text)

 table_5 = soup.find_all("table")[4]
    dekidaka.append(table_5.find("tr").find("td").text)

    table = soup.find_all("table")[2]
    rows = table.find_all("tr")
    col =[v.text for v in rows[1].find_all("td")]

    per.append(col[0])
    rimawari.append(col[2])

    uriage.append(pd.read_html(url)[10].iloc[2,1])
    keitune.append(pd.read_html(url)[10].iloc[2,2])

内容と書式
 WEBページ上で、右クリックするとページのソースを表示でHTMLデータを確認することができる
 欲しいデータの空のリストを作成
for文で1企業ずつ各データを取得する
証券コードのリストからコードを抜き出し、str()で文字列変換してURLに、HTMLを取得して、欲しいデータを抜き出す
for i in code:
    url = base_url + str(i)
    data = urllib.request.urlopen(url)
    soup = bs(data, 'html.parser')

最終的にデータフレームにしたいので、どれも1列のリスト化を目指している
最後に合わせてデータフレーム化 → エクセルに出力
今回はデータ数が同じことがはっきりしているので簡単に合わせられる

時価総額と株価をHTMLタグから取得する
 株価と時価総額は、わかりやすいタグがついていたので、クラス指定で抽出

<th colspan="2" class="v_zika1">時価総額</th> <td colspan="2" class="v_zika2">3,168<span>億円</span></td>

<span class="favorite"><a href="#" class="add-favorite-stock-btn"></a></span>
<span class="kabuka">1,852円</span>

↑のようなHTMLからデータを取得する

・時価総額
 zika.append(soup.find("td",class_="v_zika2").text)
<td>タグがついていて、クラス属性"v_zika2"を指定、find().textでテキストのみを抽出する
  リスト名.appendで、空のリストに取得した要素を加えていく

・株価
    kabuka.append(soup.find("span",class_="kabuka").text)
同様に、<span>タグ、クラス指定で株価を取得、リスト化

出来高、PER、利回りの取得
株探のサイトは、テーブルで構成されているデータが多いので、HTMLからtableタグ指定、またはpandasのpandas.read_html()でテーブルをそのままデータフレームとして取得できる

株探のサイト、テーブルデータになっている部分が多い

import pandas as pd
print(pd.read_html("株探のサイトのURL"))

このコードでWEBページのテーブルデータを一括で取得できる
取得したテーブル、インデックスを数えて、
何番目のテーブルに欲しいデータがあるか確認する

内容
 
5番目のテーブルの1番目(1,1セル)に出来高のデータがあるので、まずは
 table_5 = soup.find_all("table")[4]
 で5番目のテーブルデータをtableタグから一括で抜き出す
 [4]で5番目のテーブルを指定
 table_5.find("tr").find("td")
 で、最初の tr タグの、最初の td タグのデータを取得できる

<table>
 <tbody>
  <tr>
   <th scope='row'>出来高</th>
   <td>323,600&nbsp;株</td>
  </tr>

実際に抽出するテーブル部分のHTML

~.textで文字列だけリスト化する
dekidaka.append(table_5.find("tr").find("td").text)

PERと利回りは、3つ目のテーブルにあるので、まずはテーブルデータを一括で取得

table = soup.find_all("table")[2] #3つ目のtableの取得
rows = table.find_all("tr")
col =[v.text for v in rows[1].find_all("td")]
#PBR、PER、利回り、信用倍率をリスト化している(1行だけ抜き出し)

ここでは、find_allはResultSetなので、find().find()のように、find_allを重ねて使えない → [1]でテーブルの2行目を指定してリストに変換して扱う
 ※rows は 2行4列のResultSet
 下記参照

<table>
 <thead>
  <tr>
   <th scope='col'><abbr title="Price Earnings Ratio">PER</abbr></th>
   <th scope='col'><abbr title="Price Book-value Ratio">PBR</abbr></th>
   <th scope='col'>利回り</th>
   <th scope='col'>信用倍率</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>132<span class="fs9">倍</span></td>
   <td>20.47<span class="fs9">倍</span></td>
   <td>-<span class="fs9">%</span></td>
   <td>1.52<span class="fs9">倍</span></td>
  </tr>

抜き出す部分のテーブルのHTML

[v.text]で文字列だけリスト化されているので、
リストの中の1番目と3番目のデータを抽出してそのままリスト化
per.append(col[0])
rimawari.append(col[2])

売り上げと経常利益の取得

最新のデータのみを抽出したかったが、タグが同じで差別化する方法がわからなかったので、ここではpandasからそのままテーブルデータを取得した

抜き出したいテーブル(11番目)

import pandas as pd
df = pd.DataFrame(pd.read_html(”株探のサイトURL”)[10])
df.to_excel(r"エクセルのパス")

11番目のテーブルを取得してエクセルに出力するコード
実行結果

データフレーム(エクセル)の3行目、2列目が売り上げ
3行目3列目が経常益なので、~.iloc[行,列]で欲しいデータのセルを指定してリスト化
インデックスは0から始まっている

内容
    uriage.append(pd.read_html(url)[10].iloc[2,1])
    keitune.append(pd.read_html(url)[10].iloc[2,2])
※HTMLタグから順番に指定していっても同じことができる

4,データフレームを作成し、元のエクセルへデータを重ねて出力

手順4のコード
df = pd.DataFrame({"時価総額":zika,"株価":kabuka,"売上高":uriage,"経常利益":keitune,"RER":per,"利回り":rimawari,"出来高":dekidaka})

dfs = pd.concat([book_df,df],axis = 1)
dfs.to_excel(r"エクセルのパス")

内容
 各項目がすでにリスト化されていて、今回はデータ数が同じことがわかっているので、列名を指定しながらデータフレームにする

 df = pd.DataFrame({"時価総額":zika,"株価":kabuka,"売上高":uriage,"経常利益":keitune,"RER":per,"利回り":rimawari,"出来高":dekidaka})

最初にエクセルから読み込んだ book_df と作成した df の2つのデータフレームをpd.concat([ , ], axis =  ) で統合
ラベルも含めてデータ数が同じであることが前提
axis = 0 or 1 で行を追加するか列を追加するか指定
エクセルへ出力
 dfs = pd.concat([book_df,df],axis = 1)
 dfs.to_excel(r"エクセルのパス")

 今回は作成しながら、トライ&エラーをするので新規シートへ出力した、pandas.ExcelWriterで、新規シートに保存することもできる
 また、エクセルのパスを読み込んだものと同じにすると、データが上書きされる(※元データは消えることに注意)

コード全体

import pandas as pd
import urllib.request
from bs4 import BeautifulSoup as bs
#import requests
#import lxml.html
#import unicodedata

book_df = pd.read_excel(r"エクセルのパス",sheet_name = 0, index_col = 0)
code = book_df["証券コード"].to_list()

#ベースURL
base_url = "https://kabutan.jp/stock/?code="

#空のリストを作成
zika = []
kabuka = []
per = []
rimawari = []
uriage = []
keitune = []
dekidaka = []

#for文で1企業ずつ各データを取得する
for i in code:
    url = base_url + str(i)
    data = urllib.request.urlopen(url)
    soup = bs(data, 'html.parser')

    zika.append(soup.find("td",class_="v_zika2").text)
    kabuka.append(soup.find("span",class_="kabuka").text)

    table_5 = soup.find_all("table")[4]
    dekidaka.append(table_5.find("tr").find("td").text)
   
    table = soup.find_all("table")[2]
    rows = table.find_all("tr")
    col =[v.text for v in rows[1].find_all("td")]
   
    per.append(col[0])
    rimawari.append(col[2])

    uriage.append(pd.read_html(url)[10].iloc[2,1])
    keitune.append(pd.read_html(url)[10].iloc[2,2])
    
df = pd.DataFrame({"時価総額":zika,"株価":kabuka,"売上高":uriage,"経常利益":keitune,"RER":per,"利回り":rimawari,"出来高":dekidaka})

dfs = pd.concat([book_df,df],axis = 1)

dfs.to_excel(r"エクセルのパス")
結果

今後の課題

・for文のリスト内包表記がよくわからない
・HTMLタグの<> A <>   <>  <>のAの場所のみの抽出がわからない
・どの方法で抽出するのがベスト(時間が早い)かわからない

この記事が気に入ったらサポートをしてみませんか?