Python初心者が最初に作ったWebアプリ


Pythonに全く触れたことのない初心者が、とても簡単なWebアプリを作成した時の備忘録です。
Qiita初投稿です。いつもお世話になっております。

■アプリ概要
・ユーザーからの入力を受け付けるWebページを表示
・入力された書籍タイトルを元に、書籍APIの検索リクエスト(Google Books API)
・レスポンスから書籍サムネイルを表示

作成工程、その中でハマった所、解決方法を記載していきます。

—環境—
Mac OS

1.Pythonのインストール

Macはデフォルトで2.x系のPythonがインストールされています。
下記コマンドでインストールされているPythonのバージョンを確認。

Terminal
$ python --version
Python 2.7.16

※ python -V でも同じ結果が得られます

Python 2.x系は2020年1月1日にサポート終了しています。
3.x系とのversionの違いで結構不整合が起きる + 今時2.x系を使用していると小学生にも鼻で笑われるらしいので、、、
最初に3.x系にアップデートします。

↓公式サイトから3.x系のパッケージをダウンロード
https://www.python.org/downloads/

Q1.ハマりポイント
インストールしたのにバージョンが2.x系のまま
python3 --version と確認すると3.x系になっている

Terminal
$ python --version
Python 2.7.16
$ python3 --version
Python 3.7.7

A1.解決方法
1.brewコマンドでpyenvをインストール(読み方が分からず、心の中でぴえんって読んでる)

Terminal
$ brew install pyenv

2.viエディタ等を使い~/.bash_profileに下記4行を追記
export PYENV_ROOT="\$HOME/.pyenv"
export PATH="\$PYENV_ROOT/bin:\$PATH"
eval "\$(pyenv init -)"
export PATH="\$HOME/.pyenv/shims:$PATH"

※viエディタはTerminalからファイルを編集できるコマンドです。
使い方が結構特殊ですが、Linuxのサーバー構築などでもよく使います。
https://prev.net-newbie.com/linux/commands/vi.html

Terminal
$ vi ~/.bash_profile
# ~/.bash_profileを編集する
$ source ~/.bash_profile

3.pyenvを使いMac全体にpython3.x系を認識させる。

Terminal
$ pyenv global 3.7.0
$ pyenv rehash

これで3.x系が使えるようになりました。

Terminal
$ python --version
Python 3.7.7

2.フォルダ構成

プロジェクトのルートフォルダ直下に index.html と server.py と cgi-bin フォルダを配置。
cgi-bin フォルダ内に index.py を配置します。

project/
  ┝ ─ index.html
  ┝ ─ server.py
  └ ─ cgi-bin/
         └ ─ index.py

3.index.htmlの作成

今回はレイアウトも何も考えず、ただ文字列の入力を受け付けるだけのhtmlを作成します。

index.html
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <form method="POST" action="cgi-bin/index.py">
      <label>取得図書画像タイトル:</label>
      <br>
      <input type="text" name="text">
      <button type="submit">送信</button>
    </form>
  </body>
</html>

ザックリ説明すると、
<form>タグで囲まれている箇所に入力されたデータが、
<submit>契機で、cgi-bin/index.py にPOSTメソッドで送信される。
そんな感じです。

下記のようなページが作成されます。

テキストボックスに入力されたデータが、[送信]ボタンを押すと、index.py に送信されます。

4.server.pyの作成

ローカルで検証用のサーバーを立てるために必要になります。

server.py
import http.server
http.server.test(HandlerClass=http.server.CGIHTTPRequestHandler)

Terminalで作成したPythonファイルを起動すると、ローカルサーバーが立ち上がります。
ローカルサーバーが立ち上がった状態で
http://0.0.0.0:8000/
にアクセスすると、先ほどのindex.htmlが表示されるはずです。

Terminal
$ python server.py
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

※ちなみにローカルサーバーを中止するのは command + C です。

5.index.pyの作成

index.htmlから送信されたデータを使い、Google書籍検索APIのリクエストを送ります。

index.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import cgi # CGIモジュールのインポート
import cgitb
import sys
import requests
import json

# 書籍検索APIの雛形
api = "https://www.googleapis.com/books/v1/volumes?q={title}&maxResults=10&startIndex=0"

# デバッグに使うので、本番環境では記述しない
cgitb.enable()

# ユーザーが入力したフォームデータを取得する
form = cgi.FieldStorage()

# HTMLを記述するためのヘッダ
print("Content-Type: text/html; charset=UTF-8") 
print("")

# フォームのデータが入力されていない場合
if "text" not in form:
    print("<h1>Error!</h1>")
    print("<br>")
    print("テキストを入力してください!")
    print("<a href='/'><button type='submit'>戻る</button></a>")
    sys.exit() # index.pyの終了

text = form.getvalue("text") # テキストデータの値を取得する
url = api.format(title=text) # 書籍検索apiの検索単語となる{title}に入力テキストを当てはめる
response = requests.get(url) # リクエストを投げる
data = json.loads(response.text) # レスポンスをjson形式に変換

# レスポンスをhtmlに反映

print(data['items'][0]['volumeInfo']['title'])
print("<br>")
print("<img src=" + data['items'][0]['volumeInfo']['imageLinks']['thumbnail'] + ">")
print("<br>")
print("<a href='/'><button type='submit'>戻る</button></a>")

