フューチャー技術ブログ

Go Conferenceの📛を作る

フューチャーアドベントカレンダーの3日目のエントリーです。昨日はrkyymmt@githubさんによる私のコンピュータ遍歴でした。

Go Conference 2019 Autumnでは、弊社フューチャーがTofu On Fireスポンサーを努めさせていただきました。Go Conference Fukuokaが参加者に名札を配っており(トップ写真左側)、それが羨ましく、東京のイベントでも配布したい!と思ったので、名札提供に手をあげさせていただきました。どうせ配るなら、外国の人が喜びそうなGenuine Tofu on Fireの実物をお土産にしてもらえばいいかな、ということで、このようになりました。また、個人の名前と、Connpassのアバター画像(≒Twitterのアイコン)が入っているオフ会にはぴったりな名札なので、ぜひとも他のイベントでも身につけていただければと思っています。

この名札の中身ですが、Goを使って作っていますので、その技術解説その他です。なお、コードはこちらになります。

https://github.com/shibukawa/tofuonfire

tofuonfireコマンドの使い方

上司にお伺いをたてる

このプログラムを実行する前に、まずはスポンサーしますというのを宣言します。フューチャーもイベント協賛はいろいろやっていますし、特にうちのユニットはGo案件の割合も多いので、OKはもらいやすくて助かります。今回は参加者、スピーカー、スポンサーあわせて240人とかで、なおかつ1つ120円ということが分かったので、コスト的にはイベントスポンサーと考えると費用対効果は高いです。

OKをもらったら、イベントのスポンサーページにフューチャーの会社のロゴを載せてもらいます。Tofu On Fireと最初からネタバラシしていましたが、それに気づいたツイートは1件だけでした。

名札を買う

今回は学研モールの、学研の保育用品というサイトから購入しました。1つの商品は10セットまで、という制約があったので、一般参加者の分は2回に分けて購入しました。色は、よくスピーカーに配っているGopherぬいぐるみの色に合わせて、水色(一般参加者)、紫(スタッフ)、ピンク(スポンサー)などをチョイスしました。スピーカーは絵文字📛と同じ、一番格式の高い赤にしました。スカラーシップは緑。遠方枠の名札を買い忘れて他の色のあまりから補填したのは失敗。

中に入れる紙のサイズは購入するまではわからなかったのですが、スクリーンショットの名札で35mm四方でした。

コマンドの実行

このイベントのために突貫で作り、あまり再利用性とか考えていないので、もし利用したい場合はフォークして改変して利用ということになると思いますが、プログラムの概要の紹介を兼ねて使い方を紹介します。

このプログラムはconnpassのイベント情報からユーザー情報を取得して、PDFファイルを作成するところまで行います。Go ConferenceのconnpassのURLは https://gocon.connpass.com/event/148602/ です。この数値をコマンドライン引数に設定します。

$ go get github.com/shibukawa/tofuonfire
$ ./tofuonfire 148602

これでoutputフォルダにPDFがばらばらっと生成されます。これを印刷したらOKです。簡単ですね。

実際には弊社のロゴのPNG画像をローカルに置かないといけない(&その画像はApacheライセンスではないので同梱していない)ので、これだけだとエラーになるのですが、そのままは使わないと思いますので、動作確認が取れているコード片として利用していただければ、と思います。

あとフォントは源真ゴシックを使っていますが、これもライセンスが違うので同梱はしていません。NotoフォントだとOpenTypeですが、Go系のツールはたいていTrueTypeしか対応していない(PureGoの準標準ライブラリがある)ので、NotoフォントのTrueType実装ともいうべきこのフォントを利用させていただきました。これもダウンロードして実行フォルダに置きます。

PDFの印刷と名札の制作

あとはプリンターでA4でカラーで印刷して、カッターとカッティングボードを用意して手作業で入れていきます。中の厚紙(園児の名前、血液型とかを書く欄があるやつ)を引き出して、上に重ねて一緒に差し込むと比較的かんたんにきれいに入れることができました。

