Pythonを使って位置情報から天気予報を送るLINE Botを作った！

# 目的
- LINE Botに慣れる。
- Pythonを用いて位置情報を送信すると天気予報を返信してくれるbotを作成する。

# 注意！
LINEmessagingAPIもスクレイピングも初めてやったのでプログラムに変なとこがある可能性が高いです。(特にスクレイピングに関しては)
改善点などあったら是非コメントしてください！

# 参考
下記のサイト、記事を参考にさせていただきました。ありがとうございます。

[PythonとLINE APIとHerokuでBOTを作る[Python編]](http://shikasen-engineer.com/python_line_bot/)
--LINE Botとスクレイピングを組み合わせるのに参考にさせていただきました。

[Messaging API リファレンス](https://developers.line.biz/ja/reference/messaging-api/)
--公式のリファレンスです。

[LINE初心者!-LINE Botでオウム返し編-](https://www.casleyconsulting.co.jp/blog/engineer/3028/)
[PythonでLINE Botを作ってみた](https://qiita.com/kro/items/67f7510b36945eb9689b)
[Python, LINE APIを使ってbotを作成する](https://qiita.com/connectcrew-ishii/items/830af1827e7788e557d3)
--1番最初に何からやったら良いか分からないときに非常に参考になりました。

[イメージマップを使って終電に乗り遅れないボットを作りました](https://engineering.linecorp.com/ja/blog/imezimatsupumetsuseziwoshi-tsutezhong-dian-nicheng-richi-renaibotsutowozuo-rimashita/)
--位置情報を使ったBotを作るのに参考になりました。

[Beautiful soupでの スクレイピング基礎まとめ[初学者向け]](https://qiita.com/U-MA/items/896c49d46585e32ff7b1)
[PythonとBeautiful Soupでスクレイピング](https://qiita.com/itkr/items/513318a9b5b92bd56185)
--Pythonでスクレイピングをするのに参考になりました。


# 開発環境
- OS X 10.14.6
- Python 3.6.6
- Flask 1.1.1
- Line-bot-sdk 1.14.0
- Heroku

# 前提
- Line Developerに登録済み
- Herokuに登録済み

# ファイル構成
Line_bot
├ main.py
├ scrape.py
├ runtime.txt
├ requirements.txt
└ Procfile

# ソースコードとその説明
```python:main.py
from flask import Flask, request, abort
 
from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage, LocationMessage
)
from linebot.exceptions import LineBotApiError

import scrape as sc
import urllib3.request
import os
 
app = Flask(__name__)
 
YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]
YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"]
 
line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(YOUR_CHANNEL_SECRET)
 

@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']
 
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)
 
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)
    return 'OK'
 
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
  text = event.message.text
  if '位置情報' in text:
    line_bot_api.reply_message(
      event.reply_token,
      [
      TextSendMessage(text='位置情報を教えてください。'),
      TextSendMessage(text='line://nv/location')
      ]
    )
  
@handler.add(MessageEvent, message=LocationMessage)
def handle_location(event):
    text = event.message.address

    result = sc.get_weather_from_location(text)
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=result)
    )

if __name__ == "__main__":
    port = int(os.getenv("PORT", 5000))
    app.run(host="0.0.0.0", port=port)
```

「位置情報」という文字列の入ったテキストをbotが受信すると、「位置情報を教えてください」という返信とともに位置情報の選択画面へのURLを送信するようにしました。
詳しくはLINE Developerのドキュメントである[LINE URLスキームを使う](https://developers.line.biz/ja/docs/messaging-api/using-line-url-scheme/)を参照してください。

```python:scrape.py
import requests
from bs4 import BeautifulSoup
import re

# 位置情報からその日の天気を返す
def get_weather_from_location(original_location):
  # 住所の中から郵便番号を抽出する
  location = re.findall('\d{3}-\d{4}', original_location)
  # 1回目のスクレイピングでは住所を検索し、候補から取ってくる
  url = "https://weather.yahoo.co.jp/weather/search/?p=" + location[0]
  r = requests.get(url)
  soup = BeautifulSoup(r.text, 'html.parser')
  content = soup.find(class_="serch-table")
  # 2回目のスクレイピングで用いるURLを得る
  location_url = "http:" + content.find('a').get('href')
  r = requests.get(location_url)
  soup = BeautifulSoup(r.text, 'html.parser')
  content = soup.find(id='yjw_pinpoint_today').find_all('td')
  info = []

  for each in content[1:]:
    info.append(each.get_text().strip('\n'))
  
  # 時間
  time = info[:8]
  # 天気
  weather = info[9:17]
  # 気温
  temperature = info[18:26]
  # 上の3つの情報を合わせる
  weather_info = [(time[i], weather[i], temperature[i]) for i in range(8)]

  result = [('{0[0]}: {0[1]}, {0[2]}°C'.format(weather_info[i])) for i in range(8)]
  result = ('{}\nの今日の天気は\n'.format(original_location) + '\n'.join(result) + '\nです。')

  return result
```
scrape.pyのget_weather_from_locationでは受け取った位置情報のうち郵便番号を抜き出してyahoo天気からその日の天気予報を取ってくるようにしています。

```txt:runtime.txt
python-3.6.6
```

```txt:requirements.txt
Flask==1.1.1
line-bot-sdk==1.14.0 
beautifulsoup4==4.7.
urllib3==1.24.1
soupsieve==1.6.1
```
もしかしたら不要なものも書いているかもしれません。
ご指摘ください。

ソースコードはここにおいてます。
[https://github.com/RY908/Weather_bot](https://github.com/RY908/Weather_bot)

# ハマったポイント
Line botとはあんまり関係ないのですが、スクレイピングするのが初めてだったのでスクレイピングにかなり時間がかかりました笑

# 結果
![Screenshot_20190924-171917609_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297281/bcdc5d47-faed-31f0-a0dc-7dcc4f3b7913.jpeg)
無事１日の天気を送ることができました！

今後はプッシュ通知などを使って改良していきたいです！