ISUCON8で予選敗退してきました
昨日、ISUCON8のオンライン予選があり予選敗退してきたのでその様子を報告します。
牛久大仏「う〜ん、顔採用w!」 というチーム名で筑波に編入した同期の@k5342, @chigichan24とISUCON8オンライン予選に参加してきました。
↓↓チームメンバーの記事はこちら↓↓
Rubyで参加しBest Scoreは5000点、最終Scoreは4000点と学生枠ボーダーには全然届かない点数でフィニッシュしました。
サーバはserver1: DB, server2: app1, server3: app2として利用し、
app1にベンチに来てもらい、ラウンドロビンでapp1とapp2にロードバランシングするといった構成になっていました。
スコアが目に見えて上がったのは複数台構成に変更したときくらいでした。
もちろんやってないこともたくさんあったけど、それなりに高得点者の歩いていた道を辿っていた気がするのでもう少し点数伸びてもよかったなぁというのが正直な感想です。
N+1潰したし,静的ファイルのキャッシュはしたし,複数台構成にして負荷分散したし,CSVも微妙にメモリに乗らないように軽くしたが....だめだった.
— プリキュア (@chigichan24) September 16, 2018
以下詳細です。
また、自分が関係するムーブだけ書いていきます(他のメンバーのムーブはブログに書いてくれるはず)
時刻 | 作業内容 |
---|---|
前日 | |
16:00 | Redisの使い方をブログにまとめる→ これ |
22:00 | 1台構成→複数台構成時に変えたときに起こりうる問題を色々考える 今まで1台構成でばかり解いていたので色々考えることがあることが分かって、色々調べた |
当日 | |
8:00 | |
10:05 | DBのschemaを確認する |
10:30 | 自分のポートでwebサーバを立ち上げようとするが失敗する、ここで時間を多少消費する(firewalld周り) |
11:30 | reservationsのreserved_atにindexを貼ってみる |
11:45 | kataribe, myprofierを入れる(nginxに置き換え作業が発生していたため待ち時間が発生する) |
←DBめちゃ重との連絡を受けたのでN+1を潰していくことにした | |
15:00 | get_eventsから呼ばれてるget_eventのみ修正しN*N+1をN+1に改善した #6 サクッと書いたクエリを「ほんまに合っとんかこれ?」と3人で悩む時間が発生していた(合っていた)。ここでかなりのタイムロスをした感がある |
15:20 | get_eventsのtransaction要らなくね?となったので外した #7 |
16:30 | get_eventをN+1→1にした #8 クエリ自体は1になったけど処理方法がよくなかった(Array#selectでぐるぐる回しまくっていた) |
17:00 | 開発に使っていたサーバからの移動を命じられ、いそいそと移動する |
17:15 | app serverのメモリがやべえという連絡を受け、メモリを節約できそうな場所を探すがよくわからん(残り1時間を切っていたのでget_eventを複数回呼んでいるところの修正は諦めた) |
17:30 | メモリ使用を減らすためにとりあえずSELECT *を消した、が終了直前だったのでマージは見送られた #10 |
17:45 | app serverの再起動確認を行った&ベンチにenqueueされる様子を眺めていた |
17:55 | やることがなくなったので反省会開始
|
ISUCON終了 | |
18:30 | 同じ場所でISUCONに参加していたluvtechno(カニオムライス), qnighy(カニオムライス), Altech(カニオムライス), gedorinku(winjisucon)と一緒に反省会会場(焼肉)へ移動 ↑反省し終わった(肉を撮り忘れていた) Wantedly社員の人が反省会費用を出してくれた(特にCTO)!ありがとうございます! |
22:30 | つくばへ帰り反省会2次会(ラーメン)へ移動 |
ISUCON第2ラウンド開始 | |
23:45 | 色々とやってないことはあったのでおもむろにPRを作り始める |
24:00 | get_eventsでpublicなイベントだけ欲しい場合も一度全てのイベントを取得してからフィルタリングしていたのでSQLで行うように変更 #11 |
24:30 | get_eventの修正後の処理がよくなかったので最適化した #12 |
25:20 | get_eventをループ内で複数回呼んでるところ用にget_events_from_idsを作った、これが動けばN+1は全滅のはず #13 |
26:00 | メモリが足りないとのことだったので不必要な変数の取り回しをなくした #14 |
おまけ
また、今回は自分のインターン先のWantedlyオフィスを会場として使わせてもらいました。
チームメンバーのうち1人は元インターン生、1人はWantedly全然関係ないという状況でしたが快諾してもらってありがとうございました!
所感
- 初動が遅かった
コードを書き始めたのが13:00ごろだった、このころには1万点弱に到達していたチームもちらほらいた気がする - 例年よりも作り込まれていてすごいと感じた
フロントエンドはvue.jsで書かれていて時代の波を感じた p
debugよりもbinding.pry
の方がやはり優秀- 帰ってからlocalで開発してみた結果、明らかにlocalで開発した方が実装速度が早いことが判明した(vim難しい)
- 来年からは社会人として出るのでもっと力をつけていきたい
【ISUCON用】Redis Cheat Sheet
ISUCON予選が明日に迫ってきました。
Redisの操作が全く覚えられないのでまとめておきます。
セッティング
インストール
この辺とか見たらいい感じにできるのではないでしょうか(未確認)
使い方
# デフォルトは127.0.0.1:6379で繋がるはずなのでとりあえずこれで def redis @redis ||= (Thread.current[:isu8_redis] ||= Redis.new(host: "127.0.0.1", port: 6379)) end # keyはこんな感じでメソッドで隠してあげるとtypoが減ってよさそう def redis_key_unread_count(channel_id) "isu7:unread_count:#{channel_id}" end # redisの操作もこんな感じでメソッドで隠してあげよう def redis_increment_unread_count(channel_id) user_ids = redis_get_user_ids user_ids.each do |user_id| redis.hincrby(redis_key_unread_count(channel_id), user_id, 1) end end
Redis豆知識
格納されているフォーマットについて
Redisには数値という概念がなく、一度全て文字列として格納されている。 (incrとかは文字列を数値に変換してから+1して文字列に戻すなどの操作をしているっぽい)
そのため、redis.get ...
とかで取得した数値は毎回to_i
する必要がある。
redis-cliでのデバッグ
- とりあえず格納されているkeyを全部見たい
keys *
- 一旦格納されてるデータ全部消したい
flushall
データ型
つらつら書いていこうかと思ったけどドキュメント見ればわかると思ったのでひとことに留めておきます。
String
標準的なkey value
get
, set
, mget
, mset
, incr
, incrby
とかをよく使う気がします。
文字列型 — redis 2.0.3 documentation
# 使う可能性のあるコマンド一覧 SET(key, value) GET(key) # なければnilを返す GETSET(key, value) MGET(key1, key2, ..., keyN) SETNX(key, value) MSET(key1, value1, key2, value2, ..., keyN, valueN) MSETNX(key1, value1, key2, value2, ..., keyN, valueN) INCR(key) INCRBY(key, integer) DECR(key, integer) DECRBY(key, integer)
List
Listです。
両端からアクセスできる(rpush
, lpush
)けどisuconだとListは帯に短し襷に長し感があります。
Hashを使った方がいい場合の方が多そう。
idを保存しておいてllen
とかでSQLのcountの擬似実装とかには使えるかも?
リスト型 — redis 2.0.3 documentation
# 使う可能性のあるコマンド一覧 RPUSH(key, string) LPUSH(key, string) LLEN(key) LRANGE(key, start, end) # key, 0, -1 で全取得 LINDEX(key, index) LSET(key, index, value) LPOP(key) RPOP(key)
Set
sorted setを使おう。setを使いたい場面ってisuconであんまり思いつかない。
セット型 — redis 2.0.3 documentation
Sorted Set
score付きのset。コマンド名に癖があるけどなんかすごい高機能。
DBのtableをそのままredisに載せようと思ったら
- Hashとしてそのままデータをredisに突っ込む
- Sorted Setでkeyをテーブル名、memberをhashのkey, scoreをDBのindexとして使う
みたいな感じになるんじゃないかな。(やったことない)
scoreの範囲を指定して取得とかできるので本当にtableと置き換えられる。
ただ、データの変形をせずにそのままテーブルの置き換えてパフォーマンスがどのくらい変わるのかはよく分かっていない。
ソート済みセット型 — redis 2.0.3 documentation
# 使う可能性のあるコマンド一覧 ZADD(key, score, member) ZCARD(key) # memberの数を返す ZSCORE(key, member) # 対応するmemberのscoreを返す ZREM(key, member) # memberを削除 ZINCRBY(key, increment, member) ZRANK(key, member) # 指定したmemberのスコア順にソートされた場合のindexが取得できる ZREVRANK(key, member) # ZRANKの降順版 # indexで範囲指定してmemberを取得できる、WITHSCORESをつけると対応するscoreも取得できる ZRANGE(key, start, end, [WITHSCORES]) ZRANGE(key, start, end, [WITHSCORES]) # ZRANGEの降順版 # scoreで範囲指定してmemberを取得できる。LIMIT, offsetも指定できるので実質SQL ZRANGEBYSCORE(key, min, max, [LIMIT, offset, count, [WITHSCORES]]) ZCOUNT(key, min, max) # ZRANGEBYSCOREのCOUNT版 # 複数のsorted setから和集合、積集合を作る。共通するmemberのscoreは和をとる。 # ランキング計算とかするときにめちゃくちゃ便利なんだけど、複雑なので時間が余らないと使わない気がする? ZUNIONSTORE(dstkey, N, k1, ..., kN, [WEIGHTS, w1, ..., wN], [AGGREGATE, SUM|MIN|MAX]) ZINTERSTORE(dstkey, N, k1, ..., kN, [WEIGHTS, w1, ..., wN], [AGGREGATE, SUM|MIN|MAX])
Hash
Hash。tableをそのままredisに持ってこようとするような荒技を使うときに使う。 操作方法自体はStringと大差ない。
ハッシュ型 — redis 2.0.3 documentation
# 使う可能性のあるコマンド一覧 HSET(key, field, value) HGET(key, field) HMSET(key, field1, value1, ..., fieldN, valueN) HMGET(key, field1, ..., fieldN) HINCRBY(key, field, value) # 負数を指定すればデクリメントもできる HEXISTS(key, field) HLEN(key) HKEYS(key) HVALS(key) HGETALL(key)
雑にaxiosの便利ラッパー書いた
フロント書くときにaxiosが便利なのでよく使うんだけどHTTPメソッドごとで引数の型が若干違うので毎回忘れてググってる。
なので引数が同じになるようなラッパーに雑に書いてgistにあげておくことにした。
特に変なことはしてないのでコピペで使えるはず。
RubyKaigi 2018に行ってきた【Day1】
まだ途中ですが、公開してから考えようと思います。
Keynote
ことわざの話
「名は体を表す」= 名前重要
ソフトウェアの世界には物理的制約がなく、概念で構成されているため名前はとても重要。
なのでソフトウェアエンジニアは概念に名前をつける必要がある。
1. 振る舞いに名前をつける
クラス、メソッド、変数に名前をつけるのがこれに該当
よく「メソッドの命名が難しい」と言うが、名前をつけるのが難しいのではなく十分に概念を理解できていないだけ
使う場面、機能を十分に理解していれば難しくない
なのでいい名前がついている機能は自然と良い機能・実装となる
Rubyにはyield_self
というメソッドがある
class Object def yield_self(*args) yield(self, *args) end end
何がしたいかという情報が入っていない
なので昨日コミットして新しいaliasを作った(matzがCRubyに機能面でコミットしたのは5年ぶりらしい!)
object.c: Add a new alias then
to Kernel#yield_self
· GitHub
thenという表現はmatzが推しているだけで他のRubyコミッターはみんな反対しているとのこと
2. プロジェクトに名前をつける
良い名前は求心力になる
例えばRuby
Rubyという名前をつけなかったら仙台に1000人以上集まってくれるような言語になってなかったんじゃないか
Rubyは良い名前付けだったなぁと思うが名前を付けたのは1993年の話
今つけるとしたらもっと違う名前をつけるかもしれない
今はググラビリティ(=Google検索時の単語のユニークさ)を意識した方がいい
悪い例としてGoやSwiftが挙げられる(www)
現代のプロジェクトの名前付けのトレンドとしては
- 単語を組み合わせる(Ruby on Rails, tensorflowなど)
- 既存の単語から1文字変えてみる
- 変な単語を選ぶ(kaminari, hanami, kibaなど) ← 何をやっているのか分からないという弊害もある
名前はとても重要なので、良いプログラマーは名前つけにsensitiveであるべき
「時は金なり」= Time is Value
時間をどのように使うかが自分の人生を決定づける
プログラミングにおける時間は2つあると思っている
開発時間 ≒ エンジニアがPCに向かってる時間
Rubyは生産性が高い
- 便利なメソッドがたくさんあるのでやりたいことはだいたい探せばある
- 色んなフレームワークがある
- Rubyを使っている人がたくさんいる
- 個人の言語の選択基準は「近くに詳しい人がいる言語」を選ぶべき
- (見かけ上)シンプルな構文
実行時間 ≒ コンピュータが処理する時間
パフォーマンスの話
Ruby 1.8以前は確かに遅かった
Ruby 2.6からはJIT compilerが入ったりとかなり改善されてきた
今まで言われ続けてきたconcurrency(=並列実行)もどんどん現実のものとなってきている
Ruby 3.0ではRuby 2.0の3倍速くなる
実行時間が短くなればサーバーのリソースを減らすことができて、その分が全て利益になる
「塞翁が馬」
人生の幸、不幸は予測できない=なにが起こるか分からない
1995-2004 Rubyを知らない人が多かった
2005-2012 普及し始め「Ruby is Great!」と言われるように
2013-2018 一時期の興奮が冷め安定期に入った
Rubyは死んだのか?という記事を目にするようになった
Rubyは毎年死んで毎年生まれ変わっている
Ruby is Dead Every Year
互換性は大事だが変化は止めてはいけない
RubyKaigiが「Rubyがもっとこうなったらいいなぁ」と考える機会になればいいなあと締めた。
Analyzing and Reducing Ruby Memory Usage
TODO: スライドを見つけたらまとめる
草生えるの人
feature cache Direct Isen - メモリをどのくらい使われているか調べる方法 - Reading code - Mall stack tracing - rubyは2つのヒープに分けられる - object space - allocation tracer - malloc stack logging :love: - プログラムがどのくらいメモリを使っているか - どこがメモリを食っているか - 誰がmallocしたのかが分からない - メモリ使用量を減少するパッチについて - feature cacheの最適化 - shared strings - 存在する文字列の添え字の調整で表現できるならそれで - 同じrequireは1度だけ - どうやって同じファイルとするのか? - 毎回requireされたかどうかチェックすると遅い - setupに時間がかかるようになる - key generation - ruby objectを消す - cacheを参照するときに生成していたruby objectを作成しないでよくする - rails アプリケーションのメモリ使用量4.2%減らせる - パッチを書く前にすでにないか調べてみよう - Stack VM - コード - AST - Linked List - Byte Code - Byte CodeはVMによって実行される - Linked List(T_NODE) -> ByteCode
puts “Hello World”
Hijacking Ruby Syntax in Ruby
www.slideshare.net Rubyの黒魔術を使って
final
override
abstract
with
defer
といった他の言語にある機能を作ってみたよ、という発表。
使った黒魔術たち
Binding#local_variable_set
はbindingした箇所でlocal変数を宣言できる
存在しない変数を定義したときはbindingのblockのスコープを抜けるとその変数定義は消える
存在する変数に対して定義したときはoriginalの変数定義を上書きする ← 🤔🤔🤔
↓
ということは好きなところで変数の中身をいじれる!
TracePoint
ほぼ全てのイベントをhookに処理を走らせることができる
- 行を移動したとき
- raiseが発生したとき
- classを定義したとき
- returnしたとき…
- bindingしたとき
これらの情報を合わせると コードの任意の箇所の変数を上書きできる!
refinements 前の記事を参照
finalist, overrider, abstrikerで使われているMethod Hook
Module#method_added
Module#method_removed
Module#method_undefined
BasicObject#singleton_method_added
BasicObject#singleton_method_removed
BasicObject#singleton_method_undefined
Rubyはmethod定義の方法がたくさんあるため、このくらいMethod Hookをつける必要がある。 同じようなことをしようと思ったときは気をつけてねという話でした
TODO: 途中
All About RuboCop
TODO: スライドを見つけたらまとめる
英語が分からんぞ、、、
Fast Numerical Computing and Deep Learning in Ruby with Cumo
TODO: スライドを見つけたらまとめる
chainerの人 PFN Cumo
CUDA programming GPUはやい コア数の桁が違う 行列演算早い
CUDA GPUのメモリをallocate CPU メモリ -> GPU メモリ GPU側で計算 特徴 非同期になる GPU likes a job queue
Numo -> Cumoの文字列置換で動く element-wise operation 要素ごとの足し算 並列的に処理がしやすい reduction operation 和をとったりするやつ 並列かさせるのむずかしい 行列の積にも対応 cuBLAS
cudamallocが遅くなりやすい memory poolを使おう 一番簡単な実装は配列 best-fit hashのchain法かな? 基本はchain法、ほしいメモリサイズよりも大きいものが余ってたら切り出して利用する
rubyでは文字列評価によって行う
NumoよりCumoの方がめっちゃ早い 75倍早い
GPUはGCと相性がよくないかも? mkmfの機能が足りない 第三のコンパイラの指定ができない broadcast operationが遅かった
すごそう
+=
のoverrideができない
A parser based syntax highlighter
TODO: あとでまとめる
既存のhighlighterは正規表現で実装されている atomはcsonという形式で書かれてる
それの問題点は?
RailsのService層とうまく付き合うにはどうすればいいのか調べてみた
「RailsのService層ってどう使っていくのがいいんだろうね?」って聞かれたときにすぐ答えられなかったのでまとめておきたいと思います。
※ Fat Modelの解決策としてTrailblazerが最近よく話題に上がりますが、私がまだ使ったことがないので触れない方向で行きます。
情報を漁る
まずは「Rails service」とググって検索して引っかかった記事を読みました。
- アクションが複雑になる場合 (決算期の終わりに帳簿をクローズする、など)
→ 複雑な処理をmodelから分離させたい- アクションが複数のモデルにわたって動作する場合 (eコマースの購入でOrder, CreditCard, Customer を使用する、など)
→ どのmodelに書けばいいのかよく分からないのでとりあえずserviceに書いとけ感ある- アクションから外部サービスとやりとりする場合 (SNSに投稿する、など)
→ 外部サービスだしServiceっぽい!- アクションが背後のモデルの中核をなすものではない場合 (一定期間ごとに古くなったデータを消去する、など)
→ 複雑な処理をmodelから分離させたい- アクションの実行方法が多岐にわたる場合 (認証をアクセストークンやパスワードで行なう、など)。これはGoF (Gang of Four) のStrategyパターンです。
→ 複雑な処理をmodelから分離させたい
- 一つのModelで複数のミドルウェアと通信する場合はService層に書く
→ 複雑なロジックをmodelから分離したい- 複数のModelが絡み合う処理はService層に書く
→ どのmodelに書けばいいのかよく分からないのでとりあえずserviceに書いとけ感ある- Service層では振る舞いだけを定義する(状態を持たないためModuleで設計する)
→ テストをしやすいようにする、1serviceに責務を負わせすぎないようにする
- クラス名には動詞と目的語と「Service」を付ける
→ ソースを読んだときにすぐServiceだと気づけるようにするため、命名を悩まないようにするため- 引数は出来る限りnewで渡してインスタンス化する
→ 1serviceに1つの役割だけを持たせるため、- 1つのサービスにpublicなメソッドは、原則1つにする
→ 1serviceに1つの機能だけを持たせるため、上と一緒- 初期化したインスタンスはprivateのattr_readerで呼ぶ → なるべく隠蔽したい、attr_readerにまとめることで可読性が上がる
- 切り分けたメソッドは全てprivateなgetterメソッドとして実装する
→ 可読性、隠蔽の面から
- 命名規則を1つに定める 記事では名詞+動詞orだと動詞に違和感がある場合があるので動詞+名詞の方がいいかもって言ってる
→ 可読性- Service Objectを直接インスタンス化しないようにする
→call
をClass Methodとして実装することでステートレスなメソッドとして実装したい- Service Objectの呼び出し方法を1つに定める
→call
,run
,execute
などの意味を持たないメソッドを使うよう義務付けてクラス名でどんな機能なのか表現できるようにする、serviceを利用しようと思ったときに調べなくて済むようになる- Service Objectの責務を1つに絞り込む
Manage
などの責務が曖昧な単語をserviceクラスの命名として使わないよう気をつける
→ 細かい粒度で作って疎にするため- Service Objectのコンストラクタを複雑にしない
→ 複雑なコンストラクタは責務が複雑な証拠。できるだけシンプルなserviceになるように心がける。- callメソッドの引数をシンプルにする 利用しやすいように、引数が2つ以上あるときはキーワード引数を使うとよい
- 結果はステートリーダー経由で返す
call
による副作用を期待するもの(DBに対する更新、メール/Slack送信など)だけではなく処理の結果を受け取りたいときはcall
の返り値をservice objectそのものにすると柔軟な処理がかけて良い。- callメソッドの可読性を下げないようにする
メソッドを分割して可読性を維持する- callメソッドをトランザクションでラップすることを検討する
→ 処理の中断によるバグをなくす- Service Objectが増えたら名前空間でグループ化する
→ 可読性向上
ServiceをCommandパターンで作るのをいい感じにサポートしてくれるGemが紹介されていた。
- commandパターンの為のレールを敷いてくれる
- 引数のvalidationができる
run
とrun!
をいい感じに使い分けることができる- services/ではなくinteractions/として別のディレクトリを切ることを推奨している
- そもそもserviceという曖昧な名前を使うのがよくないのではと思っていたのでこれはよさそう
記事で主張していることが被ってきたのでまとめてみると
Railsでよく使われているServiceの役割
ARを継承したクラス(≒テーブル)を利用しないロジックをまとめる
- 便利ツール群みたいなイメージ
- Gemが提供している機能を使いやすいようにwrapする、外部サービスとの連携など
複雑なロジックをまとめておきcontroller側で簡潔に呼ぶ
- 複雑なロジックなのでCommandパターンを使ってできるだけ分割して書くと色々メリットがある
- 可読性の向上
- ステートレス(
call
のclass method化)にすることにより、テストをしやすくなる
- 複数リソースを扱うために使うのもここに該当
- starategyパターンもここに該当
オープンソースを眺めてみる。
理想はだいたい分かったので実際どんな感じになっているか確かめるためにオープンなRailsアプリケーションはどうなっているのか眺めてみた。
#{動詞}#{目的語}Service
という命名規則- 基本的にServiceクラスのpublic methodは
call
のみ- 綺麗なCommandパターン
call
の見通しがよくなるように処理をprivateメソッドを切り出している- privateメソッド内で他serviceを呼んでいて綺麗に分離できていてすごい
- 便利ツール群はlib/にあった
#{動詞}#{目的語}Service
という命名規則- 機能によってnamaspaceが切られている
- 基本的にServiceクラスのpublic metodは
execute
のみ - レコードオブジェクトを操作するタイプのServiceはレコードオブジェクトをそのまま返していることが多い
- google-apiのwrapはlib/でしていた
- 便利ツール群はlib/に書いてあるみたい
#{名詞}#{動詞}er/or
という命名規則- 色々なメソッドが生えている
- たまにCommandパターンのserviceがあったりする
- 正直よくわからない
- 便利ツール群はlib/にあった
- discourseって意外とイケてない?
まとめ
- Serviceという言葉は曖昧なのでプロジェクトごとでしっかりとルールを決めよう。
- Commandパターンで実装するのか、全部のせServiceクラスを作って実装するのか
- 多くの記事がCommandパターンを用いた実装を推奨
- でも、ある程度の経験がないとどのくらいの粒度で作ればいいのか悩むかも?
- 複雑なロジックを整理するためにServiceを使うときはCommandパターン使って上手に分割しよう。
- Serviceクラスを作るときは
Manage
などの責務の広そうな単語を使わないよう注意する。 - Commandパターンのような一貫したルールがあるのとないのとでは初見でServiceのコードを見たときのインパクトが全然違う。
- Serviceクラスを作るときは
- 今まで便利ツール群はServiceに置いてたけどlib/に置くのがベターっぽい。
追記
色々書きましたが Service層とうまく付き合うには texta.fm の 3. Low-Code Developmentを聞いてもらうのが一番です。聞いたことない人はぜひ聞いてみてください。Service層に対する解像度がぐっと高まると思います。
呉高専HPをスクレイピングしてワードクラウドを作った
これは呉高専エンジニア勉強会Advent Calendar 2017の15日目の記事です。
こんにちは。呉高専OB1年目の@euglena1215です。
AdventCalendar 4記事目になります。
あと6,7記事書かないと埋まらないだろうと思っていたのですが、色々な人に手伝ってもらって気付いたら全部埋まっていました!
もう感謝感激です。
こんな感じになりました。
呉高専はものづくりと国際交流を頑張ってるみたいです。
で、終わってもいいんですがちょこっと作り方をまとめてみます。
ソースコードはこちらになります。
GitHub - kure-kosen/kurekosen_wordcloud
基本的にはこのページを参考に(ほぼパクリ)しました。 qiita.com
!!!注意!!!
今回はrobots.txtを目視で確認したところ404が返ってきたのでスクレイピングしても問題ないと判断し、プログラム上ではrobots.txtのチェック処理を入れていません。
他のサイトへスクレイピングを行う際はrobots.txtを確認した上で実行してください。
また、リクエストの間隔を1秒以上空けるのがマナーなので気をつけましょう。
処理の流れ
python get_url.py
で https://www.kure-nct.ac.jp/ からアクセスできる呉高専ドメインのhtmlファイルのURLを全て取得し、url.txtに書き出します。
イメージとしては、アクセスしていないURLのスタックとアクセス済みのURLのスタックを用意してアクセスしていないURLのスタックから済みのスタックへと移していきアクセスしていないURLのスタックが空になった時点で済みのスタックを書き出す感じです。python get_wordlist.py
でurl.txtを元にhtmlファイル内の必要なテキストに形態素解析をかけて単語ずつに切り分けURLごとのファイルに書き出します。python wordcloud.py
で2. で書き出された単語を元にワードクラウドを生成します。
2.で書き出されたファイルのファイル名はURLとほぼ同じものにしてあるので、読み込むファイル名を正規表現で絞ってやれば今年の高専日誌の情報のみなど任意の条件からワードクラウドを生成することができます。
最後に面白いと思った結果を紹介します。
例年高専日誌は部活動やコンテストの結果を載せることが多いので「大会」や「チーム」といった単語が多いのですが、今年は「大会」と同じくらいの文字サイズで「イベント」がありました。
これはインキュベーションワークやその他の活動によって色々なところでイベントがたくさん開催されるようになった裏付けなのではないでしょうか。
ワードクラウドを使って1年を振り返るのもなかなかいいですね。
Podcast配信機能をRailsに組み込んでみた
これは呉高専エンジニア勉強会 Advent Calendar 2017の9日目の記事です。
呉高専OB1年目の@euglena1215です。
今年の12月はすごい勢いで記事書いてます。
とあるラジオのサイトでPodcastを配信したくなったのでどう実現するべきなのかを考えた過程を書いていこうと思います。
コードだけ見たい人はこちら
podcastを配信する仕組みについて
AppleのQ&Aから引用させてもらうと
Can I host my podcast on Apple Podcasts?
No, Apple Podcasts does not host podcasts. You must host media files and RSS feeds on your own web servers or use a third-party host. You can then submit your podcast feed for inclusion in the iTunes Store podcast directory.
What is a podcast feed?
A podcast feed is a URL to an RSS (rich site summary) in XML format. RSS is a technology for announcing updates to a website, and XML is a format for creating content on the Internet.
To publish a podcast on Apple Podcasts, use an Apple Podcast Hosting Partner or create a website that generates a feed conforming to RSS 2.0. When you update the site that hosts your podcast, the RSS feed notifies Apple Podcasts that a new episode is available.
iTunes Connect Resources and Help
とのことです。
ざっくり説明すると Appleではホスティングはしてないよ、何らかの方法でRSSを公開してURLを登録してくれればPodcastとして登録してあげるよ。ってことですね。
ふわっと理解するにはこのページが分かりやすいです。
haruthanatos.wixsite.com
ちなみにTech系Podcastとして有名なrebuildはRSSはこんな感じです。
http://feeds.rebuild.fm/rebuildfm
調査してみる
まずPodcastを配信する方法はなにがあるのかを調べたところ以下を発見しました。
Wordpressのプラグイン
wordpressでぽちぽちすればPodcast配信機能を追加できる。
参考:Podcast番組の作り方・配信方法【WordPressで30分でラジオを始める方法】 – ヒーローズジャーニー
Octopod
Octopod is a set of Jekyll templates, helpers and extensions to deliver your podcasts the cool text file lover's way.
https://github.com/pattex/octopod
設定ファイルをYAMLで書くだけでpodcastのRSSと良い感じのpodcastのページが表示してくれるwebサーバを立てられる
rebuild.fm のrssには <generator>Jekyll</generator>
と書かれてあるのでこれを利用しているっぽい?
dropcaster
With Dropcaster, you simply put the mp3 files into the Public folder of your Dropbox. Then run the Dropcaster script that generates the feed, writing it to a file in your Dropbox, e.g. index.rss. All mp3 files in the Public folder of your Dropbox are already accessible via HTTP, and so will the RSS file. You can then take the RSS file's URL and publish it (again, this is because any file in the Public folder of a Dropbox automatically gets a public, HTTP-accessible URL).
https://github.com/nerab/dropcaster
dropboxにmp3ファイルをアップロードして設定ファイルを作るとrssを自動生成してくれるGem 生成されたrssをレンタルサーバーやS3にアップロードすればpodcastをすぐ配信できるようになる
rss(Ruby標準ライブラリ)
生成されるRSSがpodcast配信に必要なnamespaceに対応していたので頑張れば書けそう
選択してみる
今回の要件としては
wordpressとoctopodはアプリケーションとして完結してしまっていて機能を組み込む余地が少ないのでパス、
dropcasterでdropboxを使わずにRSSを生成することも考えましたがコード読むより作った方が早そうだったのでRuby rssを使って実装することにしました。
設計してみる
擬似コードでやりたいことを表現したらこんな感じです。
日本語で説明するのは苦手なのでやりたいことを察してほしい。。。
# routes.rb Rails.application.routes.draw do # /podcast にGETでアクセスすると # PodcastControllerのindexメソッドの結果を返す get "/podcast", to: "podcast#index" end # podcast_controller.rb class PodcastController < ApplicationController def index rss = if RSSがキャッシュに存在する cached_rss else radios = Radio.published # 公開されているラジオ全件取得 Podcast.Feed.new(radios).generate # podcast用RSSを文字列として出力 end render xml: rss # xml形式のデータとしてrssを出力 end end # radio.rb class Radio < ApplicationRecord # レコードが追加/更新/削除された後実行される after_save :update_rss_cache def update_rss_cache # キャッシュを更新 cached_rss = Podcast.Feed.new(radios).generate end end
RSSを文字列として出力できるモジュールを実装してしまえばpodcastを配信できるイメージが湧いたので実装に移っていきます。
実装してみる
rssのドキュメントとrebuildのrssをにらめっこします。
そしてできたのが
feed.rb, config.rb
です。
Configにプロジェクトに依存しているデータを集め、FeedではRSSを生成する処理に専念できるようにしてます。
initialize
の引数に取っているradioのschemaはこんな感じです。
# == Schema Information # # Table name: radios # # id :integer not null, primary key # title :string(255) not null # description :text(65535) not null # mp3(ファイルパス) :string(255) not null # created_at :datetime not null # updated_at :datetime not null # published_at :datetime # duration(再生時間[sec]) :integer not null # size(バイト数) :integer not null #
複雑なことはしてないのでなんとなく処理の流れはつかめると思います。
PRはこんな感じになりました。
https://github.com/kure-kosen/cho_kure_web/pull/26
テストしてみる
いつか絶対にテストを書くという強い意志は持って日々を過ごしています。
まとめ
少しの実装でPodcast配信機能を実装することができました。
テストはきちんと書きましょう。