はじめに
筆者は「ポリタス」「ゼゼヒヒ」などのウェブ・メディアの開発に関わってきたエンジニアです。技術とメディア運営をつなぐことに関心があり、そのためのツールとして「データ・ジャーナリズム」の研究と実践にチャレンジしています。
今回は「東京都議会議員選挙」を題材にしました。投票を行う際に、対象の候補者についてざっくりとした情報を得られるようにしたいというのがゴールです。そのために、東京都議会議員選挙の候補者情報をJSONデータ化し、そのデータを使ったウェブサイトを構築しています。
この記事は、データ収集のプロセスにフォーカスをあてたものです。別途、このデータを利用したサイト構築(React + Redux + Material-UI + GitHub Pages)についての記事を執筆予定です(2017.7.4: 後編として、政治ハック:2017年東京都議会議員選挙の候補者情報サイトをReact + Material-UIで構築を公開しました)。
なおこの記事で解説したデータ収集ツールとそのデータを使ったウェブサイトのソースコードをgithubで公開しています。
ソースコード:
https://github.com/mshk/togisen2017
ウェブサイト:
https://mshk.github.io/togisen2017/
データの用途をクリアにする
データの収集は、最終的に得たいと思う情報を定義しないと作業に無駄が生じてしまいます。
今回は、都議会議員選挙の候補者の「SNSプロフィール(自己紹介の文章)を獲得する」ことをゴールに設定しました。別記事で扱うウェブサイトを通じて、投票の際に参考となるような短くて凝縮された候補者紹介のテキストを一覧して観られるようにしたいと思います。
データ記事制作のプロセス
筆者はデータを使った記事制作過程を
- データの収集
- 選別
- 加工
- 確認
- 公開
の5つに分けて考えるようにしています。まずは元となるデータを集め、そのデータの中から本当に必要なモノを選び、必要な形式に加工、最後に確認を行って公開するという工程です。データの収集や加工は、対象の数が増えると作業量が飛躍的に増えるため、できるだけ荒い内容で作業を進めながら対象を早期に選別して数を減らしていくことがキモとなります。
今回は、
- 東京都選挙管理委員会が公開している候補者の情報を収集(スクレイピング)
- 候補者情報のホームページURLの項目を使って、候補者ごとのホームページを巡回、ページに掲載されたFacebook、TwitterなどのSNSアカウントへのリンクを収集
- さらにそれぞれのSNSアカウントからプロフィールの情報を収集(Facebook Graph API/Twitter API)
という3つの段階に分けてデータを「収集」します。
「選別」については、求める対象が候補者のSNSプロフィールということではっきりしているため、特に行いません。データは獲得した段階で不要なテキストの除去や、統一のフォーマットへの正規化を行い、JSON形式で出力しています(確認、公開のフェーズについては別記事で扱う予定です)。
技術スタック
今回使用したツールは下記の通りです。
- Node.js
- Jake
- cheerio-httpcli
- facebook-node-sdk
- node-twitter
Node.jsでスクレイピング
スクレイピングのためのツールが充実しているか、開発を担当するエンジニアの習熟度合いはどうかなどを基準に決めることになる訳ですが、今回はNode.jsを選択しました。一般論で言えばNode.jsはスクレイピングのためのプラットフォームとしては比較的順位の低い選択肢になるかと思いますが、主に筆者が慣れていることから選択しました。
多くの方がご存知のようにNode.jsには「プログラムが非同期で実行される」という特色があり、多くのサイトを対象としたスクレイピング時に同時にリクエストを行うことができるという強みがある反面、サイトAから取得したデータを元にサイトBにアクセスするというような逐次処理を記述する際には注意が必要です。
コマンドラインからタスクを実行するためJakeを使用し、実際のコードはJakefileとして記述しています。
Cheerioを使ったウェブサイトのスクレイピング
スクレイピングのツールとしては、cheerio-httpcliを使用しました。
Googleで検索した中で、解説ドキュメントがいくつか見つかったこと、サンプルコードを使って実際にHTMLの取得と解析など望む機能が提供されていたことから選択しました。
cheerio-httpcli自体はドキュメントも分かりやすく、サンプル通りにコードを書くことで望む結果が得られると思いますが、今回のように不特定の複数のサイトを巡回するケースではHTMLのリクエストと解析のプロセスをPromiseでラップして実行順序を保証するための工夫が必要になります。
Promiseによる逐次処理を動的に作成する
前述したように今回のスクレイピングでは
- 選挙管理委員会のページを取得して候補者のリストを取得
- それぞれの候補者の情報からホームページのURLを取得
- 上記URLのコンテンツを取得してSNSへのリンクを抽出
- さらにそれぞれのSNSリンクを巡回
という作業を行っています。抽出されるリンクは不特定なため、事前にPromiseとthen()を繋いでいくコードをかくだけでは対応できず、動的にPromiseの配列を生成し、Promise.allでまとめて実行するようにしました。
実際のコードは、Jakefileの45行目あたりを参照してください。
Twitterプロフィールの取得
Twitterプロフィールは、node-twitterを使用して取得しました。
Twitterアカウントの抽出については前段の解析時に
- metaフィールドの’twitter-site’
- body中のリンクで’twitter.com’を含むもの
を取得しています。body中のリンクは単純にツイートを参照しているケースも拾ってしまうため誤検出の可能性がありますが、metaフィールドに設定がない場合も多いので併用しつつ最後は人力でのチェックを行っています。
Twitterは’users/lookup’というAPIを提供していて、100アカウント単位で取得したアカウントを100個ずつに分割してプロフィール情報を取得します。
Facebookプロフィールの取得
Twitterに較べて面倒なのがFacebookプロフィールの取得処理です。
まずFacebookのプロフィールページは個人アカウントとページアカウントの2種類があり、それぞれにデータのフィールドが異なりますが、これをURLから判別することができません。
Graph APIは、URLをidとしてクエリに’metadata=1’をつけてリクエストを送るとページのメタ情報を返すので、こちらを元に「Facebookページ」か「個人アカウント」かの判別を行います。
今度はこの情報を元にGraph APIへのリクエストを行い、プロフィールが含まれると思われるフィールドをリクエストします。
Facebookは自己紹介のためのフィールドが’description’や’bio’など細かく設定が可能で、対象アカウントによってどのフィールドを使うかが異なるため、実際にデータを取得しつつ最も効率よくデータを獲得できるように調整をする必要があります。
Graph APIも、1リクエストで複数のクエリを行うバッチAPIを提供しています。Twitter同様取得対象のリストを分割してリバッチ・リクエストを作成します。ちなみに、Graph APIのバッチ・リクエストは1リクエストにつき50までのクエリを行うことができます。
データ・ジャーナリズムについて
今回のプロジェクトは「ジャーナリズム」とまでは言えない規模のものですが、基本的なプロセスはデータ・ジャーナリズムの手法にならっています。筆者も日本語訳に参加した「The Data Journalism Handbook」には、欧米のジャーナリストがエンジニアとタッグを組むことで新しい報道のあり方を模索する様子が書かれています。興味をもった方はぜひチェックしてみてください。
筆者のサイトで、データ・ジャーナリズムについて幾つかの記事を書いています。こちらも良かったらどうぞ。
あとがき
選挙はデータを使った記事制作と相性が良いのですが、候補者の情報が公開されてから選挙当日を迎えるまでの期間が短く、開発にスピードが要求されます。
今回のプロジェクトでは、まず候補者情報をJSONデータ化するというシンプルなゴールを設定しました。
今後別の選挙があった際に、このプロジェクトのコードをベースにさらに情報を追加したチャンレンジができたり、あるいは他にも同じようなチャレンジをしたい方の作業を少しでも減らすことができれば幸いです。
社会問題や政治に関わる活動を行うことは自分の知る限り日本のエンジニアの間であまりポピュラーではない印象がありますが、大量のデータのハンドリングや数字による分析など面白い題材も多く、また性質上たいていのデータの利用が無料で、ちょっとしたコーディング・チャレンジの対象としては格好のものです。また自分の技術がレバレッジされることで多くの人の役に立つ可能性もあります。
最後に、今年のWWDCのシークレット・ゲストとして登場したミシェル・オバマ前大統領夫人のスピーチを貼っておきます。