僕とコードとブルーハワイ

omega (@equal_001) の日記

PyConJP2017に参加してきた

そういえば今年も参加していたので回想メモ。

今年のプロポーザルは通らず

今回もプロポーザルを2つ出したけど見事落選したので、登壇はしなかった。
今年は分析系が多かったですね。

pycon.jp

pycon.jp

1日目

Keynote聴きながら朝食。
今年の朝食は豪華だった。量が多くて食べきれなかった。

午前中は友人・知人と歓談して終了。

お昼はメディア会議という企画で喋って来ました。

pycon.jp

どういう企画かというと、

メディア会議は今年初めて開催する新しい企画です。 9/8(金)に2つのイベントを行います * ~あなたとメディア、新たな出会い~ * 登壇者とmeet up ~本を書きたいあなたと握手~
企画の狙いは「エンジニアとメディア企業との交流の場を設け、新しいPythonの書籍などを生み出すきっかけをつくる」です。

https://pycon.jp/2017/ja/events/media-meeting/

もうちょっと詳細を説明すると、
最近はじめて本を技術書を書いた人、出版社、編集者が集まって、
「実際執筆ってどうよ?」とか「なにが大変だったの?」みたいなのを話すという感じです。
今年から始まった企画なので手探り感有りましたが、弁当食べながら聞ける気軽さもあってか結構好評だったそうです。
来年もやりたいねーという話がでていたので、今度初めて本を出すよ!という人は手を上げてみると良いかも。

午後はブースを回ったあと、

プレゼンテーション:Pythonistaで始めるiOSプロトタイプ開発 | PyCon JP 2017 in TOKYO を聞いてきた。
objc_utilでネイティブオブジェクトを扱う方法とかObjective-Cでのアプリ開発を知っていないとよくわからない部分があって割とレベル高めだった。

Partyでは、色々な人が近状を見て声かけてくれたり(会社で無能上司達に潰されて心身壊した件)、療養していた間会ってなかった友人たちと久しぶりに話したり、久しぶりにPythonについてちゃんとした質問とか出来たり(会社でpython関係で突っ込んだ質問しても答えてくれる人がいないので)、とにかくとても楽しく過ごせました。

2日目

色々質問した結果深夜まで作業が捗ってしまったので昼過ぎに眠気抜けないまま会場へ。

前職の人たちとお弁当食べながら雑談。
とにかく眠かったのでコーヒーを飲んで作業してた。

プレゼンテーション:Pythonをとりまく並行/非同期の話 | PyCon JP 2017 in TOKYO は聴きたかったのでコーヒーをキメて最前列着。
最近だとasyncioの説明だけで終わる発表が多いけど、この発表はタイトル通り非同期IOについて体系的に説明した内容。
個人的には2017年にざっくりおさらいできてラッキー!という感じだった。
並行/並列処理とはなにか、というところから話してたのも理解しやすさに繋がったかも。

ここで力尽きてLT見ずに寝てた。


イベント終了後、pyconjpに参加してた友人とサイゼリヤで飲み食いした。


今年も楽しかったです。
ありがとうございました!来年も楽しみにしてます。

Python入門者向けハンズオン #6 のメンターをしてきました

Python入門者向けハンズオンのメンターをしてきました。
メンター参加は今回で3回目です。

python-nyumon.connpass.com


◼どういうイベント?

Pythonに興味があるけどまだ触ったことがない方向けにハンズオンを行います。 イベント冒頭にてPythonの概要に関する講義が行われたあと、 実際に手を動かしながらPythonの基礎を一通り体験して頂きます。 さらにその後、ご自身で簡単なアプリケーションを作って頂きます。

https://python-nyumon.connpass.com/event/62147/

プログラミング自体まったく初心者という人から、他の言語を10年以上触っているという人まで来ます。
今のところ、大体5か月に1回のペースで開催しています。


◼イベント内容は?

イベント内容は基本的に毎回同じで、以下の4つのパートに分かれます。

