LoginSignup
46
43

More than 5 years have passed since last update.

スクレイピングする Hubot スクリプトをつくろう

Last updated at Posted at 2014-12-09

これは Hubot Advent Calendar 2014 の 9 日目の記事です。

昨日は @udzura さんの 『carrierモジュールで雑にHubotからコマンドを打つ』でした。stream を中心としたプログラミングは Node.js らしい良い形なのですが、ぼく個人としてはあんまり使えてないですね。あと @bouzuya Advent Calendar 的な煽りが見えた気もしますが、気のせいでしょう。

また、今回は @bouzuya の Hubot 連載の第 7 回です。目次は、第 1 回の記事にあるので、そちらをどうぞ。

前回まで、そして今回は

前回は 簡単な Hubot スクリプトをもっとつくろう (おみくじ系) ということで、Response#random を使って、ランダムにひとつを選んで返す Hubot スクリプトをつくりました。

今回は外部の npm パッケージを使いながらスクレイピングする Hubot スクリプトをつくりましょう。npm パッケージを自由に使えるようになれば、できることがさらに広がります。

スクリプトの完成イメージ

まず、今回つくろうとしている Hubot スクリプトのイメージを共有しましょう。

今回は「スクレイピング」ということで、Hubot Advent Calendar 2014 でも利用している「 Adventar 」に HTTP リクエストをして、その結果を解析しましょう。

実行イメージはこんな感じです。

bouzuya> hubot adventar hubot
hubot> Hubot http://www.adventar.org/calendars/384

さっそくつくる

まずは Hubot スクリプトのテンプレートを generator-hubot で生成します。

$ mkdir hubot-adventar
$ cd hubot-adventar

$ yo hubot:script
...

もう慣れたものだと思います。

次に Node.js のスクレイピングでの定番パッケージである requestcheerio を使いましょう。以下のコマンドで npm リポジトリからそれらのパッケージをダウンロードし、package.json に依存関係を追加できます。

$ npm install --save request cheerio
request@2.49.0 node_modules/request
├── caseless@0.8.0
├── json-stringify-safe@5.0.0
├── forever-agent@0.5.2
├── aws-sign2@0.5.0
├── stringstream@0.0.4
├── tunnel-agent@0.4.0
├── oauth-sign@0.5.0
├── node-uuid@1.4.2
├── qs@2.3.3
├── mime-types@1.0.2
├── combined-stream@0.0.7 (delayed-stream@0.0.5)
├── form-data@0.1.4 (mime@1.2.11, async@0.9.0)
├── tough-cookie@0.12.1 (punycode@1.3.2)
├── http-signature@0.10.0 (assert-plus@0.1.2, asn1@0.1.11, ctype@0.5.2)
├── bl@0.9.3 (readable-stream@1.0.33)
└── hawk@1.1.1 (cryptiles@0.2.2, sntp@0.2.4, boom@0.4.2, hoek@0.9.1)

cheerio@0.18.0 node_modules/cheerio
├── entities@1.1.1
├── lodash@2.4.1
├── dom-serializer@0.0.1 (domelementtype@1.1.3)
├── htmlparser2@3.8.2 (domelementtype@1.1.3, domutils@1.5.0, entities@1.0.0, domhandler@2.3.0, readable-stream@1.1.13)
└── CSSselect@0.4.1 (domutils@1.4.3, CSSwhat@0.4.7)

簡単ですね。npm はこのように簡単にパッケージを追加できます。dependencies もきちんとソートしてくれます。

さて、書いていきましょう。では、こちらに完成したものがございます。

src/adventar.coffee
# Description
#   A Hubot script for listing advent calendars in Adventar
#
# Configuration:
#   None
#
# Commands:
#   hubot adventar <query> - lists advent calendars in Adventar
#
# Author:
#   bouzuya <m@bouzuya.net>

cheerio = require 'cheerio'
request = require 'request'

module.exports = (robot) ->

  robot.respond /adventar(?: (\S+))?/, (msg) ->
    query = msg.match[1]

    # send HTTP request
    baseUrl = 'http://www.adventar.org'
    request baseUrl + '/', (_, res) ->

      # parse response body
      $ = cheerio.load res.body
      calendars = []
      $('.mod-calendarList .mod-calendarList-title a').each ->
        a = $ @
        url = baseUrl + a.attr('href')
        name = a.text()
        calendars.push { url, name }

      # filter calendars
      filtered = calendars.filter (c) ->
        if query? then c.name.match(new RegExp(query, 'i')) else true

      # format calendars
      message = filtered
        .map (c) ->
          "#{c.name} #{c.url}"
        .join '\n'

      msg.send message

解説

ええと、解説します。

require

cheerio = require 'cheerio'
request = require 'request'

cheerio / request という変数にそれぞれ npm パッケージを読み込んでいます。使いかたの詳細はそれぞれのリポジトリを見てください。

(?: (\S+))?

robot.respond /adventar(?: (\S+))?/, (msg) ->

Hubot スクリプトに限った話じゃないけど、個人的にこの (?: (\S+))?(?:\s+(\S+))? のような正規表現をよく使います。(?:) はマッチしても取り出しの際のグループには追加されません。

request

# send HTTP request
baseUrl = 'http://www.adventar.org'
request baseUrl + '/', (_, res) ->

素朴に HTTP リクエスト + コールバックの設定。Node.js での基本形です。今回は手抜きのためにコールバックの第一引数の err を無視しています。

cheerio

# parse response body
$ = cheerio.load res.body
calendars = []
$('.mod-calendarList .mod-calendarList-title a').each ->
  a = $ @
  url = baseUrl + a.attr('href')
  name = a.text()
  calendars.push { url, name }

cheerio を使うと jQuery のようなセレクタなどが使える。ぼくは基本的には上記のように情報を抜き出したらつめかえて、それ以降は cheerio を使わない主義です。cheerio でメソッドチェインするとわかりづらいので。

あとは特にないかな。普通の JavaScript な動きだし。いつもなら面倒なので全部チェインにしているけど、コメントいれたほうがわかりやすいかと思って、区切ってみました。

動かしてみる

じゃあ、動かしてみましょう。

$ HUBOT_SHELL_USER_NAME='bouzuya' PATH="./node_modules/hubot/node_modules/.bin:$PATH" $(npm bin)/hubot -a shell -n hubot -r src
hubot> hubot adventar hubot
hubot> Hubot http://www.adventar.org/calendars/384
hubot> hubot adventar
...
いろふ http://www.adventar.org/calendars/331
蒙古タンメン中本 http://www.adventar.org/calendars/330
ラーメン http://www.adventar.org/calendars/329
プリキュア http://www.adventar.org/calendars/328
しょぼちむ http://www.adventar.org/calendars/327
hubot> 

よいのでは

まとめ

スクレイピングをする Hubot スクリプトをつくってみました。そんなに細かく説明していませんが、たぶん大丈夫でしょう。もうぼくの知っていることはほぼ書いたと言って良いですね。

ちなみに今回のサンプルは bouzuya/hubot-adventar にあります。うまく動かない、などがあれば参考にどうぞ。

最後に

こういう場面で紹介するのにちょうど良い API って何なのでしょう。できればトークンなどが不要で、それなりに知名度があって、可能なら画像を返してくれると Slack などで見ると非常に良いのですが。

次回どうしましょうか。Hubot で HTTP を待ち受ける話でもしましょうかね。

では。

46
43
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
43