書籍の新刊情報を通知するサービスを作る(1_簡単な検索機能の実装)

# 目的
自身の学習と転職活動のポートフォリオ的なものを兼ねて、WEBサービスを作ってみることにしました。
マンガが好きなので、コミックスの次巻がいつ出るのかを自動で通知できるものがあれば便利だと思い、新刊の発売日が決まったらメールで通知するサービスを作ります。
(WEB検索すると既に便利なサービスがありますが、あくまで学習目的です)
開発に時間がかかるのと、ページが長くなりそうなので小出しにして記録に残します。

# サービス概要
次項に簡単な画面イメージを添付しますが、以下の機能を実装予定です。
1. タイトル名・著者名から登録したい作品を検索する機能
1. 作品を登録し、発売日前に通知する機能(メール通知を予定)
1. 登録済み作品を一覧表示し、通知の解除・再登録を行う機能(マイページ)

## 画面イメージ
トップページ
![トップページ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111972/d4800d90-e4d4-1f34-7aa6-2bd9913277bf.png)
アクセス時に検索画面が表示されるようにします。
タイトル名・著者名ともにあ～んの先頭一文字から絞り込みを行います。

　
タイトル名の絞り込み画面
![タイトル遷移結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111972/7d552b5c-c19d-781e-3477-074902d71665.png)
例えば「あ」から始まる作品を登録したい場合、「あ」のリンクをクリックすると「あ」から始まる作品をリスト表示します。

　
著者名の絞り込み画面
![著者名遷移結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111972/e5e12adc-437a-22b4-101d-00daa34fca09.png)
著者名の場合は著者名の先頭一文字 → タイトル名へ遷移できるようにします。

　
マイページ画面
![マイページ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111972/05fbe58d-9495-b302-540a-e5399225e5a3.png)
通知先のメールアドレスと登録済みの作品一覧を表示します。
通知の要不要を切り替えられるように作品名の横にボタンを設置します。
画像には載せていませんが、通知するタイミングをユーザー側で決められるようにする機能も付けたいと考えています。

## 使用技術
以前から少し勉強していたことと、今後の活用性を鑑みてPythonを使うことにしました。
フレームワークは小規模なプロジェクトである点、学びやすさという点からFlaskを選択します。
また、書籍情報の取得には楽天API(楽天ブックス)を使用します。

WEB開発の経験がなく、知識が不十分なため必要に応じて追記します。

# Flaskの基本のおさらい
まずチュートリアルの通りにコードを書いて、ブラウザに表示できるかどうかを確認します。

```hello.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello, Flask!'
```

Visual Studio Code上のターミナルから以下のコマンドを実行します。
```
flask --app hello run --debug
```

ブラウザ上で http://127.0.0.1:5000 にアクセスして、'Hello, Flask!'が表示されることを確認できればOK。
![Flaskチュートリアル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111972/e5e47e3f-a883-0db9-d757-e7f2a796f37f.png)

# 検索機能の実装…の前に
コーディングの前に、今回は楽天APIを使用するので下準備が必要です。
楽天APIの使用にはユーザー登録が必要で、登録時にドメインも記載する必要があるため、まず公開用のドメインを取得しなければいけません。

AWSに載せる予定でいるので、有名な某ドメイン取得サービスではなく、Route53を使用してドメインを取得しました。
詳細は割愛しますが、以下のようにステータスが表示されていればOK…のはず。

![ドメイン取得結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111972/b2eb9909-b14e-29a3-b7e4-16a456c28a00.png)
(本の情報を感知するイメージから、BookSensorというサービス名にします)

ドメインが取得できたら、https://webservice.rakuten.co.jp/ にアクセスしてユーザー登録を行います。
(自分は既に楽天会員だったため、既存のIDでサインインできました)

新規アプリ登録画面からアプリ名とURLを入力して登録します。
正常に登録完了すると、アプリIDが発行されるので、このIDをコード内で使用します。

# コーディング
ひとまずこの記事では、楽天APIから検索条件に応じた結果が取得できるところまでを確認します。
index.htmlとapp.pyの2ファイルを用意し、それぞれ以下のように記述します。
```index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>BookSensor(新刊情報通知サービス)</title>
  </head>
  <body>
    <h1>BookSensor(新刊情報通知サービス)</h1>
    <form action="/search" method="get">
      <label for="keyword">キーワード:</label>
      <input type="text" id="keyword" name="keyword" required />
      <button type="submit">検索</button>
    </form>

    {% if books %}
    <h2>検索結果</h2>
    <ul>
      {% for book in books %}
      <li>
        <strong>{{ book.title }}</strong><br />
        著者: {{ book.author }}<br />
        出版社: {{ book.publisher }}<br />
        発売日: {{ book.release_date }}<br />
        価格: ¥{{ book.price }}<br />
        <a href="{{ book.link }}" target="_blank">詳細を見る</a>
      </li>
      {% endfor %}
    </ul>
    {% endif %}
  </body>
</html>
```
キーワードを入力する検索ボックスを用意し、検索ボタンを押下すると「作品名」「著者名」「出版社名」「発売日」「価格」「楽天ブックスのリンク」を取得します。

```app.py
from flask import Flask, request, render_template, jsonify
import requests

app = Flask(__name__)

RAKUTEN_API_URL = "https://app.rakuten.co.jp/services/api/BooksBook/Search/20170404"
RAKUTEN_APP_ID = "app_id" #取得したアプリIDを記載

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/search', methods=['GET'])
def search_books():
    keyword = request.args.get('keyword')
    if not keyword:
        return render_template('index.html', books=None)

    # 楽天APIにリクエストを送信
    params = {
        "applicationId": RAKUTEN_APP_ID,
        "format": "json",
        "title": keyword,
    }
    response = requests.get(RAKUTEN_API_URL, params=params)

    if response.status_code == 200:
        data = response.json()
        books = [
            {
                 "title": item["Item"].get("title", "タイトル不明"),
                "author": item["Item"].get("author", "著者不明"),
                "publisher": item["Item"].get("publisherName", "出版社不明"),
                "release_date": item["Item"].get("salesDate", "発売日不明"),
                "price": item["Item"].get("itemPrice", "価格不明"),
                "link": item["Item"].get("itemUrl", "#")
            }
            for item in data.get("Items", [])
        ]
        return render_template('index.html', books=books)
    else:
        return render_template('index.html', books=None)

if __name__ == '__main__':
    app.run(debug=True)
```

内容としては、検索ボックスに入れたキーワードを含む作品を楽天の持っている商品から検索し、一覧を表示する、という仕組みです。

例えば、「ドラゴンボール」を含む作品を検索すると、
![検索画面1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111972/db8cebc9-c3b9-2659-5e9b-bf12d89e05f3.png)

以下のようにズラッと情報が表示されます。
![検索結果1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111972/97e97970-cf1c-50ab-35ec-aa68f321bd6f.png)

これで簡易検索機能が正常に動くことが確認できました。
今後は、仕様に沿った検索機能の実装 → 作品登録・通知機能の実装 → 登録済み作品の一覧表示機能の実装
の順で進める予定です。