1. Python言語入門

  • Pythonのはじめかた、基本的文法
  • 標準モジュールの使い方
  • 3rd-partyの使い方
  • 仮想環境の構築方法

2. スクレイピング

3. オリジナルプロダクト開発

  • 今日学んだことを活かして好きなものを作ってみる
  • 作ったものの共有会

4. 懇親会

  • ピザやお酒、ノンアルコール、お菓子を手に取りながら歓談

イベントで使用する資料はGithubで公開しています。


◼ハンズオン #6 の感想・多かった質問

感想

今回は今までの中で一番メンターの人数が多かった(13人)ので、かなり手厚いサポートができたと思います。
参加者が36人だったので、1メンターあたり2.7人の参加者が付くという計算になります。
この内、多言語経験者が半数ほど居たのでもくもくと進めてる人が多く、メンターへの質問待ちが発生する状態は見られませんでした。
アンケートでの満足度も高かったようで嬉しい限りです。

また、3回目のメンターということで初回に感じていた緊張はなくなりました。
(資料内容も大きく変化していないというのもある)

今回結構多かった質問

Anacondaをインストールしていたので、venvモジュールと衝突するというものでした。

この対処法には幾つか方法があるのですが、
今回は先に進まないとオリジナルプロダクト開発に進めないということもあり、

  • Anacondaをマシン上から削除するか、
  • 諦めてcondaコマンドで仮想環境を作る

という方法で対応しました。
他のメンターの方とも相談しましたが、
まずはcondaでやってもらい仮想環境とは何かを知ってもらう、という方に切りました。
(venvモジュールについてはイベントで解説&資料に使い方が書いてありますしね)


◼メンターを3回経験して学んだこと

毎回学ぶことはたくさんあります。
今回は、メンターを3回やってみて何を学んだことのなかで、
初学者の方からエラー系の質問がきたときにどうしてるかを脳内整理のためにまとめてみました。

よく「資料にあるコマンドを入力したらこうなった」という質問をされることが多いです。
この質問がくるとき、以下について一緒に確認するようにしています。

  • どういう命令を実行させようとしているのか
  • エラーとなる原因は何なのか
  • 何故実行が成功/失敗するか

まず最初に、
そもそも、今どういうことがしたくてこのコマンドを実行しているか、という部分を説明する。
ここがわかっていないと、後で「これってなんでこの結果返ってくるんですか?」という質問が来ます。

次に、
エラー(または例外)の原因を一緒に探る。
Stack Traceの読み方、表示内容の意味を説明します。
"xxxError:.." の意味を説明して、エラー発生場所を一緒に確認します。

最後に、
失敗の原因について説明します。
成功したコードと失敗したコードの差を見比べます。
ここで、ミスしない方法とか(lintツールとか)、公式ドキュメントを紹介したりすることもあります。


こんな感じでやってます。


◼おわりに

毎回メンター側も学ばせてもらっています。
今後も予定が合えば参加していきたいと思います。

最近、Python Boot Campというイベントを各地でやっているみたいなので、Python入門者向けハンズオンがないときはそちらへ参加してみるのもアリです。
Python Boot Camp(初心者向けPythonチュートリアル) — PyCon JP

「いちばんやさしいPythonの教本」が出版されました

先月、前職時代に同僚と共著で執筆した「いちばんやさしいPythonの教本」が出版されました。
実は8月に出版されてました。あれから1か月程経ったのか。早い。



いちばんやさしいPythonの教本 人気講師が教える基礎からサーバサイド開発まで (「いちばんやさしい教本」シリーズ)

以下、本書の簡単な紹介

今年はいろんなPythonの入門本が出てますが、「いちやさPython*1」は、本当に初めてプログラミングをする人向けの本となっています。

プログラミング初心者向け本

Python・エディタのインストールから、ターミナルとは、コマンドとはなんぞや〜というところも解説しています。
前半はPythonの基本文法、後半はbot開発やbottle*2を用いた簡単なWebアプリケーション開発について解説しています。