当日はお客さんに探してもらいやすいように、おおまかに先頭のアルファベットごとにグルーピングして会場に持っていきました。

一週間ぐらい前から作り始めましたが、キャンセルで繰り上がり(有料イベントは待ち行列はなくて空いた瞬間申し込みなので正確には繰り上がりではないのですが)がちょこちょこあって、それの対応が少し面倒でした。今回は金曜日の夕方で名簿を最終確定ということになっていたので、そのタイミングで繰り上がった人の分のPDFを印刷して月曜日のイベントに備えました。あとはスポンサーやスピーカーの人の分の情報収集とかですね。このあたりはスタッフの渉外担当のbudougumiさんやらymotongpooさんにこまめに情報を教えてもらってなんとかしました。Tofu on Fireスポンサーはスタッフ兼務じゃないとなかなか厳しいタスクだな、という感想を持ちました。

実装の解説

スクレイピング

Connpassにはイベントそのもののリストを取得するAPIがあり、イベントカレンダーを作るユースケースとしては十分なのですが、個別のイベントの詳細情報の取得のAPIは用意されていません。そのため、イベント情報のHTMLをパースする必要があります。

まず、Connpassのサイトに迷惑をかけないように、一度だけHTMLをダウンロードし、その後はローカルのキャッシュから読むようにします。HTMLファイルをキャッシュするのではなくて、読み込んで取り出したユーザー名と画像ファイルのリストのJSONを出力するようにしました。このJSONが存在したら読み込みをそこから行う感じです。

