株価とスクレイピング②

目的:webスクレイピングの活用とエクセルへ出力

目標
1 日経平均プロフィルというwebページから、日経平均の銘柄・コードを取得
2 データを整形
3 エクセルへ出力

webスクレイピングのやり方について

 前回はpandas_datareaderという、いくつかのサイトからデータを抜き出す専用のモジュールを使用した。
 これでは、抜き出すデータやWEBサイトが限定されるので、どのWEBサイトでも使えるように、次のような手順でスクレイピングを行う必要がある。
 
 1,対象のWEBページのHTMLをデータコピーして取得(クローリング)
 2,WEBページの解析(パース)、特定のデータの抽出
 3,抽出したデータの整形や保存など

スクレイピングに使用されるライブラリ

Requests
 WEBページを取得することができる
Beautiful Soup 
 取得したWEBページの情報を解析(パース、parse)することができる
Selenium
 人がWEBページを閲覧しているように、ブラウザソフト(WEBドライバ)を使用して、情報を取得、解析することができる。動作が遅い

日経平均について

 日本経済新聞社が選定した225社の株価を係数をかけて除数で割ったもの
  日経平均株価 = (株価 × 株価換算係数)の225社の合計/除数
 日経平均プロフィルは日経平均の公式サイト、データの二次利用を禁止しているので、スクレイピングは明記されていないが、グレーゾーン
 個人利用はOK

プログラム実行順

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

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

内容
import pandas as pd   データフレーム(2次元配列)を扱うライブラリ 
import urllib.request        urllib.request.urlopen(URL)でURLにアクセス
 ※取得できる情報は色々あるみたいだが、HTTPのことがまだわからない
from bs4 import BeautifulSoup  
 bs4ライブラリからBeautiful SoupをインポートHTML文字列(ファイル)を解析することができる。

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

2,日経平均のサイトからHTMLを取得

手順2のコード
url = "https://indexes.nikkei.co.jp/nkave/index/component?idx=nk225"
data = urllib.request.urlopen(url)
soup = BeautifulSoup(data, 'html.parser')

内容
 日経平均プロフィルの225銘柄一覧ページのURLを指定
 urllib.request.urlopen(url)で、指定したサイトのHTMLを取得、Beautiful Soup 4(bs4)に渡すとBeautiful Soupオブジェクトを生成することができる
このオブジェクトを検索したり抽出したりすることで、必要なデータを抜き出す(スクレイピング)ことができる
 書式
 BeautifulSoup(解析するデータ, 'html.parser')
 html.parserはHTMLを解析するライブラリ、他にもlxmlやxmlを指定することができる

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

手順3のコード
nikkei_name = soup.find_all("div",class_="hidden-xs col-sm-8")
nikkei_code = soup.find_all("div",class_="col-xs-3 col-sm-1_5")
#nikkei_code = soup.select("div[class= 'col-xs-3 col-sm-1_5']")

#for文で回して、リストにテキストだけを残して入力していく
retu_1 = []
for i in nikkei_name:
   retu_1.append(i.text)
retu_2 = [a.text for a in nikkei_code]

内容
 WEBページ上で、右クリックするとページのソースを表示でHTMLデータを確認することができる
 今回は、銘柄名と証券コードを抜き出す
  銘柄名は
   <div class="hidden-xs col-sm-8"> 銘柄 </div>
  証券コードは
   <div class="col-xs-3 col-sm-1_5"> 証券コード </div>
のHTMLタグがついているので、find_all()関数で、タグがついているすべてのデータを取得する(find()で、最初のデータのみを取得できる)

書式
 soup.find_all("div",class_="hidden-xs col-sm-8")
 ”div” divというタグがついた全データを対象にする
 class_=   class関数と重複するため、HTMLのclass指定はアンダーバーをつける
 この書式だと、"hidden-xs col-sm-8"というクラス名を指定してデータを検索する
 つまり、タグ div  クラス名 hidden-xs col-sm-8 の全データを取得
 タグはリスト指定すると、まとめてデータを取得することもできる、ただし、データは1行になる
 HTMLは階層構造になっており、html – head – title - テキストのようになっているので、上位のタグを取得するとテキストも取得できる
 ※HTMLは未勉強なので詳しくはわからない
 
 書式はいくつも書き方があり、nikkei_code = soup.select("div[class= 'col-xs-3 col-sm-1_5']")でも同じデータが取得できる
 ※いっぱいありすぎてよくわからなかった

find_all()で取得したデータは bs4.element.ResultSet という形でリスト化されるため、タグごとオブジェクトとして入ってしまう
そこで、テキスト部分を抜き出す必要がある

タグを含んだデータとして入力される

書式
  retu_1 = []      空のリスト作成
  for i in nikkei_name:
       retu_1.append(i.text)   
 要素を1つずつ取り出し、i.textでテキストだけを空のリストに入れていく
  retu_2 = [a.text for a in nikkei_code]
  retu_2は書式違い
 新しくリストを作らず、元のリストに戻すこともできるが整理がつかなかったのでこの形で

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

手順4のコード
df = pd.DataFrame(retu_1,columns = ["銘柄"])
df["証券コード"] = retu_2
df = df[~df['証券コード'].str.contains('コード')]
df = df.reset_index(drop=True)

print(df.head(10)) ※確認のため
df.to_excel(r"エクセルのパス") ※エクセルへ出力

内容
find_all()ですでにリスト化されているので、列名を指定しながら、1列ずつデータフレームに追加する
※注意 今回はデータ数が同じことがあらかじめわかっている

 銘柄名と証券コードの中に、同じタグで「社名」「コード」という行が入ってしまうため、検索して削除する
 書式
  df = df[~df['証券コード'].str.contains('コード')]  

 df['証券コード'].str.contains('コード')で、証券コード列の「コード」の文字列を検索する
 「~」は否定を意味する演算子
 この書き方で、証券コードの列の「コード」という文字列がある行を削除するという意味になる
 つまり、特定の文字を含む列を抜いたデータフレームを作り直すことができる。ただし、インデックス番号ごと抜けるのでインデックスを振りなおす

書式  インデックスをふり直し
df = df.reset_index(drop=True)
drop=Trueで元のインデックスを削除する

コード全体

import pandas as pd
import urllib.request
from bs4 import BeautifulSoup

url_1 = "https://indexes.nikkei.co.jp/nkave/index/component?idx=nk225"

data_1 = urllib.request.urlopen(url_1)

# HTMLを解析して取得

soup = BeautifulSoup(data_1, 'html.parser')

nikkei_name = soup.find_all("div",class_="hidden-xs col-sm-8")
nikkei_code = soup.find_all("div",class_="col-xs-3 col-sm-1_5")

#for文で回して、リストにテキストだけを残して入力していく

retu_1 = []
for i in nikkei_name:
    retu_1.append(i.text)

retu_2 = [a.text for a in nikkei_code]
    

df = pd.DataFrame(retu_1,columns = ["銘柄"])


#mylist = list(zip(retu_1,retu_2))


df["証券コード"] = retu_2

df = df[~df['証券コード'].str.contains('コード')]

df = df.reset_index(drop=True)


print(df.head(10))


df.to_excel(r"エクセルのパス")
証券コードは文字列として入力されている


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