「解説パート」と「実践パート」からなる講義形式

各章、「解説パート」と「実践パート」の2構成で成り立っており、講義のような形式をとっています。
まず最初に解説パートで用語や概念の解説をし、次の実践パートで「解説パート」で出た新しい要素を用いながら実際に動作するプログラムを作っていく、という流れになっています。

フルカラーで解説図が豊富

本書内のデザインはフルカラーなので、重要ポイントがパッと見でわかりやすくなっています。解説図が多く挿入されているのも特徴です。


初心者向けだけど、Pythonでできることを幅広く解説している一冊になったなぁと思います。
この一冊を手に取った人が、プログラミングを楽しみ、ものづくりの可能性が広がった!とちょっとでも思って貰えたら嬉しいなー。

*1:本書タイトルの「いちばんやさしいPythonの教本」の略称。

*2:ここに脚注を書きます Bottle: Python Web Framework — Bottle 0.13-dev documentation

今年もage++


先々週のことですが、今年もまた一つ加齢しました。

毎年プレゼントを頂いて恐縮の一方なので、今回は1箇所にだけひっそりwishlistを公開しただけなのですが、ありがたいことに今年も何人もの方からお祝いの品をいただきました。

1. データ分析プロジェクトの手引: データの前処理から予測モデルの運用までを俯瞰する20章
みことさん、プレゼント贈ってくださりありがとうございました!
ちょうど今必要とする範囲の知識が書かれた本ということで、ガンガン読み進めています。

2. 筋トレが最強のソリューションである マッチョ社長が教える究極の悩み解決法
カレーメシ先輩、筋肉本ありがとうございました!
読了しました。
とりあえず筋トレはじめました。背筋が良くなりつつあります。

3. マイクロサービスアーキテクチャ
4. 限定酒 ねのひ 桜酵母のお酒 「なないろ桜」 純米 500ml
みっひゃ氏、2つも贈ってくれてありがとうございました!
今年の目標である設計力向上、この本を読んでやっていくぞい。お酒は花見しながら呑むぞい。

5. 井村屋 チョコえいようかん55gx5本
6. 108ピース ジグソーパズル 地獄シリーズ
jp_taku氏、プレゼントありがとうございました!
えいようかん、ウマイんだよな。(今回はもしもの時のために保存しておきます)
昨年に引き続き無地のパズルを贈られましたが、108ピースでもまた挫折しそうです。今度白いの頑張ってみます(白目)

7. THE IDOLM@STER CINDERELLA MASTER 029小早川紗枝
8. THE IDOLM@STER CINDERELLA MASTER 039塩見周子
うらたく氏、ドンピシャ推しメンのCDありがとうございました 🙏
二人の限定SSR再販しないですかね〜(切実)

あとは実家から大量のご当地レトルトカレー、私の大好きな爽健美茶、クオカード、ひつじのケーキとクッキーが届きました!
今年も多くの方々からお祝いの言葉と贈り物をいただけて幸せいっぱいです。
来年度は新しくやることが多くありかなりバタバタしそうな一年ですが、
やっていきの気持ちで推して参ります。

年末年始に読んだ本

年末年始に積読 or 途中までしか読んでなかった本を読み進めた。

  • Pythonからはじめる数学入門
    • 数式をPythonコードに落とすときにどう書くかという練習になったのでまぁまぁ良かった。
  • Unixという考え方
    • Unixの哲学的なものを知るにはちょうどいいんじゃないか程度に思っていたが、日々のシステム設計をするときの良い指標の一つにもなった。これが正解とは言わないが、役立つ考え方がコンパクトに詰まっている。
  • インフラエンジニアの教科書
    • 今いる会社は受託メインだし今後もオンプレで本番サービス動かすことないと思うが、クラウドと自前の両方のコスト感覚を知っておくと後々の仕事で役立つ機会は多いので読んだ。インフラエンジニアって何してるんだろうという人が最初に読むと良いかな。
  • Effective Python, PythonCookBook
    • 2016年の復習として。初級から中級に上がるときに読めばちょうどいい。CookBookは「問題と解決方法」という流れで書かれているが、解決方法がユニークだったり自分では思いつかなかった発想が多く載っていて飽きが来なかった。


