Pythonでスクレイピング、TwitterBotで投稿、Herokuで定期実行

##Pythonでスクレイピングし、 TwitterBotへの投稿を、Herokuで定期実行するところまでをやってみた。
・耳鳴りに悩まされているので、”耳鳴り”に関する情報を定期的にツイートするボット。
・Mac
・Python3
・具体的には、以下の２つを実行するアプリを作成した。
[1] Yahooニュースから”耳鳴り”、”めまい”の検索結果をスクレイピングし、定期的にツイート
[2] 耳鳴り改善”や、”耳鳴り原因”などのツイートを、定期的にリツイート（イイねも）


#※環境構築、ディレクトリ構成
デスクトップにディレクトリmiminariを作成し、scraping.pyを作成。
以下の通り、仮想環境を構築し起動する。

```
python3 -m venv .
source bin/activate
```
必要なモジュールをインストール。

```
pip install requests
pip install beautifulsoup4
pip install lxml
```

ディレクトリ構成

```
miminari
├scraping.py
├date_list.txt
├source_list.txt
├text_list.txt
├title_list.txt
├url_list.txt
├twitter.py
├Procfile
├requirements.txt
└runtime.txt
```

#[1]Yahooニュースから”耳鳴り”、”めまい”をスクレイピングし、定期的にツイート
##（１）scraping.pyを作成する
###①ニュースタイトルとURLをスクレイピングする
Yahooニュースから、”耳鳴り”、”めまい”を検索し、urlをコピー。
サイトには、ニュースが10個表示されている。タイトルとURLのありそうな場所を探す。
GoogleChromeの”検証”を見ると、h2タグのclass=tにあることがわかる。
これを踏まえてコードを書いていく。
<img width="1075" alt="スクリーンショット 2020-03-21 19.15.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/edb0f89d-5d51-79cb-8451-9571e1f7029d.png">



```.py:scraping.py
from bs4 import BeautifulSoup
import lxml
import requests

URL = "https://news.yahoo.co.jp/search/?p=%E8%80%B3%E9%B3%B4%E3%82%8A+%E3%82%81%E3%81%BE%E3%81%84&oq=&ei=UTF-8&b=1"

res = requests.get(URL)
res.encoding = res.apparent_encoding
html_doc = res.text
soup = BeautifulSoup(html_doc,"lxml")

list = soup.find_all("h2",class_="t")
print(list)
```

とすると、以下のようにリスト形式で取得できる。

