あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

node.jsでスクレイピングしてみた

node.jsの記事を最近チラホラ見かけるので、入門してみました。

node.jsはサーバー書いてこそって感じなんだろうと思いますが、ネットワークプログラミングの経験がないので、まずは他の言語で簡単にできることをnode.jsで書いてみようという趣旨です。

node.jsのインストールとか

node.jsのパッケージマネージャである、npmを使いたかったので、GitHubのnpmのページの方法でインストールしました。

MacPortsにnode.jsはあったんですけど、npmはないし、Homebrewにはnode.jsもnpmもあったんですけど、インストールしたらnpmがうまく動かなかったので、なんだか面倒な方法でインストールすることになった気がしてます。

作ったツール

コマンドラインで以下のように打ち込むと、任意のディレクトリにYahoo!画像検索APIで取得できる画像を保存するサンプルを書きました。*1

node imggetter.js ./MyOppai おっぱい

第一引数でディレクトリ名を指定し、第二引数でキーワードを指定します。

依存関係など

以下がインストールされている必要があります。

また、以下のモジュールも必要です。

モジュールはいずれもnpmから簡単にインストールできます。

ApricotはRubyのHpricotに似たスクレイピングモジュールで、asyncjsは非同期処理を簡単に書くためのモジュールです。

サンプルコード(imggetter.js)

var sys = require('sys'),
    spawn = require('child_process').spawn,
    fs = require('fs'),
    path = require('path'),
    url = require('url'),
    querystring = require('querystring'),
    Apricot = require('apricot').Apricot,
    async = require('asyncjs'),

    map = function(arr, func) {
      return Array.prototype.map.call(arr, func)
    }

if (process.argv.length < 4) return console.log('引数が足りん')

var argv_path = /\/$/.test(process.argv[2]) ? process.argv[2] : process.argv[2] + '/',
    keyword = process.argv[3],

    target = 'http://search.yahooapis.jp/ImageSearchService/V1/imageSearch',
    params = {
      appid: 'YOUR_APP_ID',
      query: keyword,
      results: 50,
      start: 1,
      adult_ok: 1
    }

path.exists(argv_path, function(exists) {
  if (!exists) fs.mkdirSync(argv_path, 0777)
})

async.range(1, 951, params.results)
  .each(function(start, next) {
    params.start = start
    Apricot.open([target, querystring.stringify(params)].join('?'), function(xml) {
      if (!xml.toDOM.getElementsByTagName('ClickUrl')[0]) return
      async.list(map(xml.toDOM.getElementsByTagName('ClickUrl'), function(el) {
          return el.firstChild.nodeValue
      }))
        .each(function(img_url, next) {
          var extention = '.' + url.parse(img_url).pathname.match(/\.(.*)?$/)[1],
              timestamp = (new Date()).toISOString().replace(/:/g, '_').replace('Z', '+09_00'),
              curl = spawn('curl', ['-o', argv_path + timestamp + extention, img_url])
          curl.stderr.on('data', function(data) {
            sys.print(data)
          })
          curl.stdin.end()
          next()
        })
        .delay(1000)
        .end()
      next()
    }, true)
  })
  .delay(50000)
  .end()

delayの時間は、プロセスを立ち上げ過ぎて、終了しない程度に適当な数値を入れてあります。

curlを使っているのが反則な気もしますが、標準モジュールで上手くできなかったので、諦めました。

使ってみた感想など

npmというパッケージマネージャがあるし、JavaScriptで操作できなかったIO周りが操作できるようになったことで、node.jsという名のJavaScriptに酷似した言語を手にいれたような感覚を覚えました。

パッケージも充実してくると、作りたいものをJavaScriptで書くことがより簡単になりそうです。

サーバーも書けるということで、これから勉強したいなと思っているところですが、Rhino版node.jsとかが出て、GoogleAppEngine上で利用できるようになったりするとうれしいなーと思います。
それっぽいのはあるらしいです。GoogleAppEngineで使えるRhino版のサーバーサイドJSがあると教えてもらいました。一見node.jsっぽい感じ。

情報も少ないし、まだまだ自分には敷居が高そうです。。。

その他

ちゃんとしたプログラムが未だに書けないので、頑張りましょうワタクシ。

*1:[http://d.hatena.ne.jp/jdg/20080504/1209924643:title=昔も同じようなもの作ってました]けど、あまり成長してないですね