だいぶ消化できた。技術書以外だと4冊読んだ。連日ゆっくり本を読める時間があるのはとても嬉しいし、贅沢だ。
2017年は特にこれといってすぐに大きな役立ち方はしなさそうだが長く使える知識を増やしていきたいところだ。
そういえば年末年始は本とコーディングとアニメと故郷の友人たちとの会合に時間潰していたら年が明けていた。
そして美味しいものを食べて体重が増えた。運動も頑張ろう。

Rust で 言語処理100本ノック 第1章 前半

2016/12/16 追記:この記事のコメントで私の煩雑コードにアドバイスしてくださった方々、本当にありとうございました!多くの学びを得られましたことをここで御礼申し上げます(稚拙ながらコメントの返信をしました)。



これは Rust Advent Calendar 2016 の 14日目の記事です。

初心者枠なので、濃密でためになる情報はありません。ごめんなさい。


今年の秋頃から趣味でのろのろとRustについて調べ始め、最近勉強がてらあの有名な言語処理100本ノック をやってみています。

言語処理100本ノックはPythonを想定して問題作成されている事もあってか、Rustで書こうとした時に色々苦労したので、
今回はそのことについてつらつら書いていきます。


長くなるので、今回は第一章の前半だけ。


00. 文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ

    let text = "stressed";
    println!("{}",
        text.chars()  // ["s", "t", .. "d"]
        .rev()  // iterableをreverseする
        .collect::<String>()  // iterable to String
    );
    // https://doc.rust-lang.org/std/iter/trait.Iterator.html#examples-17

    // rsplitを使った方法も出来た
    let text_2: Vec<&str> = "stressed".rsplit("").collect();
    println!("{}", text_2.join(""));

ふわっとした感じで言うと、Pythonで言うところの.split("")がchars() にあたるらしいです(細かいことをいうとリスト型ではなく、charのイテレータ)。
まずchars()でiterableにして、rev()でreverse、Stringに変換みたいな感じでやっています。

してるのは、これを入れないとコンパイラが型アノテーションできずにエラー吐くからです。

このあたり最初わからずにだいぶ苦労しました。

ちなみに rsplit() というsplitした上で逆順にリストに格納するメソッドがあったのでそちらでも書いてみました。どっちの書き方が早いのかな...。

01.「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ.

    let text: Vec<_> =  "パタトクカシーー".split("").collect();
    // .split("")すると0番目と-1番目は""(空文字)
    // mapでもいけそう
    let mut result = "".to_string();
    for (idx, x) in text.iter().enumerate() {
        if idx % 2 == 0 {
            result.push_str(x);
        }
    }
    println!("{}", result);
    // 文字列を先にlistにできたら text.split(|x| x%2 == 0); みたいにできそう
    // https://doc.rust-lang.org/beta/std/vec/struct.Vec.html#examples-50

to_string() で&str->Stringに変換したあと偶数配列だけcharを取得してます。pushは破壊的なのでmutな変数にpush_str()する、という流れです。
そういえば同じようなことが出来るメソッドで to_owned() というのがあるんですが、いまいち違いがわかってません。


02. 「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ.

    // 01と同じ方法で
    let text_1: Vec<_> = "パトカー".split("").collect();
    let text_2: Vec<_> = "タクシー".split("").collect();

    let mut result = "".to_string();
    // https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.zip
    let iter = text_1.iter().zip(text_2.iter());
    for x in iter {
        result.push_str(x.0);
        result.push_str(x.1);
        // println!("{}{}", x.0, x.1);  // tupleのaccessはdot
    }
    println!("{}", result);

