【アマゾンで安価更新されたらＬＩＮＥに送信】をできなかった話

<h2>アマゾンンの価格SearchをしてどうせならLINEでおくる</h2>
アマゾンで安価更新されたらをＬＩＮＥに送信しようとしたらAmazonでスクレイピングは禁止されていた。


___結論___　スクレイピングでしようとしては駄目。禁止されていたのでAPIを使おう。

---
スクレイピングが禁止されているかどうかはrobot.txtが用意されているで確認します。

Amazon
https://www.amazon.co.jp/robots.txt

---
今回はスクレイピングをしたいと思っていたので日経平均株価に代替えする。

日経平均は下記で確認してスクレイピングをしても大丈夫とのこと確認
https://www.nikkei.com/robots.txt

---
補足
「User-agent」の部分は許可するユーザーを表しています。
表示されている「*」は全てのユーザーを許可するという意味です。
「Allow」と「Disallow」で判別。
「Allow」はスクレイピングを許可するという意味。
「Disallow」はスクレイピングを許可しないという意味。

<h2>修正、日経平均で安価更新されたらＬＩＮＥに送信</h2>

必要ライブラリ
import requests 
from bs4 import BeautifulSoup

持ってない人は
```php:terminal
pip install beautifulsoup4
```
持っているかどうかの確認
```php:terminal
pip freeze
```

<h2>日経新聞のほしい情報を検索</h2>
日経平均が＊＊＊＊円になった時にLineで通知を送るようにしたい。（既存のアプリがありそう）

https://www.nikkei.com/markets/kabu/

"https://www.nikkei.com/markets/kabu/"

※日本語は勝手に置き換わる


```php:Nikkei.py
import requests
from bs4 import BeautifulSoup

url = "https://www.nikkei.com/markets/kabu/"

def NikkeiPrice():
    
# URLにアクセスする 戻り値にはアクセスした結果やHTMLなどが入ったNikkeiPageが帰ってきます
    NikkeiPage = requests.get(url)

    # NikkeiPageからHTMLを取り出して、BeautifulSoupで扱えるようにパースします
    soup = BeautifulSoup(NikkeiPage, "html.parser")
    
    # <h2 class="m-headline_text">日経平均株価<span class="cmn-notes">（4日、大引、円）</span></h2>
    title = soup.find("h2",class_= "m-headline_text").get_text()    
    print(title)
    # <span class="mkc-stock_prices">33,422.52</span>
    price = soup.find("span",class_="mkc-stock_prices").get_text()
    print(price)
    

NikkeiPrice()

```


def NikkeiPrice():
//こいつに収納

soup = BeautifulSoup(NikkeiPage,"html.parser")
//URLの全ページをとってくる。HTMLを取り出して、BeautifulSoupで使えるようにしている。

開発ツールをひらいて欲しい情報を探すと下記が書かれている。
※Google chromeで開発ツールを開いて左上の矢印がかかれた絵をタッチして必要な情報を探す。
"h2 class="m-headline_text">日経平均株価<span class="cmn-notes">（4日、大引、円）</span>/h2"

---
__情報の取得方法1__
title = soup.find("h2",class_= "m-headline_text").get_text()    ※classの後の_に注意する    

__情報の取得方法2__
title = soup.select_one("#CONTENTS_MARROW > div.mk-top_stock_average.cmn-clearfix > div.m-headline > h2").get_text())

CSSセレクターを使って指定した場所のテキストを右クリックしてCopy→CopySelectorを選択しselect_one以下に張り付ける。※なおfirefoxなどではCSSセレクターはない。

__情報の取得方法3__
今回のページにはなかったがidの表記があればこちらを使用するのが一番手っ取り早い
title = soup.find(id="*****").get_text())

---

.textがないとテキスト化されないので注意

実行：戻り値にNikkeiPrice()がないとただObjectを作っただけになる。

soup = BeautifulSoup(NikkeiPage.content, "html.parser")にcontentがないと
TypeError: object of type 'Response' has no len()

---
補足

urlopen関数とは:
Pythonのurllibモジュールで使われるURLを取得するためのメソッド。urllibはURLを使用するためのモジュールでその中にurlopen関数が含まれている。

単純にrequests.get(URL)でもかまわない（import request)


select()はfind()などでも応用可能だが前者のほうが応用が利くような気がする

|タイプ|すべての要素をリストで返す|ひとつだけ要素を返す|引数（検索条件の指定）|
|:---|:---:|:---:|--:|
|find系|find_all()|find()|要素名, 属性指定（キーワード引数）|
|select系|select()|select_one()|CSSセレクタ|

---

パーサーにも種類がいくつかあるので他で代用可能
__html.parser__
利点：バッテリーが含まれている、まともな速度、寛大（Python 2.7.3および3.2以降）。
短所：それほど寛大ではない（Python 2.7.3または3.2.2より前）

__lxml__（import必要)pip3 install lxml
利点：非常に高速、寛大
短所：外部C依存

__html5lib__(import必要)pip install html5lib
利点：非常に寛大で、Webブラウザーと同じようにページを解析し、有効なHTML5を作成します
短所：非常に遅い、外部Python依存関係

---

requests.get(url)の代わりにurllib.request.urlopen(url)を使用してもいいがrequestsはより使いやすく、高レベル

---

補足２
同一のclassがあり二つ以上の値を取得してしまった時の対処法
例えば￥3640￥3640
converterdPrice = price[1:5]で指定してやると6つ目以降がなくなる。

