開拓馬の厩

いろいろやる

クックパッドインターンでちょっぴり成長した

10 day 技術インターンシップに参加してちょっぴり*1成長してきました。かなり楽しかったので体験記を書きます。

internship.cookpad.com

前半5日間

前半の5日は講義形式でwebアプリケーションの開発について学びました。

初日はオフィス見学と環境構築で、2~5日目で以下のようなことを学びました。

  • 事業開発
  • サーバサイド(Ruby on Rails)
  • フロントエンド(React Native、Typescript)
  • クラウドインフラ(AWS、Docker 内製デプロイツールの itamaehako のハンズオン*2 )

個人的にはクラウドインフラの回が一番楽しかったです。 EC2やRDSのインスタンスを立てたり、ロードバランサを置いて負荷分散をしたりといったことを体験しました。 これらは学生が個人でやるには限界があり、ここで体験できたのはとても良かったと思います。 サーバの設定内容をコードの形式で保存しChefツールで環境構築を自動化することで、サーバ環境構築の冪等性を確保するという概念を知ることもできました。

また、実習として、ISUCON形式でアプリケーションの処理速度をインターン参加者同士で競うということも行われました。 私は2位に入賞することができ、賞品としてレシピ本をもらいました🥈

後半5日間

後半はOJT*3コースとPBL*4コースに分かれて参加者ごとに異なる活動をしました。 OJTは実際にクックパッドの開発現場に参加するコースで、PBLは与えられた課題を解決するようなアプリケーションをチームで開発するコースです(やや期間が長いハッカソンのようなもの)。 私はPBLコースに配属され、「食材の購入を支援するアプリを作る」という課題に3人のグループで挑戦しました。

我々のチームは「休日に1週間分の食材を買い込んで、平日はそれらを使って料理する」というシナリオを想定し、1週間分の献立を決めると必要な食材の総量を計算してくれるアプリを作成しました。 自分は主にクライアントサイドの開発担当としてひたすらReact Nativeを書きました。 React Navigationで画面遷移を実装したり、Native BaseでUIを設計したり、コンポーネントはできる限りSFC*5になるように書いて使いまわしやすくしたり実践的なReact Nativeの開発ができました。

ただ、ライブラリの選定や設計方針を予め相談した方が良かったのかもしれません。今回のアプリではヘッダ部分をReact Navigationが生成するものを使いましたが、個人的には使いづらいように感じる部分もありました。 React Native Navigation等の他のライブラリを使用する、ヘッダ部分もNative Baseで実装するという選択肢をあまり考慮せず、なんとなくで決めてしまったのは良くなかったと思います。

仮に今回作ったものを実際に事業として動かすのであれば、デザイン面*6と技術面*7での課題を解決するためにユーザインタビューや機械学習モデルの構築などが必要で、事業開発のほんの一部分しか体験できていないようにも思いました(インターンの期間中にユーザインタビューまでやるのは無理そうだけど)。

参加前はOJTコースで現場の業務環境を体験したいと考えており、PBLコースに配属されたのを残念に思っていました*8。 しかし、アプリの設計から実装まで技術レベルが同じぐらいのメンバーでグループ開発するという体験はそんなにできることではないため、それはそれで良い体験になったのではと思います。

感想

いろいろなことが体験できた10日間でした。 Ruby on RailsやReact Nativeといったフレームワークを初体験したり、インフラエンジニアリングという分野のことを知ったりすることができました。

正直、今回のインターンそれ自身による成長はそこまで大きくは無いと思います。本当に成長するのは今回触れた技術をアルバイトなどで活用したときになるのではと思います。 しかしながら、今回のインターンを通じてweb系エンジニアの方々の働き方を垣間見たり、同期の学生達が様々なことに挑戦していることを知ったりして、勉強していくモチベーションが高まりました。

このインターンで成長したというよりは、今後成長する足がかりができた感じがしました。(植物に例えるなら、幹が伸びるというよりは新しい枝が生えたのかな)

学んだこと

ほぼ初見でしたが、2週間で多少使えるレベルにはなったかなと思います。 特にReact NativeはPBLコースの5日で沢山書いたので、その気になれば自力でアプリひとつ作れる程度にはなったかな? Typescriptやweb版のReact.jsの理解も深まりました。

  • 新規事業開発

企業がどのようにして新規事業を立案したり、ユーザのフィードバックを得たりしているのかを体験できました。大学では得られないものだったと思います。 研究のテーマ探しやユーザスタディなどでも役に立ちそうだと思いました。

新しく興味を持ったこと

  • インフラの最適化 ISUCON

AWSへのデプロイや、クラウドインフラの最適化は独学では体験しにくい分野なので体験できてよかったと思います。 インフラエンジニアという職種を知ることもできました。 また、社内ISUCONを体験して本家ISUCONにも興味が湧きました。

  • Rust

インターンの内容とは無関係ですが、参加者にRustaceanがいて布教されました。 Web Assemblyと組み合わせて使ってみたいです。

その他

クックパッドはオフィスにキッチンがある変わった会社です。 そのキッチンを使って、インターン参加者や社員の皆さんとごはんを作りました。 大勢で料理をしてみんなで食べるというのはとても楽しかったです。それに料理も美味しかったです。餃子とか唐揚げとか中華スープとか🥟 f:id:pf_siedler:20180913154158j:plain 3日目の餃子パーティー f:id:pf_siedler:20180913154204j:plain 最終日の懇親会

*1:圧倒的成長というが界隈で使われていますが、「圧倒的」という部分に嘘っぽさとブラック労働的な苦労自慢のニュアンスが感じられて好きではないので「ちょっぴり成長」したことにします

*2:どちらもオープンソースとして公開されている

*3:On-the-Job Training

*4:Problem Based Learning

*5:Stateless Functional Component

*6:献立を立てる→必要な食材を計算というコンセプトなので、献立を立てるという体験を楽しくさせるデザイン上の工夫が必要だと考えられる。また、世間一般の消費者は休日にまとめ買いや献立を予め決めるといった行動をどの程度行っているのかを分析し、このアプリが本当に必要とされているか見極めなければならない

