Wikipediaが大好きなしうへいと申します。
今回はリアルタイムWikipediaスクレイピングには欠かせない、list=recentchangesについて書きます。
#list=recentchangesって?
MediaWiki APIによって提供される、各記事を「最後に編集された時間の降順」で取得したリストです。
(要するに、編集されたばかりの記事が一覧で取得できます。)
MediaWiki APIヘルプ: list=recentchanges (rc)
実際に、XML形式で/通常ページを/100件取得した様子:
https://ja.wikipedia.org/w/api.php?action=query&list=recentchanges&format=xml&rclimit=100&rcnamespace=0
XML形式で取得した場合は、rcタグ要素とその属性に、編集情報が収められて返されます。
#メモその1 - 記事名titleはrc要素において必須属性ではない
rc[title]には通常、編集された記事名が収められています。
「そもそも記事に対して編集をするのだから、記事名がないはずはない。」
そう思ってコーディングしていると…ぬるぽが発生します。
titleがないrc要素とは、ずばりrc[type=log]の一部です。
https://ja.wikipedia.org/w/api.php?action=query&list=recentchanges&format=xml&rclimit=500&rcnamespace=0&rctype=log
rctype=logで取得したこのリストには主に、何らかの意図でWikipediaから削除された記事名がリストアップされています。
が、その中にtitleが含まれていない要素があることがわかります。
(このリストは時間とともに変化するので、タイミングによっては表示されないかもしれません)
なぜ記事名がないのかは不明ですが、他の情報が読み取れないので、処理上は単なるノイズでしかありません…。
#メモその2 - rctypeを意識して使い分ける
rctypeオプションには以下が用意されています。
- rctype=edit 編集。
- rctype=new 新規ページ作成。
- rctype=log 削除ログ。その他。
- rctype=external 他言語版Wikipediaの編集に関する情報のようですが、いまいち不明。
- rctype=categorize 絞り込みをかけても一切の要素が引っかからないため、死に要素だと思われます。
デフォルトではrctype=edit|new|logが適用されます。
特にrctype=logの情報を活用していない場合は、オプションからlogを外してしまうのが注意点1の手っ取り早い対策になります。
#メモその3 - rctype=newのold_revidは常に0
list=recentchangesで取得した要素には、記事名の他に、古い版のid(=old_revid)と、新しい版のid(=revid)がデフォルトで併記されています。
これは非常に便利で、このidを使って新しいAPIリクエストを発行することで、版同士を比較することができます。
idを使って単純にパースしたい時は、以下のようになります。
https://ja.wikipedia.org/w/api.php?action=parse&prop=wikitext|links&oldid=58160382&format=xml
比較専用のaction=compareも用意されています。
比較的最近(ダジャレか!)実装された命令で、今のままだとちょっと扱いづらいです。改良が望まれます。
https://ja.wikipedia.org/w/api.php?action=compare&fromrev=58082298&torev=58160382&format=xml
当然ですが、新規ページに関しては古い版というのが存在せず、old_revid=0となり、前述のリクエストURLにこれを渡すと、エラーになってしまいます。
https://ja.wikipedia.org/w/api.php?action=parse&prop=wikitext|links&oldid=0&format=xml
revidを使って版を比較するコードを書く時は、この点を意識して書くか、もうrctype=editしか検索しないとか、そういう対応が必要になります。
#おまけ - rc要素を重複なく処理するには
APIを利用する際、基本的には、ある間隔でlist=recentchangesにリクエストを投げることになるのですが、それだけだと処理済みの要素とそうでない要素が混在してしまいます。
これを回避する方法は大きく二つあります。
- rccontinueパラメータを使って、必ず前回の最後から続きを行う
- rc要素に振られているrcidを使う
1は、例外処理に強い人におすすめの方法です。
(「必ず」の部分が曲者。自分はちょっとつらいです。
2は、そのままidをマッピングするなどして重複を排除する方法ですが、
たとえばRxJavaにおけるObservableとしてrc要素を流して、distinct((rc)->rc.get("rcid"))
のようにして重複を排除するやり方もあります。
メモリの消費量さえ気にしなければこういった方法が手っ取り早いと思います。