```
[<h2 class="t"><a href="https://headlines.yahoo.co.jp/hl?a=20200320-00000019-nkgendai-hlth">misonoも闘病中メニエール病 根本治療が見つかっていないがどう付き合う？</a></h2>, <h2 class="t"><a href="https://headlines.yahoo.co.jp/article?a=20200316-00010000-flash-ent">相田翔子、突発性難聴とメニエール病で苦しんだ過去「<em>耳鳴り</em>が…」</a></h2>, <h2 class="t"><a href="https://headlines.yahoo.co.jp/article?a=20200315-00000004-nikkeisty-hlth">グルテンで不調、治療法は食の見直し　指導なしは危険</a></h2>, <h2 class="t"><a href="https://headlines.yahoo.co.jp/hl?a=20200313-00000243-spnannex-ent">相田翔子、芸能界引退考えた病魔を初告白　名医の“心のケア”に感謝</a></h2>, <h2 class="t"><a href="https://headlines.yahoo.co.jp/article?a=20200310-00000011-pseven-life">「難聴」は認知症の危険因子　うつ病リスク2.4倍とのデータも</a></h2>, <h2 class="t"><a href="https://headlines.yahoo.co.jp/hl?a=20200309-00010009-nishispo-spo">五輪代表大野の同級生が病に打ち勝ち大舞台へ　81キロ級で無差別挑戦</a></h2>, <h2 class="t"><a href="https://headlines.yahoo.co.jp/article?a=20200226-00010011-newsweek-int">米軍を襲ったイラン謎の衝撃波、解明には数年かかる</a></h2>, <h2 class="t"><a href="https://headlines.yahoo.co.jp/article?a=20200223-00986372-jspa-life">持病と妊活でウツ状態に…アラフォー妻に夫がかけた言葉に涙</a></h2>, <h2 class="t"><a href="https://headlines.yahoo.co.jp/hl?a=20200215-00001569-fujinjp-life">熱エネルギー不足？血巡り停滞？「冷え」のタイプを知って、寒さに負けない体に</a></h2>, <h2 class="t"><a href="https://headlines.yahoo.co.jp/article?a=20200214-00010009-jisin-soci">医師が推奨！不眠や生理痛、頭痛も…「平熱36.5度」が不調を防ぐ</a></h2>]
```
###補足説明
・encodingとは、サーバーから返されるレスポンスの文字エンコーディング。この文字エンコーディングにしたがってコンテンツを変換する。
・apparent_encodingとは、文字化けなどが起こらないようにコンテンツを取得できるようにするための処理を意味する。
・lxmlは、HTMLの字句解析をして、タグなどを判断してデータ構造として取得するHTMLパーサーのうちの一つ。通常はhtml.parserを使うが。より高速なパーサーとしてlxmlを今回は使う。lxmlは別途インストールしてインポートが必要。
・GoogleChromeの検証から、タイトルとURLはh2タグのclass=tにあることがわかるため、find_all("h2",class_="t")とした。

###②ニュースタイトルとURLをスクレイピングする（ファイルに保存する）

```.py:scraping.py
from bs4 import BeautifulSoup
import lxml
import requests

URL = "https://news.yahoo.co.jp/search/?p=%E8%80%B3%E9%B3%B4%E3%82%8A+%E3%82%81%E3%81%BE%E3%81%84&oq=&ei=UTF-8&b=1"
res = requests.get(URL)
html_doc = res.text
soup = BeautifulSoup(html_doc,"lxml")


#タイトルとurlを取得------------------------------
_list = soup.find_all("h2",class_="t")
title_list = []
url_list = []
for i in _list:
    a_tag = i.find_all('a')
    for _tag in a_tag:
        #タイトルを抽出、get_text()は、タグで囲まれた文字列を抽出する
        href_text = _tag.get_text()
        #タイトルを抽出したリストを作成
        title_list.append(href_text)
        #get("href")は、タグで囲まれたurlを抽出する
        url_text = _tag.get("href")
        #タイトルを抽出したリストを作成
        url_list.append(url_text)

#text形式で保存
with open('title_data'+'.txt','a',encoding='utf-8') as f:
    for i in title_list:
        f.write(i + '\n')
with open('url_data'+'.txt','a',encoding='utf-8') as f:
    for i in url_list:
        f.write(i + '\n')

```
###補足説明
・get.text()は、タグで囲まれた文字列を抽出できる。
・get("href")は、その属性値を取得できる。

###③ニュースの概要（text）と日時、ソースをスクレイピングする
ニュースの概要と日時も同様にスクレイピングして、それぞれを保存する。