*7:クックパッドのレシピに記載されている材料欄をもとに買うべき食料を算出しているため、食材を1人分に正規化したり、「適量」とか「お好み」とかいった表現をいい感じの数量に変換したり等で、提示する食材量の妥当性を確保する必要がある

*8:OJTコースのみ時給が支払われるという待遇の違いもあったが、その点について不満はなかった

Python & Serverless FrameworkでLambdaファンクションをサクッと作ってデプロイする

Serverless FrameworkAmazon AWSの開発&管理を補助するフレームワークです。 最近アルバイト先でserverlessを用いたアプリケーションに関わり、その際に使いかたを軽く勉強したので、ブログに書き残しておきます。

環境

  • Node.js 10.6.0
  • Python 2.7.15
  • serverless 1.29.2

Installation

ServerlessはNode.jsのアプリケーションとして配布されており、npmコマンドからインストールできます。

npm install -g serverless

# 確認
serverless --help
sls --help # slsという短縮コマンドが自動でエイリアスされるらしい

サービスを作ってデプロイ

インストールが完了したので、簡易的なLambdaのファンクションを作成してAWSにデプロイするところまでやってみましょう。 今回は毎朝8時にLINE Notificationを送ってくれる目覚ましファンクションを作ってみます。

サービスの新規作成

sls createコマンドでサービスを新規作成できます。 このとき-tオプションでアプリのテンプレートを、-pオプションでサービスを保存するパスを指定できます。 テンプレートはaws-python*1、保存先は適当な作業ディレクトリ上のlambdaMezamashiというディレクトリにします。

cd /path/to/working/dir #適当な作業ディレクトリに移動
sls create -t aws-python -p lambdaMezamashi
cd lambdaMezamashi
ls
# => handler.py   serverless.yml

コマンドを打つと、lambdaMezamashiというディレクトリが作成され、handler.pyserverless.ymlという2つのファイルが自動生成されます。 handler.pyはLambdaのファンクションを書くファイルで、serverless.ymlは設定ファイルになります。

とりあえず動かす

アプリが完成したのでとりあえず動かしてみます。 テンプレートにはhelloという関数名で簡単なメッセージを返す関数が定義されています。 sls invoke local -f {関数名}で指定した関数名の関数をローカル環境で実行できます。

sls invoke local -f hello
# => {
# =>     "body": "{\"input\": {}, \"message\": \"Go Serverless v1.0! Your function executed successfully!\"}", "statusCode": 200
# => }

LINE Notificationを送る関数を記述

LINE Notificationの使いかたはこちらの記事でも解説しています。

まず、LINE Notifyのページからアクセストークンを取得し、環境変数LINE_NOTIFY_TOKENに書き入れます。

export LINE_NOTIFY_TOKEN=XXXXXXXXX # bash zsh等の場合
set -x LINE_NOTIFY_TOKEN XXXXXXXXX # fishの場合

次に以下のようにhandler.pyを書き換え、LINE NotifyのAPIを叩くコードを追加します。

# -*- coding: utf-8 -*-

import os
import json
import requests

def mezamashi(event, context):
    output_url = "https://notify-api.line.me/api/notify"
    token = os.getenv("LINE_NOTIFY_TOKEN", "")

    headers = {"Authorization" : "Bearer "+ token}
    payload = {"message" :  "こんにちは"}

    response = requests.post(output_url ,headers = headers ,params=payload)

    return response.json()

