LINE Botの開発②

結局

ちょっとLINE Botを触ってみたけれども、なんだかんだでLambdaにPython乗っけて作るのが楽そうだな…という結論。

構成

で行く予定。

主に実装する範囲

LINE Botの応答部分に関しては色々とLambdaを試しているうちにほぼできたので、特に問題なし。

ただ、RDSからデータをSELECTしてこなければならないので、そこのつなぎ方だけは検証する。

スクレイピング部分に関してはある程度ソースができているので、それをLambda環境で動かすような形にする。

Lambdaを使ってみた感想

デバッグがしづらいので、Lambdaにソースおいて動かしてみて…の繰り返しだった。

何かやりやすいデバッグ方法があるんですかね…?

LINE Botの開発

こんなことをするつもり

あるテーマに基づき、複数のサイトから(問題のない範囲で)スクレイピングを行い情報収集をし、それを一覧にまとめるようなリンク集サイトを作ろうかな…と思ったのだけれども…

どうにも「問題のない範囲で」取ってこようとすると取得できる情報があまりリッチにならない。

検索機能をつけるつもりではいたものの…わざわざサイトにする必要ある?という気持ちに。

なので、LINE Botで検索ワードを投げたらその結果を返すようなBotを作ってしまえば良いのではないかという結論に至る。

今のところの構想

技術スタックは以下をとりあえず考えている。

仰々しい感じにはなるが、DRFを触っときたいという気持ちがあるのでまずは上記の構成で。

後々はLambdaにのせかえてサーバレス構成にして費用を抑えたい…。

勉強半分、自分がほしいBotの開発目的半分、という形で進める。

現状

とりあえずDjangoの環境を立てて、スクレイピング対象のページ1個目のHTMLを抜いてきてローカルで仮で稼働、

BeautifulSoupを使ってスクレイピングしDBに記録するところまでは実施済み。

レンタルスペース予約のシステムをつくった

レンタルスペース予約のシステムを作った

いろいろとDjangoがらみの話を書いていましたが、やっと本番運用が始まるので。

以下のようなシステムを作りました。

Atelier 5-25-6

仲間内で運営しているレンタルスペースの予約用システムです。

環境

フレームワークDjango、Bootstrap4、jQuery サーバ:AWS EC2 DB:MySQL

機能

  • スペースの予約登録

時刻や用途、支払方法などを設定し、予約登録が可能です(当たり前)。

予約のリクエストがあった際には、メールアドレスにユニークなURLを送りつけて、そのURLを踏むことで本予約として受け付ける形式にしています。

あまり会員登録などはさせたくなかった反面、不正な予約がたまりすぎて欲しく無いなというところもあり、そんなフローにしています。

DjangoでGoogle Calendarへアクセス① - Livegram

DjangoでGoogle Calendarへアクセス② - Livegram

ここらへんでも触れていますが、予約を入れたらGoogle Calendarに勝手に入るので見ればわかり、

逆に都合の悪い日や、このシステムを介さない予約はGoogle Calendar側に予定を入れてしまえば同期されるため、バッティングを防げる仕組みにしています。

不正予約防止の一貫。入力の際にreCaptchaを噛ませてます。

  • Slackへの予約があった際の通知

利用者の方がメールに送られたURLを踏んだ時点(本予約が成立した時点)でSlackへ予約が入った通知をします。

その通知をみて、詳細な連絡を行い実際に使ってもらう、という流れになります。

  • クレジットカード決済

DjangoでStripe決済 - Livegram

ここでも触れていますが、予約が入ったあと決済を行うような仕組みも実装しました。

クレカ決済を選択した場合は、飛んでくるメールが他の決済と異なるものとなり、決済用URLが表示される形です。

そのURLを踏んでから決済を行うと、Slackに通知が飛んでくる感じです。

あと、これに付随して金額計算のロジックも組みましたが、当然「営業時間外」という概念や「30分単位は切り上げ」などの概念もあるので、ちょっとそこは面倒でした。

作ってみて

必要だったので作ってみたのですが、開発期間は約2.5ヶ月(4~6月上旬が中心、6月後半〜8月は諸事情あり一時停止)。

土日を3〜4時間ずつくらい当てたような感じです。結構かかったな。

中身としては普通に登録・リスト表示するだけなのですが。

追加機能の予定

レンタルスペース貸しなので、スマートロックとも連携できれば強いです。予約が入ったら、管理画面にログインして、ボタン押したら鍵が発行できる、みたいな。

それを達成できそうなスマートロックはおそらくSESAMEかなと。今はQrioで運用しているので、買い替えが必要です。

買い替え代金をこのレンタルスペースが稼いでくれたらな、とか思います。

やろうと思っていることのメモ書き

自分のスキルに対する改善を考えている

一応、ITエンジニアとして働いてはいるけれども、常にスキルに対し不安を抱いているし、何をどう進めていけばよいのかわからなくなり足を止めてしまう。解消方法をGoogleに求めて「やってみようかな」と思うことは多々あれど、それで止まってしまっているので、以下に書いた項目を潰して行こうと思う。

1日1時間程度は少なくとも割きながら…。

やること

  • オライリーPython本を読み終える
  • 世界でもっとも強力な9のアルゴリズムを読み終える
  • 思考する機械コンピュータを読み終える
  • もう1個アプリを作る
  • LeetCodeを週に2〜3個ずつは対応し、Easyを完了させる
  • Udemyで買った講座を終える

とりあえずは上記を完了させる。 それまでは惑わないよう、余計な情報は入れない。

とりあえずは上記に

Djangoの開発環境整備(venv)

環境を立てた