スクレイピングには定番の“github.com/PuerkitoBio/goquery”を使いました。CSSセレクタっぽい感じで情報の取得ができます。Connpassのイベント参加者ページ(https://connpass.com/event/イベント番号/participation/)を見ると、参加者の種別ごとにparticipants_tableクラスのついた<table>タグがあるので、それをとってきて調べれば簡単そうです。

テーブルごとに登録されているユーザーのリストを取得するには次のように書けば良いです。テーブルごとのループと、テーブルの中の行ごとのループですね。


var pages [][]Card
doc.Find("table.participants_table").Each(func(i int, s var *goquery.Selection) {
var cards []Card
title := spaces.ReplaceAllString(s.Find("thead tr th").Text(), " ")
log.Println(title)
page := Page{
Category: title,
}
s.Find(".display_name > a").Each(func(index int, s *goquery.Selection) {
href, _ := s.Attr("href")
fragments := strings.Split(href, "/")
name := fragments[len(fragments)-2]
if name == "open" {
name = fragments[len(fragments)-3]
}
log.Println(s.Text() + " (" + href + ")")
card := Card{
Name: s.Text(),
ImagePath: imagepath,
}
cards = append(cards, card)
})
pages = append(pages, cards)
})

なお、画像は参加者一覧のページでは解像度が低かったので、個別のユーザーページに飛んでプロフィール画像を取得するようにしています。最終的な画像はS3なので特にアクセス絞らなくてはいいと思いますがプロフィールページは"golang.org/x/time/rate"パッケージを利用して1秒に1ページのみ取得するようにしました。もちろん、この画像も、一度ダウンロード済みの場合はページアクセスをしないようにして、試行錯誤しやすくしています。

// 1秒に1回
limiter := rate.NewLimiter(rate.Every(time.Second), 1)

// ページ取得に合間にこれを挟む
limiter.Wait(context.Background())

はい、これでうまくとれました。

というわけにはいかなかった

最初これでうまくいったと思ったのですが、実は1つのカテゴリに100人以上いると、ページングされるということがあとからわかりました。名札を作ってみたら思ったよりも余る。不思議!と思ったら一般参加者130人のうち、後ろ30人が出力されてませんでした。

これも、goqueryを駆使してなんとか取得しています。moreのリンクがあったらそれをたどり、下のページングのリンクの2ページ目から最終ページの手前までのページにアクセスして追加で取得するようにしています(1ページ目は取得済みなので飛ばし)。ページングのリストが最大10ページまで表示だとすると、1000人までは対応できます。それ以上になるともうちょっとコードが必要かと。

PDFにする

PDFは"github.com/signintech/gopdf"パッケージを使いました。
このパッケージはREADMEから参照されているサンプル集が充実していたのは良かったです。

gopdfの座標の単位はポイント数です。A4の紙は約595ptx841pt。一枚の名札は35mm四方なので99pt。余白を入れてA4一枚で5x8で40枚詰め込んだPDFを作成します。

こちらのコードを見ていただくのが早いと思いますが、ページを作って、個々人の名札のオフセットを計算し、それをもとに、枠線とか、画像とか、文字とか細々と位置とか大きさを調整して配置して保存するとPDFができあがります。なるべくアイコンが大きくなるようにしています。文字列は事前にサイズ計算しておいて、センタリングするとかもしています。手間暇かかるけど一番楽しい部分ですね。

Connpass以外のユーザー情報も取り込む

今回は一般参加者と当日スタッフ、レギュラースタッフはConnpassから情報が取れましたが、スピーカーとスポンサーは別の管理になっていました。これらの情報は手作業でJSONにまとめておいて、追加で読み込むようにしました。次回やるならここを自動化したい。

反省

スポンサーとスピーカーの情報収集が手動というのはすでに説明しましたが、他にも反省が一つあります。

今回は名札を各自受け取ってから受付という流れでしたが、受付のときに懇親会参加者は別途受付を行い、お酒が飲めるかどうかを確認してシールを貼る、という手順になっていました。参加者名簿と懇親会名簿を集約し、なおかつ事前アンケートでお酒が飲めるかどうかの情報を収集していたら、受付作業をもっとシンプルに簡略化しつつ、名札に🍶アイコンも印刷できたのになぁ、というのは次回やりたいですね。

まとめ

GoでのPDF作成は思いの外かんたんでした。印刷後のサイズもぴったりでした。ちょっとした領収証とかを生成するタスクが来てもgopdfでばっちりそうですね。

あと、今回は狙ったとおりに、参加者の人が喜んでくれると同時に、フューチャーの社名入りの写真や投稿をガンガンTwitterに投稿してくれて、会社のPR効果としてはとても高かったと思います。GoConに参加していない人からも「うらやましい」という声も聞くことができました。エンジニアと園児をかけたネタだ、というツッコミはありましたが、それは意図してませんでした。なるほど。

どうしても採用目的のチラシは置いても売り残りがちですし、本当はGoやりたい!とフューチャーに応募してくれる人が増えるとうれしいものの、イベントのPR作戦としては名前だけを売る、という方向にしました。

今回のGoConからは平日開催の2000円の有料イベントになって、仕事とかのどうしても仕方がない理由をのぞいたキャンセルもほとんどなくなり、90秒で枠が埋まることもなくなり、運営としては狙ったとおりの効果が得られました。その一方で、DeNAの玉田さんが当日朝の準備のときに懸念されていましたが、有料イベントだと景品表示法が関係してきます。今回は2000円なので全員配布のノベルティは総付景品にあたり、400円を超えるとNGになります。全員分はなかったので問題はなかったと思いますが、今回はモバイルバッテリーを持ち込んだスポンサーさんもいて、そろそろ配布物にかかっている1人あたりお金をスポンサー各社に確認・・・みたいなのが必要なのかなぁ、という気がしています。

名札はイベント運営に直結した消耗品なので、おそらくこの規制からは外れるのではないか、と考えています。あとは、前回のGoConでDeNAさんが配布していた、DeNA社員による発表内容紹介のチラシなんかも良いですね。この路線で、全スピーカーの発表内容&スケジュールが入ったパンフレットのスポンサーというのはありな気がします。あとはWantedlyさんの同人誌はうらやましいな、と思っています。

大小の勉強会は今後もさらに増加傾向になると思いますし、コンプライアンスは維持しつつ、各社がイベントの協賛として個性を出していく、みたいな方向性でアイディアを競っていきたいですね。

明日はTakaakiOtomoさんによるPub/Subと業務利用についての予定「Pub/Subと業務利用についての予定」です。