---

<h2>カンマを取り除く</h2>
数値を比較するうえでカンマ（,）は不要なので取り除く。またString型の可能性があるのでfloat型（小数点なのでInt型は駄目）に訂正

```
ConvertedPrice = price.replace(",","")
floatPrice = float(ConvertedPrice)
```
なお"."と","の両方とも消したい場合
```
import re
ConvertedPrice = re.sub("[,.]","",price)
```

<h2>Lineに送信する処理をつくる</h2>

https://notify-bot.line.me/ja/

Lineと連携するので下記サイトにアクセス右上のマイページにアクセスしアクセストークンを発行し下記のように記載

```
 if(floatPrice > 3000):
        sendLineNotify()


def sendLineNotify():
    print("lineに通知がいきました")
    lineNotifyToken = "あなたのLineNotifyトークン"
    lineNotifyApi = "https://notify-api.line.me/api/notify"
    headers = {"Authorization": f"Bearer {lineNotifyToken}"}
    data = {"message": "今がお買い時です！https://www.nikkei.com/markets/kabu/"}
    requests.post(lineNotifyApi, headers=headers, data=data)
```

最後にsleep関数で自動処理を組み込む。下記は一時間ごとにトラッキング（検索サーチ、Pythonの実行）している。NikkeiPrice()の実行をwhile(True)：の下層におく
```
import time

while(True):
    print("トラッキングしました")
    time.sleep(60 * 60)
    NikkeiPrice()
```

最終的な記述は下記になる。floatPrice > 3000は実用化するならfloatPrice < 3000に変更
Lineのトークンは作るシステム毎に発行する。
```php:Nikkei.py
# coding: UTF-8
from bs4 import BeautifulSoup
import requests
import time

# アクセスするURL

url = "https://www.nikkei.com/markets/kabu/"

def NikkeiPrice():
    
# URLにアクセスする 戻り値にはアクセスした結果やHTMLなどが入ったNikkeiPageが帰ってきます
    NikkeiPage = requests.get(url)

    # NikkeiPageからHTMLを取り出して、BeautifulSoupで扱えるようにパースします
    soup = BeautifulSoup(NikkeiPage.content, "html.parser")
    
    # <h2 class="m-headline_text">日経平均株価<span class="cmn-notes">（4日、大引、円）</span></h2>
    title = soup.find("h2",class_= "m-headline_text").get_text()    
    print(title)
    # <span class="mkc-stock_prices">33,422.52</span>
    price = soup.find("span",class_="mkc-stock_prices").get_text()
    print(price)
    ConvertedPrice = price.replace(",","")
    # print(ConvertedPrice)
    
    floatPrice = float(ConvertedPrice)
    print(floatPrice)
    
    
    if(floatPrice > 3000):
        sendLineNotify()


def sendLineNotify():
    print("lineに通知がいきました")
    lineNotifyToken = "Zcccv5BLFd61X8p6ScmczmJgwc6YhZt9T4dMj"//Tokenは適当です
    lineNotifyApi = "https://notify-api.line.me/api/notify"
    headers = {"Authorization": f"Bearer {lineNotifyToken}"}
    data = {"message": "今がお買い時ですhttps://www.nikkei.com/markets/kabu/"}
    requests.post(lineNotifyApi, headers=headers, data=data)


while(True):
    print("トラッキングしました")
    time.sleep(10)
    NikkeiPrice()

     
# NikkeiPrice()
```









---

Amazonのスクレイピングをやりたい方へ
下記で値は取れるらしいが何度も言うように違法だ。また取得するといっても
python amazon-search.pyを実行すると
最初:Learning Python: Powerful Object-Oriented Programming (English Edition)
次:入門 Python 3 第2版
最終的に:"None"をループするらしい。

```
from urllib.request import Request, urlopen
from bs4 import BeautifulSoup



amazonURL = "https://www.amazon.co.jp/s?k=python+%E3%82%AA%E3%83%A9%E3%82%A4%E3%83%AA%E3%83%BC&__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=34KC426KLCM3U&sprefix=python+%E3%82%AA%E3%83%A9%E3%82%A4%E3%83%AA%E3%83%BC%2Caps%2C217&ref=nb_sb_noss_1"
hdr = {
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
    'Accept-Encoding': 'none',
    'Accept-Language': 'ja-JP,en-US;q=0.7,en-GB;q=0.3',
    'Connection': 'keep-alive'}

def amazonTrackingPrice():
    amazonPage = urlopen(Request(amazonURL,headers=hdr))
    soup = BeautifulSoup(amazonPage,"lxml")
    # print(soup)
    
    title = soup.select_one("#search > div.s-desktop-width-max.s-desktop-content.s-wide-grid-style-t1.s-opposite-dir.s-wide-grid-style.sg-row > div.sg-col-20-of-24.s-matching-dir.sg-col-16-of-20.sg-col.sg-col-8-of-12.sg-col-12-of-16 > div > span.rush-component.s-latency-cf-section > div.s-main-slot.s-result-list.s-search-results.sg-row > div:nth-child(6) > div > div > div > div > div.a-section.a-spacing-small.puis-padding-left-small.puis-padding-right-small > div.a-section.a-spacing-none.a-spacing-top-small.s-title-instructions-style > h2 > a > span").get_text()
    
    print(title)
    
    
    
amazonTrackingPrice()
```

hdrなどいらなさそうな要素を付与しているが、アマゾンでその情報をいれないとはじかれる。