歩いたら休め

If the implementation is easy to explain, it may be a good idea.

【Python】はてなキーワードAPIを使って特徴語を抽出する

最近、スクレイピングで記事を集めることにハマっているのですが、その記事の中に含まれるトピックなり特徴語なりを簡単にチェックする方法はないかと悩んでました。

例えば、音楽ナタリーから好きなバンドの記事を集めてくる際には、関連リンクのタグを取ってきてアーティスト名を取ってきて、「これは興味のある/ない記事だ」という判別を行っていましたが、この方法はナタリーのサイトでしか使えないし、少しhtmlのタグの名前が変わっちゃうだけで使えなくなります。

頭のいい人ならここから「自然言語処理だ!」「機械学習だ!」「トピックモデルだ!」となると思うのですが、彼らは人生の9割と辞書と学習データの整備に費やしている印象があるので、週末きちんと遊びたい私は別の方法がないか探していました。

そこで、

  1. APIを使って特徴語を抽出する
  2. 抽出した特徴語や、そのカテゴリから簡単なルールベースで判別する

という方法でうまくいくんじゃないかなと試しています。

そこで、@sn_fさんがはてなキーワードAPIを紹介している記事があったので、同じ方法を試してみました。APIについて詳しいことは、公式のリファレンスを見てください。

snsn.hatenablog.com

元の記事ではJavaを利用しているのですが、私はスクレイピングのプログラムでPython3.5を利用しているため、そちらで書きなおしてみました。

# python2系ではxmlrpclibという名前です
import xmlrpc.client

# テキストはこちらの記事から: http://natalie.mu/music/news/184202
text = """
スピッツが主催する夏季恒例ライブ「ロックのほそ道」と「新木場サンセット」の開催が決定した。
「ロックのほそ道」は東北を舞台に、「新木場サンセット」は東京を中心に行われているライブイベント。今年の「ロックのほそ道」は7月14日に宮城・仙台サンプラザホール、16日に秋田・秋田県民会館にて開催され、宮城公演にはTHE COLLECTORSとハナレグミが、秋田公演にはMONGOL800がゲスト出演する。
一方「新木場サンセット」は7月19、20日に東京・新木場STUDIO COASTにて実施される。初日にはYeYe、THE COLLECTORS、清水ミチコ、People In The Box、2日目には赤い公園、UQiYO、syrup16g、米津玄師が参加し、スピッツは両イベントの全公演に出演する。
さらにスピッツは6月30日と7月1日になんばHatch、7月9日と10日に大阪城ホールという大阪の2会場で、ライブイベント「ロックロックこんにちは!」を開催。こちらにも個性豊かなゲストアーティストがラインナップされている。
なおスピッツは4月27日にニューシングル「みなと」をリリース。本作の表題曲は本日4月20日にiTunes Storeなどで先行配信がスタートしている。
"""
server = xmlrpc.client.ServerProxy("http://d.hatena.ne.jp/xmlrpc")
res = server.hatena.setKeywordLink({"body": text, 'mode': 'lite'})
print(res)

出力結果です。