新しいWebアプリのため、ローカルに環境を立てたのでその時のメモ

細かいバージョン指定とかは端折っている

とりあえず以下の形で作った

とりあえずDjangoが動くまで

  1. venv用フォルダを作りcdで移動
  2. python3.7 -m venv .
  3. source bin/activate
  4. python -m pip install --upgrade pip
  5. pip install django
  6. django-admin startproject [appname]してcdで移動
  7. python manage.py migrate
  8. touch requirements.txt
  9. pip freese > requirements.txt

VSCodeでデバック実行できるようにする

前提:PythonのExtentionを入れる 1. インタープリターの設定

インタープリターは仮想環境配下のpythonを選ぶこと!

  1. launch.jsonを作成(python -> Django を選択)

settings.py、urls.py、wsgi.py、asgi.pyの外出し(機能単位で設定しないでよくなるように)

  1. configフォルダを作って以下ファイルをアプリから移動
asgi.py
settings.py
wsgy.py
  1. 以下ファイルをconfig配下に新規作成
local_settings.py
urls.py
  1. urls.pyを編集

config側

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
]

app側

from django.contrib import admin
from django.urls import path

urlpatterns = [
]
  1. asgi.pywsgi.py'とmanage.py`を編集
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'appname.settings')
↓
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
  1. settings.pyを編集
★locals_settings.pyの読み込み importのあとすぐ実行
try:
    from .local_settings import *
except ImportError:
    pass

★urls.pyの読み込み元変更
ROOT_URLCONF = 'config.urls'

★wsgi.pyの読み込み元変更
WSGI_APPLICATION = 'config.wsgi.application'
  1. 以下項目をsettings.pyからlocal_settings.pyに移植
・from pathlib import Path
・DEBUG
・ALLOWED_HOSTS
・SECRET_KEY
・BASE_DIR
・DATABASES
  1. 動作確認をして問題なければOK

上記でできるフォルダ構成

workspace
├ .vscode
│ └ launch.json
├ config
│ ├ asgi.py
│ ├ local_settings.py
│ ├ settings.py
│ ├ urls.py
│ └ wsgi.py
├ app
│ ├ __init__.py
│ └ urls.py
├ db.sqlite3
├ managed.py
└ requirements.txt

今日のLeetCode(8/20)

設問

「Number of Good Pairs」 [1,2,3,1,1,3]みたいな配列numについて nums[i] == nums[j]'かつi < j'となるペアの数を出す

私の書いたコード

import copy
class Solution:
    def numIdenticalPairs(self, nums: List[int]) -> int:
        res = 0
        for i,j in enumerate(nums):
            copy_nums = copy.copy(nums)
            del copy_nums[0:i+1]
            res += copy_nums.count(j)
        return res

i < jなので、iよりindexが若いものを消したListを作ってcountしていく。

最初はcopy_nums = numsと書いていたが、ちゃんとコピーしないともとのnumsからも消されていってしまう…。

ほかのコードを見て

あれ、なんかみんな結構分量書いてる…?

class Solution:
    def numIdenticalPairs(self, nums):
        goodPairs = 0
        for i, value in enumerate(nums):
            for j, value2 in enumerate(nums):
                if i < j and value == value2:
                    goodPairs += 1
                    print(goodPairs, i, value)
        return goodPairs

上は単純にnum[i]'とnum[j]'をそれぞれ取ってきて比較しているみたい

ループ回数多いけど忠実な気はする。

class Solution:
    def numIdenticalPairs(self, nums: List[int]) -> int:
        usedNums = {}
        num = 0
        for i in nums:
            if i in usedNums:
                if usedNums[i] == 1:
                    num +=1
                else:
                    num += usedNums[i]
                usedNums[i] +=1
            else:
                usedNums[i] = 1
        return num

listの頭から順にdicに入れていき、同じ番号が入ってきたらdic側をカウントアップして、過去に出てきた回数分numに足していくのね…なるほど…。

今日のLeetCode(8/18)

設問

「Kids With the Greatest Number of Candies」 [2,3,5,1,6]みたいな配列candiesと任意の値extracandiesが与えられ、 extracandiesを配列の数字にうまく分配したら、配列の各数字が配列内の最大数になるかどうか?という問題。 配列が「各子供に与えられたキャンディ」でextracandiesはそのまま「追加のキャンディ」 追加のキャンディをうまく渡して、それぞれの子供はキャンディ保持数が1位になる可能性はあるか?ということ。

私の書いたコード

class Solution:
    def kidsWithCandies(self, candies: List[int], extraCandies: int) -> List[bool]:
        result = []
        sortcandies = sorted(candies, reverse=True)
        for i in candies:
            maxcandy = i + extraCandies
            result.append(maxcandy >= sortcandies[0])
        return result

最大値を取りうるかどうかだけ見ればいいので、extraCandiesをすべて渡すケースを考える。 解いたあとにして思うが、初期値が最大のやつは回す必要すらない…なぁ

ほかのコードを見て

いやまぁ大きな差はなかったけど、sortedじゃなくて普通にmaxつかってるよね…。

あと、実は詰まって調べたのが、

class Solution:
    def kidsWithCandies(self, candies: List[int], extraCandies: int) -> List[bool]:
        result = []
        sortcandies = sorted(candies, reverse=True)
        for i in candies:
            maxcandy = i + extraCandies
            if maxcandy >= sortcandies[0]:
                result.append(bool('True'))
            else:
                result.append(bool('False'))
        return result

みたいなのを試しに書いたら、全部Trueになってしまうという現象が…。

どうもこれが関係ありそうなので、ちょっと明日以降も調べよう。

リストのコピーでハマった話 - Qiita