```.py:scraping.py
from bs4 import BeautifulSoup
import lxml
import requests

URL = "https://news.yahoo.co.jp/search/?p=%E8%80%B3%E9%B3%B4%E3%82%8A+%E3%82%81%E3%81%BE%E3%81%84&oq=&ei=UTF-8&b=1"
res = requests.get(URL)
html_doc = res.text
soup = BeautifulSoup(html_doc,"lxml")


#タイトルとurlを取得------------------------------
_list = soup.find_all("h2",class_="t")
title_list = []
url_list = []
for i in _list:
    a_tag = i.find_all('a')
    for _tag in a_tag:
        #タイトルを抽出、get_text()は、タグで囲まれた文字列を抽出する
        href_text = _tag.get_text()
        #タイトルを抽出したリストを作成
        title_list.append(href_text)
        #get("href")は、タグで囲まれたurlを抽出する
        url_text = _tag.get("href")
        #タイトルを抽出したリストを作成
        url_list.append(url_text)

with open('title_data'+'.txt','a',encoding='utf-8') as f:
    for i in title_list:
        f.write(i + '\n')
with open('url_data'+'.txt','a',encoding='utf-8') as f:
    for i in url_list:
        f.write(i + '\n')


#textを取得-----------------------------------------
_list2 = soup.find_all("p",class_="a")
text_list = []
for i in _list2:
    text_text = i.get_text()
    text_list.append(text_text)
with open('text_list'+'.txt','a',encoding='utf-8')as f:
    for i in text_list:
        f.write(i + '\n')


#日時を取得---------------------------------------------------------------
_list3 = soup.find_all("span",class_="d")
date_list = []
for i in _list3:
    _date_text = i.get_text()
    _date_text = _date_text.replace('\xa0','')
    date_list.append(_date_text)
with open('date_list'+'.txt','a',encoding='utf-8') as f:
    for i in date_list:
        f.write(i + '\n')


#ソースを取得---------------------------------------------------------------
    _list4 = soup.find_all("span",class_="ct1")
    source_list = []
    for i in _list4:
        _source_text = i.get_text()
        source_list.append(_source_text)
    with open('source_list'+'.txt','a',encoding='utf-8') as f:
        for i in source_list:
            f.write(i + '\n')
```

###補足説明
・日時であるが、そのまま抽出すると、”&nbsp”という余計な文字も抽出してしまうため、replaceを使ってブランクにしている（スクレピングすると、”&nbsp”は、”\xa0”と表記されているため、replace('\xa0','')とした。
<img width="508" alt="スクリーンショット 2020-03-21 21.51.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/a00e0bef-36f2-5ed5-babb-0797fa6c0ea6.png">

###④次ページ以降もスクレイピングする
このままだと１ページ分の10個のニュースのみスクレイピング対象となるため、これを４ページ分まわす（４ページまわすとは、”耳鳴り”、”めまい”のニュース検索結果が４ページのみであったため）。
コードを以下のように修正。


```.py:scraping.py
from bs4 import BeautifulSoup
import lxml
import requests

mm = 0
for i in range(4):
    URL = "https://news.yahoo.co.jp/search/?p=%E8%80%B3%E9%B3%B4%E3%82%8A+%E3%82%81%E3%81%BE%E3%81%84&oq=&ei=UTF-8&b={}".format(mm*10 + 1)
    res = requests.get(URL)
    html_doc = res.text
    soup = BeautifulSoup(html_doc,"lxml")

    #タイトルとurlを取得------------------------------
    _list = soup.find_all("h2",class_="t")
    title_list = []
    url_list = []
    for i in _list:
        a_tag = i.find_all('a')
        for _tag in a_tag:
            #タイトルを抽出、get_text()は、タグで囲まれた文字列を抽出する
            href_text = _tag.get_text()
            #タイトルを抽出したリストを作成
            title_list.append(href_text)
            #get("href")は、タグで囲まれたurlを抽出する
            url_text = _tag.get("href")
            #タイトルを抽出したリストを作成
            url_list.append(url_text)

    with open('title_list'+'.txt','a',encoding='utf-8') as f:
        for i in title_list:
            f.write(i + '\n')
    with open('url_list'+'.txt','a',encoding='utf-8') as f:
        for i in url_list:
            f.write(i + '\n')


    #textを取得-----------------------------------------
    _list2 = soup.find_all("p",class_="a")
    text_list = []
    for i in _list2:
        text_text = i.get_text()
        text_list.append(text_text)
    with open('text_list'+'.txt','a',encoding='utf-8')as f:
        for i in text_list:
            f.write(i + '\n')


    #日時、---------------------------------------------------------------
    _list3 = soup.find_all("span",class_="d")
    date_list = []
    for i in _list3:
        _date_text = i.get_text()
        _date_text = _date_text.replace('\xa0','')
        date_list.append(_date_text)
    with open('date_list'+'.txt','a',encoding='utf-8') as f:
        for i in date_list:
            f.write(i + '\n')


    #ソース---------------------------------------------------------------
    _list4 = soup.find_all("span",class_="ct1")
    source_list = []
    for i in _list4:
        _source_text = i.get_text()
        source_list.append(_source_text)
    with open('source_list'+'.txt','a',encoding='utf-8') as f:
        for i in source_list:
            f.write(i + '\n')

    #mm-------------------------------------------------------------------
    mm += 1
```