コメントでだいぶ補足してありますが、ザックリ説明します。
↓こちらがGoogle Books API のURLです。
https://www.googleapis.com/books/v1/volumes?q=いちご100&maxResults=10&startIndex=0
q=の後に検索したい書籍の名前を入れると、関連したレスポンスを返してくれます。

curlを使っても手軽に確認できます。

Terminal
$ curl https://www.googleapis.com/books/v1/volumes?q=いちご100&maxResults=10&startIndex=0

"kind": "books#volumes",
 "totalItems": 2836,
 "items": [
  {
   "kind": "books#volume",
   "id": "vWSUDwAAQBAJ",
   "etag": "b/w9qaxsyy4",
   "selfLink": "https://www.googleapis.com/books/v1/volumes/vWSUDwAAQBAJ",
   "volumeInfo": {
    "title": "いちご100% モノクロ版【期間限定無料】 2",
    "authors": [
     "河下水希"
    ],
    "publisher": "集英社",
    "publishedDate": "2002-10-04",
    "description": "【春マン!! 期間限定無料!!/真中淳平が突如迷い込んだ恋の迷路! いちごパンツが導く超青春ラブコメディ!】※2019年5月8日までの期間限定無料お試し版です。2019年5月9日以降はご利用できなくなります。 西野と東城、二人の間で揺れる真中の気持ち。ホントに好きなのは西野? それとも東城? 勉強に全く身が入らないまま迎えた受験当日、いちご模様迷宮の入り口・幻の美少女が目の前に…!! どうする真中!?",
    "industryIdentifiers": [
     {
      "type": "OTHER",
      "identifier": "PKEY:088733268733043155P5"
     }
    ],
    "readingModes": {
     "text": true,
     "image": false
    },
    "pageCount": 188,
    "printType": "BOOK",
    "categories": [
     "Comics & Graphic Novels"
    ],
    "maturityRating": "NOT_MATURE",
    "allowAnonLogging": false,
    "contentVersion": "1.2.2.0.preview.2",
    "panelizationSummary": {
     "containsEpubBubbles": true,
     "containsImageBubbles": true,
     "epubBubbleVersion": "99b0fa95624a43e9_A",
     "imageBubbleVersion": "99b0fa95624a43e9_A"
    },
    "imageLinks": {
     "smallThumbnail": "http://books.google.com/books/content?id=vWSUDwAAQBAJ&printsec=frontcover&img=1&zoom=5&source=gbs_api",
     "thumbnail": "http://books.google.com/books/content?id=vWSUDwAAQBAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api"
    },
    "language": "ja",
    "previewLink": "http://books.google.co.jp/books?id=vWSUDwAAQBAJ&dq=%E3%81%84%E3%81%A1%E3%81%94100&hl=&cd=1&source=gbs_api",
    "infoLink": "http://books.google.co.jp/books?id=vWSUDwAAQBAJ&dq=%E3%81%84%E3%81%A1%E3%81%94100&hl=&source=gbs_api",
    "canonicalVolumeLink": "https://books.google.com/books/about/%E3%81%84%E3%81%A1%E3%81%94100_%E3%83%A2%E3%83%8E%E3%82%AF%E3%83%AD%E7%89%88_%E6%9C%9F%E9%96%93%E9%99%90.html?hl=&id=vWSUDwAAQBAJ"
   },
   "saleInfo": {
    "country": "JP",
    "saleability": "NOT_FOR_SALE",
    "isEbook": false
   },
   "accessInfo": {
    "country": "JP",
    "viewability": "NO_PAGES",
    "embeddable": false,
    "publicDomain": false,
    "textToSpeechPermission": "ALLOWED",
    "epub": {
     "isAvailable": true
    },
    "pdf": {
     "isAvailable": true
    },
略------------------------------------------------------

responseには、上記のような長い応答が格納され、
dataにはresponseをJSON型に変換して格納します。

今回はその中から、[title]と[thumbnail]を使いたいので、
data['items'][0]['volumeInfo']['title']
data['items'][0]['volumeInfo']['imageLinks']['thumbnail']
のようにアクセスし、必要となるデータを取り出します。

[0]の値を[1][2]と変更することによって、
取り出す検索結果対象を変更することができます。

↓↓↓

完成○△□

Q2.ハマりポイント
送信ボタンを押下すると、
FileNotFoundError: [Errno 2] No such file or directory: '/Users/hoge/project/cgi-bin/index.py'
とエラーが表示され、検索結果が表示されない

A2.解決方法
/Users/hoge/project/cgi-bin/index.py は存在していた。
index.py の1行目が誤っていた
× #!usr/bin/env python3
○ #!/usr/bin/env python3

Q3.requestsがインポートできない
import requests
の箇所でエラーが起きてしまう

A3.解決方法
こちらの記事が大変参考になりました。
https://qiita.com/Kent_recuca/items/349586e9c034535f2991

Pythonのsys.pathに
requestsがインストールされたパスを追記することで解決

総括

以前SpringBootを用いてWebアプリを作成したことがありましたが、
それに比べフォルダ構成、環境構築が楽ですぐに動かせるという所感です。

機械学習分野に使われていることは知っていましたが、Web環境でも使用されていることに驚きました。
Pythonに初めて触れたので、
コメントであるはずの#の後ろでなぜエラーが起きるのか、
ローカルサーバーを立ち上げると、なぜindex.htmlが表示されるのか、
まだまだ謎だらけですが、これからお勉強して理解を深めていきたいと思います。