{'wordlist': [{'cname': 'music',
   'refcount': 0,
   'score': 100,
   'word': '新木場STUDIO COAST'},
  {'cname': 'music', 'refcount': 0, 'score': 75, 'word': 'People In The Box'},
  {'cname': 'music', 'refcount': 0, 'score': 100, 'word': 'The Collectors'},
  {'cname': '', 'refcount': 0, 'score': 63, 'word': '仙台サンプラザ'},
  {'cname': '', 'refcount': 8, 'score': 14, 'word': 'ライブイベント'},
  {'cname': 'music', 'refcount': 0, 'score': 100, 'word': '秋田県民会館'},
  {'cname': 'movie', 'refcount': 68, 'score': 20, 'word': 'アーティスト'},
  {'cname': 'art', 'refcount': 68, 'score': 20, 'word': 'アーティスト'},
  {'cname': 'geography', 'refcount': 1, 'score': 48, 'word': '大阪城ホール'},
  {'cname': 'elec', 'refcount': 0, 'score': 59, 'word': 'iTunes Store'},
  {'cname': 'music', 'refcount': 68, 'score': 20, 'word': 'アーティスト'},
  {'cname': '', 'refcount': 0, 'score': 84, 'word': 'なんばHATCH'},
  {'cname': '', 'refcount': 182, 'score': 9, 'word': 'こんにちは'},
  {'cname': 'music', 'refcount': 0, 'score': 62, 'word': 'ハナレグミ'},
  {'cname': 'art', 'refcount': 1, 'score': 50, 'word': '清水ミチコ'},
  {'cname': 'music', 'refcount': 6, 'score': 10, 'word': 'サンセット'},
  {'cname': 'music', 'refcount': 0, 'score': 70, 'word': 'MONGOL800'},
  {'cname': 'music', 'refcount': 2, 'score': 44, 'word': '赤い公園'},
  {'cname': '', 'refcount': 86, 'score': 12, 'word': 'シングル'},
  {'cname': 'science', 'refcount': 5, 'score': 55, 'word': 'スピッツ'},
  {'cname': '', 'refcount': 0, 'score': 11, 'word': 'スタート'},
  {'cname': '', 'refcount': 455, 'score': 11, 'word': 'イベント'},
  {'cname': 'sports', 'refcount': 86, 'score': 12, 'word': 'シングル'},
  {'cname': 'music', 'refcount': 0, 'score': 33, 'word': '米津玄師'},
  {'cname': '', 'refcount': 76, 'score': 13, 'word': 'リリース'},
  {'cname': 'music', 'refcount': 5, 'score': 55, 'word': 'スピッツ'},
  {'cname': 'music', 'refcount': 1, 'score': 86, 'word': 'Syrup16g'},
  {'cname': 'music', 'refcount': 86, 'score': 12, 'word': 'シングル'},
  {'cname': '', 'refcount': 21, 'score': 8, 'word': '4月27日'},
  {'cname': '', 'refcount': 2, 'score': 17, 'word': '7月14日'},
  {'cname': '', 'refcount': 8, 'score': 13, 'word': '6月30日'},
  {'cname': '', 'refcount': 52, 'score': 15, 'word': '4月20日'},
  {'cname': '', 'refcount': 116, 'score': 9, 'word': 'ホール'},
  {'cname': '', 'refcount': 75, 'score': 18, 'word': 'ロック'},
  {'cname': '', 'refcount': 201, 'score': 9, 'word': 'ライン'},
  {'cname': '', 'refcount': 4, 'score': 12, 'word': '7月9日'},
  {'cname': 'movie', 'refcount': 136, 'score': 11, 'word': 'ゲスト'},
  {'cname': '', 'refcount': 244, 'score': 17, 'word': 'ライブ'},
  {'cname': 'sports', 'refcount': 82, 'score': 18, 'word': 'ロック'},
  {'cname': 'music', 'refcount': 6, 'score': 25, 'word': '表題曲'},
  {'cname': '', 'refcount': 3, 'score': 22, 'word': '7月1日'},
  {'cname': 'book', 'refcount': 75, 'score': 18, 'word': 'ロック'},
  {'cname': 'geography', 'refcount': 1, 'score': 35, 'word': '新木場'},
  {'cname': '', 'refcount': 136, 'score': 11, 'word': 'ゲスト'},
  {'cname': '', 'refcount': 775, 'score': 8, 'word': '本日'},
  {'cname': '', 'refcount': 59, 'score': 12, 'word': '個性'},
  {'cname': '', 'refcount': 601, 'score': 22, 'word': '東京'},
  {'cname': 'art', 'refcount': 230, 'score': 15, 'word': '舞台'},
  {'cname': '', 'refcount': 5, 'score': 8, 'word': '夏季'},
  {'cname': 'geography', 'refcount': 23, 'score': 26, 'word': '秋田'},
  {'cname': '', 'refcount': 159, 'score': 33, 'word': '配信'},
  {'cname': '', 'refcount': 196, 'score': 12, 'word': '実施'},
  {'cname': 'idol', 'refcount': 696, 'score': 5, 'word': 'さら'},
  {'cname': '', 'refcount': 103, 'score': 14, 'word': '初日'},
  {'cname': 'sports', 'refcount': 601, 'score': 22, 'word': '東京'},
  {'cname': 'geography', 'refcount': 27, 'score': 27, 'word': '宮城'},
  {'cname': 'music', 'refcount': 0, 'score': 33, 'word': 'YeYe'},
  {'cname': '', 'refcount': 23, 'score': 26, 'word': '秋田'},
  {'cname': 'geography', 'refcount': 601, 'score': 22, 'word': '東京'},
  {'cname': '', 'refcount': 95, 'score': 15, 'word': '主催'},
  {'cname': 'geography', 'refcount': 222, 'score': 22, 'word': '大阪'},
  {'cname': 'geography', 'refcount': 109, 'score': 20, 'word': '東北'},
  {'cname': '', 'refcount': 98, 'score': 11, 'word': '7月'},
  {'cname': '', 'refcount': 1523, 'score': 4, 'word': '10'},
  {'cname': 'science', 'refcount': 886, 'score': 6, 'word': '20'}]}

はてなキーワードの圧倒的な辞書力で、「People In The Box」などのアーティスト名がいい感じに抽出できてますね!

あとはcname(カテゴリ名)やスコアを使って、簡単なルールベースで判別してあげればなんとかなりそうです。また、アーティスト名を小文字にしたりして試してみたのですが、どうやら多少の表記ゆれも直してくれるようです。