[python]  Webスクレイピング  -BeautifulSoup-

# はじめに
場合によってはWebから情報を自動的に得ることも必要となる。
例えば、株価の情報など特定の値を持つデータ
ここではpythonを用いてWebから情報を集める方法の基本的なことをまとめた

# 環境
windows 10
python version:  3.7.8
Microsoft Edge
Yahoo Finance

# Code

```python
import requests
from bs4 import BeautifulSoup
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821152/6423b487-8ac8-bd73-9098-bc0def9f1861.png)
対象は何をしても大丈夫そうなYahoo Finance
あの大きいリアルタイム株価の情報だけをゲットしたい。
情報を得ることが成功したら396.2の値を得るに違いない。

# URL接続 -requests-
まずURLを通してページ上の情報を得る。
```python
data = requests.get('https://finance.yahoo.co.jp/quote/4689.T')
print(data)
# <Response [200]>
```
問題がなければ成功コードが帰ってくる

```python
data_error = requests.get('https://finance.yahoo.co.jp/quote/99999999')
print(data_error)
# <Response [404]>
```
存在しないURLを入力するとエラーコードが帰って来る

```python
print(data.text)
# 人間が読めないような大量のデータが出てくる
```
```
...部分抜き取り
areChart":{"selectedItems":[],"form":[{"code":"","isExisted":true},{"code":"","isExisted":true},{"code":"","isExisted":true},{"code":"","isExisted":true}],"compareCodes":"","isError":false,"notice":""},"mainStocksHistory":{"history":{"yearHighPrice":"","yearHighPriceDate":"","yearHighPriceFlag":false,"yearLowPrice":"","yearLowPriceDate":"","yearLowPriceFlag":false,"histories":[],"splitHistories":[]},"paging":{"hasNext":false,"totalPage":0,"totalSize":0,"page":0,"size":0,"startIndex":0,"endIndex":0,"displayedPageNumbers":[]},"marginLink":"","selected":{"fromDate":"20211027","toDate":"20221027","timeFrameId":"d"},"filtered":{"fromDate":"20211027","toDate":"20221027","timeFrameId":"d"},"isLoading":false,"isError":false},"mainStocksMarginHistory":{"historyLink":"","histories":[],"paging":{"hasNext":false,"totalPage":0,"totalSize":0,"page":0,"size":0,"startIndex":0,"endIndex":0,"displayedPageNumbers":[]},"isLoading":false,"isError":false},"mainStocksNews":{"hasPaidArticle":false,"articles":[],"paging":{"hasNext":false,"totalPage":0,"totalSize":0,"page":0,"size":0,"startIndex":0,"endIndex":0,"displayedPageNumbers":[]},"isLoading":false,"isError":false,"stockForecast":{"text":"","link":""}},"mainStocksProfile":{"items":[]},"incentive":{"incentiveInfo":null,"positiveCompanyList":[]},"pageInfo":{"code":"4689.T","contentsId":"4689","spaceId":"2079486148","isUnsupportedBrowser":false,"jwtToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2Njc0NzQ2NTAsImV4cCI6MTY3MDA2NjY1MH0.vPyV70KD48Yky0j3CgTJQrThlnc13cnxOV8hPD77upY","stocksJwtToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2Njc0NzQ2NTAsImV4cCI6MTY3MDA2NjY1MH0.HCuF9m8IXfCinonPvqUHCKU-zyxAYRA2hVIpI7QoWyY","chartJwtToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2Njc0NzQ2NTAsImV4cCI6MTY3MDA2NjY1MH0.vz0a4E9v9T47WaUPxuEd1n2cqQpyjAxflPUh6cEXAKk","currentPageLink":"https://finance.yahoo.co.jp/quote/4689.T","currentKey":"stocksContents","currentDateTime":1667474650618,"currentYear":"2022","loginUrl":"https://login.yahoo.co.jp/config/login?.src=quote&.done=https%3A%2F%2Ffinance.yahoo.co.jp%2Fquot
...
```
 
## HTML情報の獲得 -BeautifulSoup- 
```python
soup = BeautifulSoup(data.content, 'html.parser')
print(soup)
# まだ読みにくが、htmlの形式としてデータを得る
```
```
...部分抜き取り
表示しています。</li><li class="W7zdvb-0">用語の意味については「<a data-ylk="pos:31" href="https://finance.yahoo.co.jp/feature/special/dictionary/">用語の説明</a>」をご覧ください。</li><li class="W7zdvb-0">時系列データは<a data-ylk="pos:32" href="https://www.jpx.co.jp/corporate/about-jpx/profile/01.html" rel="noopener noreferrer" target="_blank">東京証券取引所</a>、<a data-ylk="pos:33" href="https://www.jpx.co.jp/corporate/about-jpx/profile/02.html" rel="noopener noreferrer" target="_blank">大阪取引所</a>、<a data-ylk="pos:34" href="http://www.nse.or.jp/" rel="noopener noreferrer" target="_blank">名古屋証券取引所</a>、<a data-ylk="pos:35" href="https://www.nri.com/jp" rel="noopener noreferrer" target="_blank">野村総合研究所</a>、<a data-ylk="pos:36" href="https://www.morningstar.co.jp/" rel="noopener noreferrer" target="_blank">モーニングス
ター</a>、<a data-ylk="pos:37" href="https://www.refinitiv.com/ja" rel="noopener noreferrer" target="_blank">リフィニティブ・ジャパン</a>から 
の提供を受けています。</li></ul><p class="_3vKzMSn5">情報提供会社のリンクは、すべて外部サイトへ移動します。</p><button aria-label="閉じる" class="_1pHBxEmL" type="button"><span aria-hidden="true" class="_1OlVoYo8 _2LfiAUqo"></span></button><div class="_1q5J03zs"></div></div></div></div></div></div></div></div></section></div><ul class="_2OTjANE6 _2PhgeJmM" id="support"><li class="InifRxXF"><a href="https://privacy.yahoo.co.jp/">プライバシー</a></li><li class="InifRxXF"><a href="https://about.yahoo.co.jp/common/terms/">利用規約</a></li><li class="InifRxXF"><a href="https://marketing.yahoo.co.jp/">広告掲載について</a></li><li 
...
```



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821152/002a7831-a651-3618-475b-c86d90801e82.png)

ここで、HTMLから必要な情報を取り出すために、情報が所属しているid, classを特定する必要がある
空白を右クリック → 開発者ツールで調査する



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821152/14cebe44-32eb-6f91-d730-6977e388a700.png)

開発ツールの左上の要素を選択をクリック、もしくはCtrl + Shift + C でも選択することができる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821152/3c23af5c-70e8-0b4b-c7d9-6e42fd358089.png)
必要な情報を真上にカーソルを置くことでHTMLの情報を表示される
株価のHTML情報を見たところ、`span` の`class = '_3rXWJKZF'`であった。
よって次の方法で`span`かつ`class = '_3rXWJKZFe'`を満たす、すべての情報得る
```python
print(soup.find_all('span', class_="_3rXWJKZF"))
```

```
[<span class="_3rXWJKZF">396.2</span>, <span class="_3rXWJKZF">-1.4</span>, <span class="_3rXWJKZF">-0.35</span>, <span class="_3rXWJKZF _11kV6f2G">397.6</span>, <span class="_3rXWJKZF _11kV6f2G">396.6</span>, <span class="_3rXWJKZF _11kV6f2G">397.1</span>, <span class="_3rXWJKZF _11kV6f2G">388.5</span>, <span class="_3rXWJKZF _11kV6f2G">25,406,200</span>, <span class="_3rXWJKZF _11kV6f2G">9,983,203</span>, <span class="_3rXWJKZF _11kV6f2G">317.6～477.6</span>, <span class="_3rXWJKZF">---</span>, <span class="_3rXWJKZF">---</span>, <span class="_3rXWJKZF _11kV6f2G">3,024,300</span>, <span class="_3rXWJKZF _11kV6f2G">7,633,266,686</span>, <span class="_3rXWJKZF _11kV6f2G">1.40</span>, <span class="_3rXWJKZF _11kV6f2G">5.56</span>, <span class="_3rXWJKZF _11kV6f2G">---</span>, <span class="_3rXWJKZF _11kV6f2G">1.10</span>, <span class="_3rXWJKZF _11kV6f2G">---</span>, <span class="_3rXWJKZF _11kV6f2G">369.02</span>, <span class="_3rXWJKZF _11kV6f2G">39,620</span>, <span class="_3rXWJKZF _11kV6f2G">100</span>, <span class="_3rXWJKZF _11kV6f2G">686</span>, <span class="_3rXWJKZF _11kV6f2G">371</span>, <span class="_3rXWJKZF">31,746,800</span>, <span class="_3rXWJKZF">+1,711,200</span>, <span class="_3rXWJKZF">23.15</span>, <span class="_3rXWJKZF">1,371,400</span>, <span class="_3rXWJKZF">+117,500</span>]
https://finance.yahoo.co.jp/quote/4689.T/chart?trm=6m
```
条件を満たす情報のすべてがlist形式で出てくる
```python
print(soup.find_all('span', class_="_3rXWJKZF")[0].text)
# 396.2
```
データをよくみると0番目に`396.2`と、現在の株価と一致する値が出てきた。
残念ながら`span`かつ`class = '_3rXWJKZF'`を満たすものは、現在の株価のみではなく、複数あった。
よってより範囲を絞って検索をする。

```python
ss  = soup.select('span ._3rXWJKZF')
```
`soup.select`は`soup.find_all`と同じであるが、追加的に範囲を絞ることができる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821152/096dcee5-089d-e732-3f98-e6aaecac6342.png)
ここで欲しいデータの部分をselectorコピーして`soup.select`で使用する
```python
ss = soup.select('#root > main > div > div > div.XuqDlHPN > div:nth-child(2) > section._1zZriTjI._2l2sDX5w > div._1nb3c4wQ > header > div.nOmR5zWz > span > span > span')
print(ss)
# [<span class="_3rXWJKZF">396.2</span>]
print(ss[0].text)
# 396.2
```
範囲を絞ることでlist型のデータに１つのみのデータを得られた
これで現在の株価のみをゲットすることができる

## 【補足】イメージ情報の獲得
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821152/58284d97-214b-c9e4-c168-3f4e52f33c6d.png)


```python
image= soup.select('#DetailChart__panel--3 > div > a')
print(image[0]['href'])
# https://finance.yahoo.co.jp/quote/4689.T/chart?trm=6m
```
同じようにしてイメージ (src) やリンク (href) などの情報も得ることができる


# まとめ
ここではpythonとBeautifulSoupを用いてWeb上の情報を得る方法を紹介した。
このように情報を得て、txtやcsvとして保存することで多量のデータを一気に扱うことができる。


# 関連

https://www.crummy.com/software/BeautifulSoup/bs4/doc/