def hello(event, context):
    body = {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "input": event
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response

次にpipコマンドでrequestsをインストールします。

pip install requests

次にserverless.ymlを書き換え、mezamashi関数に関数名を割り当てます。

service: lambdaMezamashi

provider:
  name: aws
  runtime: python2.7

  environment:
    # LINE Notifyのアクセストークンを記入
    # invoke localで実行する場合はこれを記述しなくても動作するが、デプロイ時に必要となる
    LINE_NOTIFY_TOKEN: XXXXXXXXXXXXXXXX

functions:
  hello:
    handler: handler.hello
  mezamashi:
    handler: handler.mezamashi

そうしたら、serverless invoke localを用いて動作確認を行います。

sls invoke local -f mezamashi
# => {
# =>     "status": 200,
# =>     "message": "ok"
# => }
# (LINEに通知が来る)

スケジュール実行の設定

Lambdaファンクションの実装が完成したので、これをスケジュール実行するように設定を書きます。 serverless.ymlfunctionsの部分にイベント設定を追記するだけです。

functions:

    #<中略>

mezamashi:
    handler: handler.mezamashi
    events: # イベントの設定を記述
      - schedule: cron(0 23 * * ? *) # cron式で日本時間の朝8時を指定

ついでにAPI Gatewayも使う

LambdaはAPI Gatewayと組み合わせることでwebアプリのバックエンドとして使用するケースもよくあります。 少し脱線しますが、ymlを用いてLambdaファンクションをAPI Gatewayと結びつける方法も説明しておきます。

eventsの項目に- http: {メソッド} {APIのURI}の形式で記述します。 こうすることでデプロイした際に自動でAPI Gatewayが作成されます。

functions:
  hello:
    handler: handler.hello
    events:
      - http: GET hello

オフラインでテストする

ファンクションが書けたので早速デプロイと行きたいところですが、その前にローカルホストでLambdaファンクションをホストして、APIのテストを行う方法を解説します。 これは、業務でデバッグAPIのテストコードを走らせる場合にも必要なことなので覚えておいて損はありません。

まず、必要なプラグインをインストールします。インストールはsls plugin install -n {プラグイン名}で行います。どうやらこのコマンドはnpm i --save-devをラップしてnode_moduleをインストールしつつ、serverless.ymlプラグイン情報を追記してくれるものらしいです。

sls plugin install -n serverless-offline
npm install serverless-offline-python --save-dev # slsコマンドからインストールできないためnpmを直接使う

次にserverless.ymlの末尾に以下のように書き込んでserverlessとプラグインを紐付けます*2

plugins:
  - serverless-offline
  - serverless-offline-python

serverless-offlineserverless-offline-pythonをインストールしたことで、serverless offlineというコマンドが使えるようになります。これによりローカル環境でcurlコマンド等を用いてAPIデバッグが可能になります。

sls offline
#=> Serverless: Starting Offline: dev/ap-northeast-1.
#=>  (中略)
#=> Serverless: Offline listening on http://localhost:3000

# =====他のterminal窓から以下を実行=====

curl localhost:3000/hello
#=> helloファンクションが動く

ちなみにserverless-offline-schedulerというオフライン環境でスケジュール実行のデバッグができるプラグインがありますが、Node.jsで書いたものしか動かないみたいです*3

デプロイする

最後にデプロイの方法を説明します*4

AWSアカウントの設定

最初にAWSアカウントの設定をします。

AWSのマネジメントコンソールにログインし、IAMから新規ユーザーを追加します。 f:id:pf_siedler:20180822104021p:plain f:id:pf_siedler:20180822104717p:plain

ユーザー名を適当に設定し、「プログラムによるアクセス」にチェックを入れます。 f:id:pf_siedler:20180822104040p:plain

「既存のポリシーを直接アタッチ」から「AdministratorAccess」を選択します*5 *6f:id:pf_siedler:20180822104044p:plain

ユーザーの作成が完了したら、「アクセスキーID」と「シークレットアクセスキー」を控えておきます。シークレットアクセスキーはこのときにしか確認できないため、忘れた場合には再度アカウントを作成する必要があります。 f:id:pf_siedler:20180822104034p:plain

これを、sls config credentialsコマンドを用いて設定します。XXXXXの箇所は適宜アクセスキー、アクセスキーシークレットに書き換えてください。

sls config credentials --provider aws --key XXXXX --secret XXXXX

デプロイ、実行、削除

アカウントの設定が済んだら遂にデプロイです。

Serverless Frameworkはsls deployコマンドを実行するだけで簡単にデプロイできるようになっています。 しかし、今回のようにPythonで実装している場合は、自分で書いたコードだけでなくサードパーティ製のライブラリも一緒にデプロイする必要があります。

serverless-python-requirementsというプラグインをインストールするとデプロイ時に自動でこの作業をやってくれるため、今回はこのプラグインを利用することにします。

sls plugin install -n serverless-python-requirements # プラグインのインストール
pip freeze > requirements.txt # 依存関係をrequirements.txtというファイルに書き出す

上記のコマンドを実行したら、デプロイします。

sls deploy
#=> Serverless: Installing requirements of requirements.txt in .serverless...
#=> Serverless: Packaging service...
#=> (中略)
#=> service: lambdaMezamashi
#=> stage: dev
#=> region: ap-northeast-1
#=> stack: lambdaMezamashi-dev
#=> api keys:
#=>   None
#=> endpoints:
#=>   GET - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello
#=> functions:
#=>   hello: lambdaMezamashi-dev-hello
#=>   mezamashi: lambdaMezamashi-dev-mezamashi

これで完成です。毎朝8時にLINEに通知が来るようになりますし、https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/helloURIcurlコマンドやAjax通信でアクセスするとhelloファンクションをWeb APIとして叩くことができます。

デプロイしたアプリの削除も簡単に行うことができ、sls removeコマンドを実行するだけです。

sls remove
#=> Serverless: Getting all objects in S3 bucket...
#=> Serverless: Removing objects in S3 bucket...
#=> Serverless: Removing Stack...
#=> Serverless: Checking Stack removal progress...
#=> .............................
#=> Serverless: Stack removal finished...

さいごに

今回はServerless Frameworkの使い方を軽く説明しました。 Serverless Frameworkは他にもDynamoDBやS3といったサービスを操作することができ、その名の通りサーバレスなwebアプリケーションを構築することができます。

S3でwebページをホストし、Lambda & API Gatewayでバックエンドの処理を行い、DynamoDBやCognitoでユーザ情報を管理するという構築にすればどんなWebアプリでも作れそうですね。

参考資料

*1:was-python3だとローカルで動かす際にpyenv等を用いてver 3.6のPython環境を用意する必要があります。今回は面倒なので2.x系で我慢

*2:# sls plugin ~~ コマンドを走らせた際に自動で追記してくれるため、serverless-offline-python以外は不要かも

*3:実際にソースコードを確認したところ、Lambdaファンクションが記述されたjsファイルをrequireで読み込んで実行するという実装がされているようだった https://github.com/ajmath/serverless-offline-scheduler

*4:これ以降の部分で解説するコマンドを実行するとLambdaファンクションがAPI Gatewayを通して全世界から実行可能な状態になります。Lambdaファンクションに脆弱性が存在した場合には思わぬ被害を被る可能性があることを心に止めておいてください。

*5:公式ドキュメントでもAdmin権限を設定する(https://serverless.com/framework/docs/providers/aws/guide/credentials/)という解説されています。

*6:Serverless Frameworkは開発中でやれることがどんどん増えています。そのためAdmin権限でないと機能に制限がかかり、開発が面倒になる可能性があります。Amdin権限で開発しつつ、製品を公開する際に権限を必要最低限に絞るような運用が望ましいと思われます。

ほとんどのエンジニアには解けるらしいパズルを解いてみた

ジャバ・ザ・ハットリ氏作成の「ほとんどのエンジニアには解けるパズル」*1シリーズを全問解いたので感想とかwrite upとかを書きます。

出題者のブログはこちら

問題一覧はこちら(全8問)

目次

感想

問題はマイルドなCTFみたいでした。CTFはakictfksnctfの問題をちまちま解いたり、SECCONの過去問を漁ったりしていたので、こうした形式のパズルには多少慣れていました。ここに上げたサイトの問題と比較すれば「ほとんどのエンジニアには解けるパズル」は簡単な部類なのかなと思います。

解くにあたってcurlコマンドの基礎を学ぶことができたのは収穫でした。もともとcurlコマンドに対する理解は「-Oコマンド付けてファイルを落とすやつでしょ」ぐらいだったので、一般的なサーバにリクエストを投げるという使い方を確認することができました。

その意味では、最初の3問は有意義でしたが、第4問以降は単なる数学パズルの色合いが濃く、学習という観点ではそこまで得るものはなかったのかもしれません。ただ、数学パズルやCTFの練習問題としては結構楽しかったと感じています。

Write up(ネタバレ注意)

復習、忘備録を兼ねてここに解法を残して置こうと思います。ここから先はネタバレが含まれているため、自力で解きたい方は読まないようにお願いします。

実のところ問題作成者はネタバレ非推奨と発言しているため、ここに解法を書くのはあまり褒められた行為ではないのかもしれません。しかし、curlコマンドの使い方等の技術的な知識は、

  1. 「未知の知識が必要な場面が生じる」
  2. 「使い方を調査する≒答えを見る」
  3. 「実際に使う」
  4. 「次回以降マニュアルを見ずに使えるようになる」

というプロセスを踏んで覚えるものであると私は思います。3の後に後戻りせず4に到達できるのであれば、2で閲覧する答えが解説書であっても、info curlの出力であっても、本記事のネタバレであっても本日的には変わらないのでは?むしろ、答えが参照しやすくなっている方が2の段階でつまずくことがなくなっていいのでは?という考えからここに解法を載せることにしました(一応ボタンを押さないと表示されないようにはしておきます)。

解説はHTTP通信などをほとんど知らない人が読んでもふんわりと理解できる程度には詳しく書くように心がけました。解いているうちに詰まってしまったら読んでみるのもいいのかもしれません。curlの使用例を知りたいという人も解説を見ながら第1問を解いてみるのもいいかもしれません。

第1問

第2問

第2問以降は暇があったら書きます(第1問程度の文量で書くと結構長くなりそうなので)。

*1:○○なら誰でもできるっていう釣りタイトル個人的にはきらい

AWS Lambdaを使ってAmazonコインを安く買う

先日AWS Lambdaを初めて使ったので忘備録を兼ねて書きます

今回書いたコードはここで見れます

Amazonコインの価格変動

1年ぐらい前からHearthstoneというデジタルカードゲームにハマっています。 Hearthstoneは課金でカードパックを買えるのですが、Amazonアプリストア版のAndroidアプリを使用すると、Amazonコインでの支払いが可能で、クレジットカードで直接買うよりより少し安くなります。

そんなAmazonコインですが、実は日々値段が変動しているみたいです。

Amazonコインの価格の変動・推移を調べてみる2018-2 | JJゃもの部屋 \(^o^)/ 破滅に向かって2011

↑の記事を見た感じ、10000コインが8100円のときにまとめ買いしておくとおトクなようです。

Amazonコインが安い日を見逃さないために、今回はpythonでwebスクレイピングを行いコインの価格を自動で追跡するスクリプトを作成しました。また、そのスクリプトAWS Lambdaで自動化しました。

Amazonコインの価格を自動で取得する

PythonAmazonコインの価格を追うスクリプトを書きました。

まず、venvを用いて仮想環境を作成し、必要となるライブラリをインストールします。

$ python3 -m venv amazoncoin
$ cd amazoncoin
$ source bin/activate # bash zsh等の場合
$ source bin/activate.fish # fish shellの場合
(amazoncoin)$ pip install requests beautifulsoup4
(amazoncoin)$ pip freeze > requirements.txt #必要なライブラリを書き出しておく(あとで使う)

10000Amazonコインの販売ページは https://www.amazon.co.jp/dp/B00KQVXDW0/ であり、価格はid=priceblock_ourpricespanタグに記載されているので、以下のようなスクリプトで価格を取得できます。*1

import requests
from bs4 import BeautifulSoup

def find_price():
    coin_url = "https://www.amazon.co.jp/dp/B00KQVXDW0/"

    resp = requests.get(coin_url, timeout=1)
    soup = BeautifulSoup(resp.text, 'html.parser')
    target = soup.findAll('span',{'id':"priceblock_ourprice"})
    return int(target[0].text[2:].replace(',', ''))

print(find_price())#=> 8100など

価格をLine Notifyで通知する

次に、取得した価格をLine Notifyを使って自分のLINEアカウントに通知することにしました。 [参考] https://qiita.com/hiro-abe/items/e42f857bd6b40bc178a3

下準備として、LINE Notifyのwebページでアクセストークンを取得し、環境変数LINE_NOTIFY_TOKENに値を書き入れておきます。

$ export LINE_NOTIFY_TOKEN=XXXXXXXXX # bash zsh等の場合
$ set -x LINE_NOTIFY_TOKEN XXXXXXXXX # fishの場合

そして、以下のスクリプトを実行するとLINEに通知が届きます。

import requests
from bs4 import BeautifulSoup
import os

def lambda_handler(event, context):

    output_url = "https://notify-api.line.me/api/notify"
    token = os.getenv("LINE_NOTIFY_TOKEN", "")
    
    price = find_price()
    headers = {"Authorization" : "Bearer "+ token}
    message =  '現在10000 Amazonコインは' + str(price) + '円です'
    payload = {"message" :  message}
    
    r = requests.post(output_url ,headers = headers ,params=payload)
    
    
    return 'Notify Amazon Coin Price is running'

lambda_handler(None, None) # => LINEの指定したトークルームに通知が来る

これで、script.lambda_handler関数を実行することで、Amazonコインの価格がLINEに届くようになりました。

AWS Lambdaで毎日自動で動かす

上記のスクリプトをASW Lambdaを用いて自動化します。 Lambdaを用いて自動化します。今回は毎朝8時に実行させるようにします。

スクリプトのパッケージ化

最初に作成したスクリプトをzipにまとめます。Lambdaは使用するスクリプトをzip形式でアップロードする方式になっているためです。*2 このとき注意しなければならないのは、標準ライブラリ以外のライブラリも一緒にzipにまとめる必要がある点です。

デプロイ用のディレクトリを作成し、そこに今回のスクリプトとライブラリをすべて設置し、zipにまとめることにします。

mkdir deploy #デプロイ用ディレクトリを作成
cp script.py deploy #今回作ったスクリプトをデプロイ用ディレクトリにコピー
pip3 install -r requirements.txt -t deploy/ #-tオプションでデプロイ用ディレクトリにライブラリをインストール
cd deploy/
zip -r package.zip * #パッケージを作成
mv package.zip ../

これで、作業ディレクトリにpackage.zipが作成されます。 動作確認として、以下のようなコマンドを実行したらスクリプトが正しく動作することを確認してください。

(amazoncoin) $ deactivate #仮想環境が有効になっている場合のみ
$ cd deploy/
$ python3 -c "import script;script.lambda_handler(None, None)"
# > Notify Amazon Coin Price is running
# (LINEに通知が来る)

AWS Lambdaの設定

パッケージが完成したので、早速Lambdaのセッティングをしてみます。

まず、Lambdaの管理ページから新しい関数を作成します。 ロールは「テンプレートから新しいロールを作成」を選択し、「Basic Edge Lambdaアクセス権限」を使用します。他は適当に入力してかまいません。 f:id:pf_siedler:20180415133028p:plain

次に、先程作成したpackage.zipをLambdaにアップロードします。 このとき、ハンドラには[pythonのファイル名].lambda_handlerを指定します。 f:id:pf_siedler:20180415133042p:plain

そして、環境変数の欄にLINE Notifyのアクセストークンを入力します。 f:id:pf_siedler:20180415133023p:plain

最後にタイムアウトの値を少し大きめの値に変更しておきます。*3メモリは最小の128mbで足りるのでデフォルトのままでかまいません。 f:id:pf_siedler:20180415133036p:plain

イベントタイミングの指定

次に通知が来るタイミングを指定します。 トリガーにCloudWatch Eventsを指定します。 ルール名と説明は適当に埋め、ルールタイプを「スケジュール式」、 スケジュール式をcron(0 23 * * ? *)を指定します。

f:id:pf_siedler:20180415133315p:plain

これで毎日グリニッジ標準時の23時、つまり日本時間の8時にスクリプトが実行されるようになりました。

おわりに

今回はpythonを用いてwebスクレイピングと、AWS Lambdaによる自動化を行いました。

今回、個人的につまずいた点は以下のとおりです。

  • ライブラリをzipに含めないとライブラリの関数が使えない点*4
  • イベントのトリガーに何を選べばいいのかわからない点

今後の課題として、価格がn円以下のときのみ通知を流すようにしたり、TwitterやDiscordに自動投稿させたりなんかをやりたいと思っています。

今回のプロジェクトはこちらのリポジトリで管理してあります。よかったらforkしてみてください。また、改善点などあればissueに書いてくださると助かります。

*1:後々、価格がn円以下なら通知するといった条件分岐をしやすくするために結果をint型で返すようにした

*2:一応、webページで直接コードを書く機能もある。zipでうpしたファイルをweb上で微修正するということも可能。

*3:今回のスクリプト実行には4秒ぐらいかかる場合があり、デフォルトの3秒ではタイムアウトが生じる場合がある

*4:もしかしたら、これを自動でやってくれるソフトがあるのかもしれないが見つからなかった

東京大学eeic3年後期実験「大規模ソフトウェアを手探る」2016年度まとめ

東大生がOSS開発やってみた…みたいな記事

「大規模ソフトウェアを手探る」って?

大規模ソフトウェアを手探る

「大規模ソフトウェアを手探る」は、東京大学工学部電気電子/電子情報工学科(eeic)で開講されている、学部3年生向けの実験科目です。

ネット上に公開されているOSSオープンソース・ソフトウェア)をひとつ選んで、それを改造するという内容の講義です。

2~3人でチームを組んで、数万〜数十万行のプログラムに体当たりで改造を試みることで、以下のようなことを体験するのがこの講義の狙いです。

  • 全容を把握できない程度に大きなプログラムを扱う
  • 他人が書いたコードに触れる
  • デバッガの扱い方を学ぶ
  • 先生やTA、他の学生からフィードバックをもらい、それを開発に活かす
  • チームでの開発を体験する

また、この実験では、成果物をブログ形式で公開することで、レポートの代替として認められています。 ネット上には「インストールしてみた」「ライブラリを使ってみた」系の記事がたくさんあり、後を追う誰かの助けになっています。 我々が実験を通して得た知見も、誰かの役に立つのではないか?ということで、ブログ形式のレポートを認めているようです。

この記事では、そんなレポートの数々を一覧にしてみました。

OSS開発に興味がある方、学科選びに悩んでいる一二年生、そしてどの実験を受講しようか迷っている未来のeeic民の一助となれば幸いです。

目次


連載形式の記事については、初回あるいは目次のページを取り上げています。

コンパイラ・言語

gcc

結局なにをどう変更したのか(まとめ) - 27++'s Report

内容

  • 生成ファイルに自動で名前を付ける
  • 末尾の;を補完する

Python その1

Python3.x系でprint命令文を追加する話. - Qiita

Pythonにプライベート変数を実装しようと試みた話。 - Qiita

内容

  • Python3.x系でもprint文を使えるようにする
  • private変数を実装する

Python その2

Pythonを改造してみた はじめに - 開拓馬’s blog

内容

テキストエディタ

Vim

Vimにnanoみたいなコマンドチートシートを表示した話 - チョコの包み紙の裏

内容

nano

nanoを手探ってみる#1: ビルドする - meloidae’s blog

内容

  • tabキーでファイル名を補完する
  • 起動後でもnanorcのリロードをできるようにする
  • 雪を降らせる

ブラウザ

FireFox

大規模ソフトウェアを手探る Firefox - Qiita

内容

  • url欄の文字を隠す
  • 右クリックからタブの複製を行う
  • 新規タブを開くとクリップボードの内容を自動で検索する
  • タブを複数選択して一気に閉じる機能を付ける

Chromium

Chromiumを手探った#1 - Chromiumをビルドしよう - しゅわしゅわdedede。

内容

  • 新規タブで表示されるページをユーザが設定できるようにする

OpenTween

OpenTweenを手探ってみた#1 - Qiita

OpenTween 目次 : トルネード一門計算機室

内容

  • お気に入り一覧を登録順にする
  • 同内容のつぶやき(パクツイ)を検索する
  • フォロー/フォロワーの変化を可視化する
  • 時間制限付きのミュート機能を追加する

ユーティリティ

Libre Office

LibreOfficeを手探る - libsoft’s blog

内容

  • spaceを入力すると下付き文字モードが解除されるようにする

Rhythmbox

Rhythmboxで1曲ループを実現する −0− - かるぅあみるく(仮)

内容

  • 一曲ループ機能を追加する

GIMP

GIMPの履歴機能に制限を加えてみた - team-cの日記

内容

  • 変更履歴の数が増えすぎてフリーズする問題を防ぐ

まとめは以上になります。

昨年度にこの講義を受講されたid:SWIMATH2さんが、15年度のまとめ記事を作成してくださっています。

東京大学eeic3年後期実験「大規模ソフトウェアを手探る」2015年度まとめ - クフでダローバルな日記

こちら記事に「来年以降のeeicの後輩たちがまたまとめてくれるのを楽しみにしています。」とあったので、大変僭越ながら便乗させていただきました。

17年度以降も、後輩たちがまとめてくれるのを楽しみにしています。

Mac bookにBasicTeXを導入してみた

先日Mac BookのOSを再インストールしたのでTeXを入れ直し、ついでにLuaLaTeXに乗り換えた話をします。

BasicTeXを入れる

Mac用のTeX環境として、MacTeXというものがあります。 とりあえずこれをインストールすればMacTeXが使えるようになるのですが、インストーラのサイズが2.8Gもあり、ダウンロードに1時間程度かかって面倒です。 そのうえあまり使わないGUIアプリがついてきてLaunchpadが汚染されます。 *1

私の場合、Emacsのorg-modeやPandocを使ってMarkdownTeXに変換するのが主な利用法なので最低限のコマンドが使えれば十分です。

そこで最小限の機能だけ持ったBasicTeXを入れることにしました。

MacTeXのサイトからBasicTeX.pkgをダウンロードします。 BasicTeX.pkgを起動しインストーラの指示に従ってインストールします。

インストールが完了すると/usr/local/texlive/2016bacis(数字はバージョンによって異なる)というディレクトリが作成されます。 *2

必要なパッケージを入れる

BasicTeXは本当に最小限で日本語環境が使えないので必要なパッケージを入れます。

ターミナルで以下のコマンドを実行します。

% sudo tlmgr update --self —all
% sudo tlmgr install collection-langjapanese

ちなみにtlmgrはTeXLive managerの略だそうです。 *3

collection-langjapaneseにはluatex-jaというパッケージが入っており、LuaTeXを日本語で使えるようになります。

ghostscriptImageMagickを入れる

次にghostscriptImageMagickをインストールします。これらは画像処理やpdf関連の処理を行うプログラムで、TeXをpdfに出力する際に使われるそうです。

brew install ghostscript
brew install imagemagick

インストールにはhomebrewを使用しました。「homebrewって何?」「brewコマンドが使えない」って人は"homebrew"で検索してみてください。

LuaLaTeXで書いてみた

LuaTeXの詳細はTeX Wikiの説明に丸投げします。ざっくりまとめるとLuaというプログラミング言語LaTeXを混ぜたシステムです。

フォントの指定がわりと楽にできたり、Unicode文字に対応していたりとLaTeXより便利な点がいくつかあるのですが、 一番の利点はdviファイルを経由せずにpdf出力ができる点です。 *4

% platex report.tex
% dvipdf report.dvi
    #LaTeXではこんな感じにやっていたのが

% lualatex report
    #LuaTeXだとこれでOK

とりあえずluatex-ja Wikiにあるサンプルを出力してみました。

\documentclass{ltjsarticle}
\usepackage{luatexja} % ltjclasses, ltjsclasses を使うときはこの行不要
\begin{document}
\section{はじめてのLua\TeX-ja}
ちゃんと日本語が出るかな?
\subsection{出たかな?}
長い文章を入力するとちゃんと右端のところで折り返されるかな?
大丈夫そうな気がするけど.ちょっと不安だけど何事も挑戦だよね.
\end{document}

上記をtest.texの名称で保存しpdf出力してみます。

% lualatex test.tex
  :
  :
! LaTex Error: File `filehook.sty’ not found.

どうやらfilehook.styが必要なのにインストールされていないのでエラーが発生したようです。tlmgrを使ってfilehookをインストールします。

% sudo tlmgr install filehook

これに限らず、hoge.sty not found.というエラーが発生した場合、sudo tlmgr install hogeでスタイルファイルをインストールする必要があります。

何度か「実行」→「エラー」→「スタイルファイルを入れる」を繰り返すと出力が成功します。

f:id:pf_siedler:20161123141357p:plain

画像が表示されない場合

以降はLuaTeXを使い始めたばかりのときに躓いた部分を紹介します。同じような失敗をしている人の参考になれば幸いです。

LaTeXで画像を使用する場合、ヘッダ部分に

\usepackage[dvipdfmx]{graphicx}

と記述します。

一方でLuaLaTeXで画像を使用する場合、ドライバ指定オプションは不要です。

\usepackage{graphicx} %LuaTeXではこう

ここでドライバ指定を付けたまま出力すると、画像の部分が白抜き状態になります。

\documentclass{ltjsarticle}
\usepackage{luatexja}
\usepackage{graphicx} %[dvipdfmx]は不要
\begin{document}
\section{はじめてのLua\TeX-ja}
ちゃんと画像が出るかな?

\begin{figure}[h]
  \includegraphics[width=10cm]{test.jpg}
  \caption{Lenna}
\end{figure}
\end{document}

f:id:pf_siedler:20161123141531p:plain

オプションを付けたままだとこんな感じ

f:id:pf_siedler:20161123141509p:plain

筆者はネットから拾ってきたLaTeXのサンプルコードを何も考えずにコピペした結果、この症状に見舞われ数時間無駄にしました。

脳死コピペ、ダメゼッタイ

*1:個人の感想です。TeXShopは人によっては便利かも

*2:フルパッケージのMacTeXの場合、ディレクトリがtexlive/2016のように数字のみになる。MacTeX関連の記事を読む場合、2016→2016bacisと読み替える必要がある

*3:こういう略語の元の言葉を覚えておくとコマンドを暗記しやすい気がする

*4:pdf直接出力はLuaLaTeXの派生元であるpdfTeXで実装された機能です。pdfTeXは国内ではマイナーですが、海外ではよく使われているそうです。

Pythonを改造してみた プロンプトをうるさくしてみた

前回まではPythonに新たな予約語を追加してきました。Pythonは与えられたコードをASTなどの中間データに変換しながら、最終的には擬似的なバイトコードを生成しceval.cで処理するという方式を取っていることがわかりました。

しかし、この記事では文法関係の話はまったくしません。というのも、Python改造に取り組むきっかけとなった講義でgdbの使い方を習ったのですが、私達はデバッガまったく使わずにここまでの改変をやってきたのです。せっかくなのでデバッガを使ってコードを読み解きたいと思います。

gdbPythonを追う

ビルドまでの経緯はこちら

Emacs上でM-x gud-gdbを入力しデバッガを起動します。 mainブレークポイントを置いて引数を入れずに(つまりインタラクティブモードが起動するはず)runします。 すると、Programs/python.cが開きました。

そのままnextstepを連打していくと、おおよそ、以下のような動きをすることがわかりました。

  1. Modules/main.cに移動し、各種初期化処理を行う
  2. Python/getopt.c内のサブルーチンでオプションを受け取る
  3. main.cに戻り、オプションに従ってフラグを切り替える
  4. 引数からソースコードの名前を取得、存在しない場合、インタラクティブモードを開始するフラグを建てる
  5. Python/pythonrun.cIntaractiveloop()関数に来る
  6. プロンプト">>> "が表示される

上記の流れを見た上で、やることを決めました。

メッセージの書き換え

Pythonをファイル名を指定せずに実行すると、インタラクティブモードが起動します。インタラクティブモードを終了したい場合は、ctrl+Dを入力するか、exit()関数、またはquit()関数を実行する必要があります。この仕様がちょっと不親切で終了しようとしてexitと入力するとUse exit() or Ctrl-D (i.e. EOF) to exitと表示され、終了してくれません。 *1

exitと入力すれば終了できるようにするのは複雑な例外処理を行う必要がありそうなので仕方ないのかもしれません。しかし、終了の仕方を説明していないのも不親切だと私は思います。「消し方が分からない、やめ方が分からない」ソフトはユーザーをイライラされるものだと思います。

そこで、Python起動時に表示されるメッセージに「exit()quit()と入力すると終了できます」という説明文を付け足すことにしました。

サクッと完成

grepで該当部分の起動時のメッセージを検索したところ、main.c内でそれらしい文字列が#defineされているのを発見。書き換えます。

#define COPYRIGHT \
    "Type \"help()\", \"copyright\", \"credits\" or \"license()\" " \
    "for more information.\nType \"exit()\" or \"quit()\" or Ctrl-D to exit."

ついでに、"help"、"license"の後ろに括弧を付けました。 *2 ビルドして実行したところ、メッセージが変化しました。

% ~/mypython/bin/python3
Python 3.5.2 (<<略>>)
[GCC 4.8.4] on linux
Type "help()", "copyright", "credits" or "license()" for more information.
Type "exit()" or "quit()" or Ctrl-D to exit.
>>>

ほんとにちょっとした変更ですが、多少は使いやすくなったのかな……?

オプションを追加 おもしろ機能

-a--hogeのようなハイフンから始まるオプションはCUIアプリにとって欠かせないものです。 オプションを受け取る部分の処理はgetopt.cようなので、ちょっと書き足して新しい予約語を足してみようと思います。

それでは追加するオプションにはどんな機能を付けましょうか。先程と同じ文字列の変更なら簡単にできそうです。プロンプトの">>> "を適当に他の記号に変えてみることにしました。

そんなわけで、Pythonの名前にちなんで--spamオプションを付けるとプロンプトが">>> "から"SPAM>> "に変わるという機能を作ってみることにしました。 *3

spamオプションの追加

getopt.c--spamオプション用の処理部分を追加します。ハイフン2つ+単語のオプションには--help--versionが存在するので、それを真似します。

else if (wcscmp(argv[_PyOS_optind], L"--version") == 0) {
    ++_PyOS_optind;
    return 'V';
}

#if SPAM_MODE
else if (wcscmp(argv[_PyOS_optind], L"--spam") == 0) {
    ++_PyOS_optind;
    return 'P';
}
#endif

ヘルプ表示、バージョン表示は-h -Vでもできます。どうやら--helpを受け取った場合、内部で-hに変換するという方式を取っているようなのでspamにもアルファベット一字のオプションを割り当てることにします。spamの頭文字である-s-Sはすでに使われているのでs"p"amの-Pを割り当てました。

ビルドして実行したところ、--spamオプションを付けても"Unknown option"エラーが発生しませんでした。

% ~/mypython/bin/python3 --hoge #存在しないオプションを付けるとエラーを起こす
Unknown option: --
<<略>>

% ~/mypython/bin/python3 --spam #普通に起動した
Python 3.5.2 (<<略>>)
[GCC 4.8.4] on linux
<<略>>

% ~/mypython/bin/python3 -P #普通に起動した
Python 3.5.2 (<<略>>)
[GCC 4.8.4] on linux
<<略>>

spamモードの追加

main.c--spamオプションを受け取ったときの処理を付け加えます。プロンプトの表示はPython/pythonrun.cで行われるのでspamフラグが立ったことを伝えなければなりません。とりあえずグローバル領域でフラグ変数を定義して伝えることにしました。

/* command line options */
#if SPAM_MODE
#define BASE_OPTS L"bBc:dEhiIJm:OqRsStuvVPW:xX:?"
int Active_spam_mode = 0;
#else
#define BASE_OPTS L"bBc:dEhiIJm:OqRsStuvVW:xX:?"
#endif
      :
      :
      while ((c = _PyOS_GetOpt(argc, argv, PROGRAM_OPTS)) != EOF) {
          if (c == 'c') {
      :
          switch(c){
      :

              case 'V':
                  version++;
                  break;
              case 'P':
                  Active_spam_mode++;
                  break;

pythonrun.cPyUnicode_FromString()関数の引数に">>> "という文字列を与えているのを発見。適当に他の文字列にしてビルドしてみるとプロンプトが変わりました。どうやらこの部分でプロンプトの文字を定義しているようなので、フラグが立っているときだけ"SPAM>> "を与えるように変更します。

#if SPAM_MODE
extern int Active_spam_mode;//main.cで定義したグローバル変数をextern

char* spam_message()//あとで拡張できるように関数にしておく
{
    return "SPAM>> ";
}
#endif
      :
      :
#if SPAM_MODE
      if(Active_spam_mode) {
      _PySys_SetObjectId(&PyId_ps1, v = PyUnicode_FromString(spam_message()));
      }
      else {
        _PySys_SetObjectId(&PyId_ps1, v = PyUnicode_FromString(">>> "));
      }
#else
        _PySys_SetObjectId(&PyId_ps1, v = PyUnicode_FromString(">>> "));
#endif

実行してみます。

% ~/mypython/bin/python3
Python 3.5.2 (<<略>>)
[GCC 4.8.4] on linux
<<略>>
>>>

% ~/mypython/bin/python3 --spam
Python 3.5.2 (<<略>>)
[GCC 4.8.4] on linux
<<略>>
SPAM>> print("やったぜ。")
やったぜ。
SPAM>>

プロンプトがが変わりました!spamモード実装完了です!

文字列をランダムに表示させる

ただプロンプトが変わるだけでは物足りないので、複数パターンのプロンプトがランダムに表示されるように変更してみます。

どうやら標準ライブラリをインクルードしても問題なくビルドできるようなのでstdlib.htime.hを用いた一番簡単な乱数生成を使うことにします。 *4

プロンプトのレパートリーは適当に5~6個用意しました。

//time.h stdlib.hのインクルード
#if SPAM_MODE
#include <stdlib.h>
#include <time.h>
#endif

#if SPAM_MODE
extern int Active_spam_mode;

char* spam_message()
{
  int c;
  static int f;
  if(!f){
    f = 1;
    srand((unsigned int)time(NULL));
  }

  c = (int)random()%10;
  switch(c){
  case 0:
  case 1:
    return "  _____ _____        __  __ \n / ____|  __ \\ /\\   |  \\/  |\n| (___ | |__) /  \\  | \\  / |\n \\___ \\|  ___/ /\\ \\ | |\\/| |\n ____) | |  / ____ \\| |  | |\n|_____/|_| /_/    \\_\\_|  |_>> ";  
  case 2:
  case 3:
  case 4:
    return "spam>> ";
  case 5:
  case 6:
    return "SPAM>> ";
  case 7:
    return "\n\n _____ _   _ _____  ___  ________ _   _ _   _ \n|_   _| | | |  ___| |  \\/  |  ___| \\ | | | | |\n  | | | |_| | |__   | .  . | |__ |  \\| | | | |\n  | | |  _  |  __|  | |\\/| |  __|| . ` | | | |\n  | | | | | | |___  | |  | | |___| |\\  | |_| |\n  \\_/ \\_| |_\\____/  \\_|  |_\\____/\\_| \\_/\\___/\n==============================================\n\n1.egg bacon and spam\n2.egg bacon sausage and spam\n3.spam bacon sausage and spam\n4. ";
  case 8:
    return "\\(^o^)/SPAM\\(^o^)/SPAM\\(^o^)/>> ";
  case 9:
    return "Nobody expects the Spanish Inquisition!\nOur two weapons are fear and surprise...and ruthless efficiency...\nOur THREE weapons are fear, surprise... ";
  default:
    return ">>> ";
  }
}
#endif

プロンプトがうるさいインタラクティブモード

ビルドして実行してみました。

f:id:pf_siedler:20161106171817p:plain
こんな感じでプロンプトがうるさくなります f:id:pf_siedler:20161106171821p:plain
スパムのスケッチに登場する食堂、メニューを書き足してみようみたいなイメージ f:id:pf_siedler:20161106171825p:plain
もはやスパムとは無関係なネタ、スペイン宗教裁判も

実は乱数の初期化がうまく行っておらず毎回同じ順番でプロンプトが登場したり、Macだとアスキーアートが崩れたり、粗がありますが完成ということで……

*1:おそらくexitやquitのreprあるいはstrの戻り値を"Use ~ to exit"にしているのだと思われる

*2:説明通りに"help"や"license"と入力すると"Type help()"というメッセージとともに関数の使い方が説明される

*3:モンティ・パイソンというコメディグループのネタ。ちなみにPythonの公式ドキュメントにもメタ構文変数としてspamが登場する

*4:stdlib.hの擬似乱数は性能がよくないので使用を控えるべきという意見もあるが、今回は乱数が偏ってもあまり問題にならないので使うことにした