01と方法はほぼ同じです。
違う点はzip()を使って2つループさせてるところくらいです。Pythonにも同様のものがあります。一旦変数に入れないと行けないところがPythonと違ってちょっと面倒ですが。
zipで回した中身がtupleになっていて、tupleの要素へのアクセスは x.0, x.1という書き方でやるようです。

03. "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.

    let text: Vec<_> = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.".split(" ").collect();
    let mut result = "".to_string();
    for x in text {
        result.push_str(x.len().to_string().as_str());
    }
    println!("{}", result);

01, 02と方法変わってない。
そういえばlistの中身を確認するときってドキュメントだとassert使って証明の流れが多いですね。
log的な感じで標準出力してくれたりしないのかな。

04. 元素記号

use std::collections::HashMap;

     :

    let text: Vec<_> = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.".split(" ").collect();
    let mut result = HashMap::<String, usize>::new();

    // 2文字を結合したものを一時的に入れる
    let mut two_chars = "".to_string();
    for (idx, x) in text.iter().enumerate() {
        let word: Vec<_> = x.split("").collect();
        // matchは使えないので愚直にif
        if idx == 0 || idx == 4 || idx == 5 || idx == 6 || idx == 7 || idx == 8 || idx == 14 || idx == 15 || idx == 18 {
            result.insert(word[1].to_string(), idx);  // idx => usize で usizeをstrにcastする方法を見つけられない
        } else {
            two_chars.push_str(word[1]);
            two_chars.push_str(word[2]);
            //result.insert(tmp.to_string().as_str(), idx.to_string().as_str());  // error: expected a literal
            result.insert(two_chars.to_string(), idx);
        }
        // Char_atはunstable https://doc.rust-lang.org/1.1.0/syntax/str/fn.char_at.html
    }