###補足説明
追加したのは、以下の箇所。URLの末尾が１ページ毎に１、１１、２１、３１となっているためfor文とformatを使って処理した。

```
mm = 0
for i in range(4): 〜〜〜〜

〜〜〜〜　q=&ei=UTF-8&b={}".format(mm*10 + 1)

〜〜〜〜
mm += 1
```

ここまでで、スクレイピングにより、それぞれのニュースのタイトル（title_list）、URL（url_list）、概要（text_list)、日時（date_list）、ソース（source_list）が作成できた。以降、Twitterへの投稿作業を進めるが、使用するのは、日時（date_list）、ソース（source_list）、URL（url_list）のみとした。

##（２）Twitterへ投稿
今回、TwitterBotの詳しい作成方法は割愛する。Botを作成する上で、TwitterAPIへの登録やTwitterへのツイートの方法は以下を参考にした。
[Twitter API 登録 (アカウント申請方法) から承認されるまでの手順まとめ](https://qiita.com/kngsym2018/items/2524d21455aac111cdee)
[TweepyでTwitterに投稿する](https://qiita.com/pontyo4/items/da8ba576c6281d67c0c7)
[TweepyでTwitterの検索, いいね, リツイート](https://qiita.com/pontyo4/items/4ef6c9cb8e15a01a3129)


ディレクトリmiminari内に、twitter.pyを作成して、Tweepyをインストール。

```
pip install tweeps
```

twitter.pyを以下の通り作成

```.py:twitter.py
import tweepy
from random import randint
import os

auth = tweepy.OAuthHandler(os.environ["CONSUMER_KEY"],os.environ["CONSUMER_SECRET"])
auth.set_access_token(os.environ["ACCESS_TOKEN"],os.environ["ACCESS_TOKEN_SECERET"])

api = tweepy.API(auth)

twitter_source =[]
twitter_url = []
twitter_date = []

with open('source_list.txt','r')as f:
    for i in f:
        twitter_source.append(i.rstrip('\n'))
with open('url_list.txt','r')as f:
    for i in f:
        twitter_url.append(i.rstrip('\n'))
with open('date_list.txt','r')as f:
    for i in f:
        twitter_date.append(i.rstrip('\n'))

#randint、len関数で、リストの０番目から nー１番目の範囲からランダムに記事を抽出
i = randint(0,len(twitter_source)-1)
api.update_status("＜耳鳴り関連のニュース＞" + '\n' + twitter_date[i] + twitter_source[i] + twitter_url[i])
```

###補足説明
・Herokuへのデプロイを踏まえ、CONSUMER_KEY等は環境変数とした。
・記事はランダムにツイートされるようにした。

#[2]”耳鳴り改善”や、”耳鳴り原因”などのツイートを定期的にリツイート（イイねも）
twitter.pyにコードを追加

```.py:twitter.py
import tweepy
from random import randint
import os


#auth = tweepy.OAuthHandler(config.CONSUMER_KEY,config.CONSUMER_SECRET)
#auth.set_access_token(config.ACCESS_TOKEN,config.ACCESS_TOKEN_SECERET)

auth = tweepy.OAuthHandler(os.environ["CONSUMER_KEY"],os.environ["CONSUMER_SECRET"])
auth.set_access_token(os.environ["ACCESS_TOKEN"],os.environ["ACCESS_TOKEN_SECERET"])

api = tweepy.API(auth)


#-Yahoo_news（耳鳴り、めまい）ツイート処理----------------------------------------------
twitter_source =[]
twitter_url = []
twitter_date = []

with open('source_list.txt','r')as f:
    for i in f:
        twitter_source.append(i.rstrip('\n'))
with open('url_list.txt','r')as f:
    for i in f:
        twitter_url.append(i.rstrip('\n'))
with open('date_list.txt','r')as f:
    for i in f:
        twitter_date.append(i.rstrip('\n'))

#randint、len関数で、リストの０番目から nー１番目の範囲からランダムに記事を抽出
i = randint(0,len(twitter_source)-1)
api.update_status("＜耳鳴り関連のニュース＞" + '\n' + twitter_date[i] + twitter_source[i] + twitter_url[i])




#-（以下が追加）リツイート処理----------------------------------------------------------------------
search_results_1 = api.search(q="耳鳴り 改善", count=10)
search_results_2 = api.search(q="耳鳴り ひどい", count=10)
search_results_3 = api.search(q="耳鳴り ピー", count=10)
search_results_4 = api.search(q="耳鳴り 薬", count=10)
search_results_5 = api.search(q="耳鳴りとは", count=10)
search_results_6 = api.search(q="耳鳴り 原因", count=10)
search_results_7 = api.search(q="耳鳴り 漢方", count=10)
search_results_8 = api.search(q="耳鳴り ツボ", count=10)
search_results_9 = api.search(q="耳鳴り 頭痛", count=10)
search_results_10 = api.search(q="#耳鳴り", count=10)
search_results_11 = api.search(q="耳鳴り", count=10)

the_list = [search_results_1,
            search_results_2,
            search_results_3,
            search_results_4,
            search_results_5,
            search_results_6,
            search_results_7,
            search_results_8,
            search_results_9,
            search_results_10,
            search_results_11
            ]

for i in range(10):
  for result in the_list[i]:
      tweet_id = result.id
      # 例外処理をする。重複して処理するとエラーが起こるようなので,
      # 例外処理にしておくとプログラム途中で止まることが無い。
      try:
          api.retweet(tweet_id)#リツイート処理
          api.create_favorite(tweet_id) #いいね処理
      except Exception as e:
          print(e)
```

#[３]Herokuへデプロイ
##（１）デプロイ
デプロイに必要なProcfile、runtime.txt、requirements.txtを作成する。
runtime.txtは、自身のpythonのバージョンを確認の上作成する。

```.txt:runtime.txt

python-3.8.0
```

Procfileは以下を記述。

```:Prockfile
web: python twitter.py
```

requirements.txtは以下をターミナルで入力して記述。

```
pip freeze > requirements.txt
```

次に以下の要領でデプロイ。
gitを初期化、Herokuとgitを紐つけて、addして、the-firstという名前でcommitする。
最後にHerokuにプッシュ。

```
git init
heroku git:remote -a testlinebot0319
git add .
git commit -m'the-first'
git push heroku master
```

定期実行する前に以下をターミナルに入力し、Twitterに投稿されていれば、ひとまず成功。

##（２）定期実行
ターミナルで以下を実行し、ブラウザ上で直接Herokuの定期実行の設定を行う。

```
heroku adding:add scheduler:standard
heroku adding:open scheduler
```
<img width="415" alt="スクリーンショット 2020-03-22 0.15.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29856/cf8f5ad2-85d5-8e2a-3c29-ba5d582c9e81.png">

上記のように設定すれば完了（上記は１０分毎にツイート設定）


#おわりに
よろしければ、フォローしてください
[Twitter@MiminariBot](https://twitter.com/miminaribot)