最初、1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語だけ抜き出すというのをmatchでやったのですが、
matchの結果にinsert()は使えないようで、expected type `std::option::Option<&str>というエラーが出てしまったので愚直にfor-ifで判定してます。PythonのIN演算子を探したのですが参照周りの制約で難しいのでしょうか、見当たりませんでした。

ちなみに match ではこういう風に使いたかった。"|" で区切ると or条件になり、それ以外の場合を"_"に書く、という感じの構文で表現できるようです。

match {
    0 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 18 => result.insert( ... ,
    _  => result.insert(... ,
}

この問題のゴールは、条件に沿ってtextをスライスした結果を連想配列(辞書型もしくはマップ型)にすることなので、Rustにも連想配列がないか調べたところありました
mutな変数にinsert()で要素を追加していくという感じです。

> HashMap::::new();
usizeをstrにどうやってcastするのかイマイチ不明だったので、HashMap::::new()で逃げてます。
まぁ本来は後に文字列出力することも考えて<&str, &str>が良いのでしょうね。

> let mut two_chars = "".to_string();
文字列から先頭2つを抜きだすのは良いんですが、それを結合させる良い方法が思いつかず、一時的にtwo_chars(こういうのはやらないほうが良いんですが...)に突っ込んでます。
"A" + "B" まで簡素に書きたいとは言わないので、今より楽な方法はないかなーと模索してます。



こんな感じで、ドキュメントを読み漁りつつなんとなく動くものができたという感じです。慣れるまでなかなかつらいですね。
Pythonやっぱり楽だなぁというのと、型にこんなに気を使うと型のない言語が不安になりますね。

もっと良い書き方を覚えたら追記していきます。

おわり。

Pythonista+スクレイピング で Advent Calendar 管理

Python Advent Calendar 2016 その2 の7日目の記事です。
qiita.com

★ はじめに

Advent Calendarのサービスの有名所と言えば「Qiita Advent Calendar」と「Adventar」がありますね。
私は両サービスとも利用しているのですが、どちらのサービスに、いつ、どんなカレンダーに参加しているのかたまに忘れてしまうことがあるので、それらがひと目で分かるように、参加登録しているカレンダー情報を一箇所にまとめるiOSアプリをPythonistaで作成しました。

今日はどんな風に作ったのか?というのをつらつら書いていきます。
特に目新しい技術・トリッキーなアルゴリズム等は使ってないのであしからず。

「各サービスのカレンダーをこまめにチェックするなりGoogleカレンダーに登録しておけば良いのでは?」みたいな話はひとまず置いておいて下さい。
技術で無駄に遊びたいときってあるじゃないですか。アレですアレ。
(実はGoogleカレンダーにもいちいち登録するのも面倒くさかったというのが主な理由なんて言えない。)

* その前に、Pythonistaって何?

簡単に説明すると、PythoniOSアプリを開発することができるiOSアプリケーションです。
今回はPythonistaの詳細についてはここでは述べませんが、 私が今年のPyconJPで話したPythonistaについての発表資料があるのでよければ御覧ください。

* 開発環境

今回作ったアプリのコードは https://github.com/Omega014/AdventCalendarCollection に置いてあります。よければ参考までに。

* やりたいことのゴール

↓ 完成形の姿です。

f:id:equal_001:20161207033718j:plain

少ない...。
でもこんな感じでいっぺんに管理したい。


★ 実装方針

タイトルからお察しの通り、スクレイピングで自分が登録しているカレンダー情報を取得します。
流れとしては、

1. 「Qiita Advent Calendar」と「Adventar」のカレンダーのURLを取得
2. 取得したURLからカレンダーページを開き、Beautiful Soupを用いて自分が登録しているカレンダーのタイトルと日にちを抜き出す
3. 抜き出したカレンダーのタイトルと日にちをjsonに保存する(雑にDB代わりにjsonファイルを使う)
4. PythonistaのUIパーツを駆使して好みのカレンダーを作る
5. jsonからデータを読み出し、カレンダーの各日にちと紐づくUIパーツに、カレンダーのタイトルが表示されるようにする

みたいな感じです。
特に目新しい技術を使っているわけではないです。


ということで、各工程について書いていきます。

1. 「Qiita Advent Calendar」と「Adventar」のカレンダーのURLを取得

Adventar はユーザ毎の登録したカレンダー一覧のページがPublicになっているので、そのURLを使います。
(ちなみに私のユーザページのURLは http://www.adventar.org/users/7109
「?year=****」 を付けなくても今年のページに飛びますが、簡単に昔の年のデータも取得できるようにYEARで管理してます。

YEAR = "2016"
ADVENTAR_URL = "http://www.adventar.org/users/7109?year=[]".format(YEAR)

Qiita Advent Calendar もユーザ毎の参加中のカレンダーを見ることができるページがあるのですが、ログインしないと閲覧できないためQiita Advent Calendar 2016の全てのカレンダーのページを対象とし、その中から自分が登録しているものだけを抽出する方法を取ります。

全てのカレンダーはいずれかのカテゴリーに属しているらしく、各カテゴリーページを見るとカレンダーのリンク一覧が見れるので、これを利用してカレンダーページのURLを特定します。

QIITA_CATEGORIES = [
    "to_be_decided",
    "programming_languages",
    "libraries",
    "databases",
    "web_technologies",
    "mobile",
    "devops",
    "iot",
    "os",
    "editors",
    "academic",
    "services",
    "company",
    "miscellaneous"
]

def _scrape_qiita_advent_calendar():
    """ Qiita Advent Calendarから
        参加登録中のカレンダーのタイトルと年月日を取得して返す関数
    """
    # 公開されている全てのカレンダーのURLをリストに集約
    urls = []
    for category in QIITA_CATEGORIES:
        html = urllib.request.urlopen(
            "http://qiita.com/advent-calendar/{}/categories/{}".format(YEAR, category)
        ).read().decode('utf-8')
        soup = BeautifulSoup(html)

        # クラス名変更で動かなくなる可能性がある
        target_xml = soup.select("[class~=adventCalendarList_calendarTitle]")
        for target in target_xml:
            title = target.find_all("a")[-1].string
            url = "http://qiita.com{}".format(target.find_all("a")[-1]["href"])
            urls.append(url)

ちなみに何故全件を対象にしたのかというと、
Qiita Advent Calendar APIなるものがなく、かといってrequestsのpostを使ってログインを試みたのですが対策されてるのかログイン出来ず、Adventarのようにまとまったページに容易にアクセスできなかったので、
スクレイピング処理走らせるのせいぜい3回くらいやろ」ということで全件取得になりました。。

2. Beautiful Soupを用いて自分が登録しているカレンダーのタイトルと日にちを抜き出す

ちなみにbs4はPythonistaに組み込まれているので pip installしなくてもそのまま使えます。BeautifulSoup(html, "lxml") と書きたいなら pip install lxmlすればokです。

アドベントカレンダーでDOMツリーが異なるので、それぞれに合ったスクレイピング処理を実装しています。
デザインの変更などで動かなくなる可能性はありますが、それは仕方ないのでその時はちまちま直しましょうという空気感でやってます。

def _scrape_adventar():
    """ ADVENTARから
        参加登録中のカレンダーのタイトルと年月日を取得して返す関数
    """
    html = urllib.request.urlopen(ADVENTAR_URL).read().decode('utf-8')
    soup = BeautifulSoup(html)
    # クラス名変更で動かなくなる可能性がある
    registrations = []
    for data in soup.find_all("span"):
        date = data.parent.find("span").string[:10]  # '2016-12-05(月)'から曜日を削る
        title = data.parent.find("a").string
        registrations.append({"title": title, "date": date})
        
    return registrations


# TwitterとGithubとでどっちも登録しちゃった時用の為にリストにしておく  
QIITA_TARGET_USERS = ["OMEGA014"]

def _scrape_qiita_advent_calendar():
    """ Qiita Advent Calendarから
        参加登録中のカレンダーのタイトルと年月日を取得して返す関数
    """

             ~ (省略) ~ 

    registrations = []
    # 自分が登録してるカレンダーだけを探して日付とタイトルを辞書に格納
    for url in urls:
        html = urllib.request.urlopen(url).read()
        soup = BeautifulSoup(html)

        title = soup.title.string

        # 特定のユーザ名がauthorの日付を探す
        # クラス名変更で動かなくなる可能性がある
        target_xml = soup.select("[class~=adventCalendarCalendar_day]")
        for idx, target in enumerate(target_xml):
            # 参加登録されていない空き日は飛ばす
            if not target.img:  # imgのaltでユーザ名を取得...
                continue
            user_name = target.img["alt"]
            if user_name in QIITA_TARGET_USERS:
                date = "{}-12-{}".format(YEAR, idx+1)
                registrations.append({"title": title, "date": date})
        time.sleep(60)  # 1minだと短すぎるかな...
        
    return registrations

URL毎にurlopenしてはアクセスしてるので、アクセス先のサーバに負担がかからないようにtime.sleepしてます。
507カレンダーもあったので1分待つのも長く感じるのですが、データの取得を何度もやらないという前提でtime.sleepするくらいで置いてます(12月入ってからカレンダーの追加登録とかそうそうしないやろという)。
ちゃんとやるならaiohttpとかasyncioあたりを使って効率良くデータを取得したほうが良いですね。

3. 抜き出したカレンダーのタイトルと日にちをjsonに保存する

Pythonistaでデータを何処に持たせるかという問題がありまして、外部サーバにDBを作って通信することも可能ですが、そこまで大それたものを作るわけではないので、アプリのディレクトリにjsonファイルでデータを持たせておくというのをよくやります。
読み込むときも楽だし、簡単な実装で扱えるのでjsonでデータ管理はおすすめです。

    # 毎回読み込みすると重くて辛いのでjsonに最新のデータを持たせておく
    filepath = os.path.join(os.path.realpath('./'), 'registrations.json')
    os.remove(filepath)
    with open(filepath, 'a') as f:
        json.dump(registrations, f, indent=4)

ここまでは単なるスクレイピングのお話。

4. PythonistaのUIパーツを駆使して好みのカレンダーを作る

ここからPythonistaの話になります。

PythonistaにはUIパーツを指先一つでスイスイ組み立てれるUI Designerという機能があるので、これを使ってUIパーツを配置し、好みのカレンダーのデザインを作っていきます。(もちろんPythonコードでもUIを生成することも可能です)。

↓骨組み完成図。UI Designer上でみるとこんな感じ。
f:id:equal_001:20161207043844j:plain

使ったUIパーツは以下:

  • Label: 曜日・日付を表示させるだけ
  • TextView: カレンダーのタイトルを表示させる
  • Button: カレンダー情報の更新のアクション(self.refresh)を登録する

この時、LabelとTextViewの命名規則として label1, textview1のように日付と対応するように名前をつけてます。
こうすることで、たとえば12/7のTextViewを呼び出すときに以下のように記述することができるようになって便利です。

day = '7'
self.view['textview'+day]

ちなみに手でポチポチとUIパーツを配置しても良いですが、かなり疲れるのでUIパーツを一気に選択してコピペで書き換えれば楽に生成出来ます。
さらにちなむと、pyuiの実態はただのjsonで、UIパーツの座標や種類などの設定がずらーっと書いてあるだけなので、中身をコピペ&置換する方法でも短時間で生成出来ます。

5. 日付情報と紐づくUIパーツにカレンダーのタイトルが表示されるように設定する

やっていることは単純で、3で保存したjsonからカレンダーのタイトルと日付を読み込み、その日付に該当するTextViewにタイトルを入れる(self.view['textview'+day].text = title) というだけです。
これを登録しているカレンダー分実行しているという感じです。

self.refreshはButtonパーツと紐付けている(UI DesignerでButtonをタップするとActionという項目があるのでそこにself.refreshと入力するだけ)ので、refreshボタンを押したらスクレイピング処理->あたらしいjsonを再度読み込んで反映を自動でやってくれます。

import json
import os
from datetime import datetime

import ui  # Pythonistaのuiモジュール

import scraper  # 1~3で作ったスクレイピング処理をするモジュール


class AdventCalendarCollection (object):
    def __init__(self):
        self.view = ui.load_view('AdventCalendarCollection')
        self.view.background_color = "#ffebd5"
        self.view.present('fullscreen')
        self.view.name = 'AdventCalendarCollection'
        self.reload_data(None)
        self.change_today_bg_color()
        
    def refresh(self, sender):
        scraper.main()
        self.reload_data(None)

    def reload_data(self, sender):
        filepath = os.path.join(os.path.realpath('./'), 'registrations.json')
        with open(filepath, 'r') as f:
            registrations = json.load(f)
        # jsonに保存する時点でdayだけ保存しても良いかもなぁ...
        for data in registrations:
            day = data['date'].split('-')[-1]
            if day[0] == '0':
                day = day[1]
            title = data['title']
            self.view['textview'+day].text = title
    
    def change_today_bg_color(self):
        # 今日の日だけカレンダーの背景に色をつける
        today_datetime = datetime.now()
        if today_datetime.month == 12:
            today = str(today_datetime.day)
            self.view['textview'+today].background_color = '#ffd9d9'


AdventCalendarCollection()

ちなみに地味に self.change_today_bg_color() で今日の日付のTextViewの背景だけ色をつけてます。そんな処理も関数にちょいと書くだけで実現できるPythonistaとても便利!


以上で出来上がりです。簡単ですね!
これで幾つか登録したあとに心配になって何度も担当日をチェックしに行ったりせずにすむ...。

★ まとめ

  • 自分が登録したAdvent Calendarの情報をスクレイピング処理で取得して一つのカレンダーにまとめて見れるiOSアプリをPythonistaで作ってみた
  • Pythonistaを使うとちょっとのPythonコードを書くだけで自分が欲しいアプリを作れて楽しいよ
  • みんなもPythonista触ってみよう! <- これが言いたかった