gasでアイマス20選をスクレイピングしてみた

gasをスクレイピングしてみたら便利だったりするかな、と思いたったのでやってみた。
ネタはニコマス20選。


ニコマス20選とは・・・


えー、半期に一度、以下のレギュレーションで好きなアイマス作品を選ぶイベントがあるですよ。

基本レギュレーション

  • 対象は2011年上半期(1月1 日〜6月30日)に公開されたニコマス作品
  • 自身のセレクトを20作品以内でブログもしくはマイリストにて公開
  • 1Pにつき1作品
  • 選考基準はフリー(お気に入り・埋もれ発掘・テーマに沿って等何でもオッケー)


詳細は、こちら参照


で、まあ、20動画を選んでマイリストかブログのアドレスを以下のエントリーサイトのコメントに登録していくわけですが、それをスクレイピングしてみようというわけです。

エントリーサイト
http://onsentackq.blog31.fc2.com/blog-entry-354.html

htmlをみてみるとブログをスクレイピングするのは厳しいそうなので、まずはマイリストを対象にデータを取得してみようと思います。

で、VBA感覚で書いたのが以下のコード。

function getImasSelect20Data(){

  //2011年上半期のエントリーブログのurl
  var url = "http://onsentackq.blog31.fc2.com/blog-entry-354.html";
  //2010年下半期のエントリーブログのurl
  //var url = "http://cryptos.jp/archives/973";

  //シートを追加
  var today = new Date();
  var yesterday = new Date(today.setDate(today.getDate()-1)+14400000);   //日本時間より4時間前の時刻がとれるので時間を調整
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  ss.insertSheet("2011年上半期ニコマス20選エントリー " + yesterday.getFullYear() + "/" + (yesterday.getMonth()+1) + "/" + yesterday.getDate(), 1);

  //ブログのhtmlを取得
  var response = UrlFetchApp.fetch(url);
  var blogHtmlStr = response.getContentText();

  //mylist分のデータを取得
  var select20MylistData = getMylistData(blogHtmlStr);

  //ブログ分のデータを取得(あとで処理を作れたらいいなぁ)
  //var select20BlogData = getBlogData(blogHtmlStr);

  //mylistとブログのデータを結合
  var select20data = select20MylistData;

  //スプレットシートに出力
    var range= SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(1, 1);
    for ( var i = 0;i<select20data.length ; i++ ){
      range.offset(i+1, 0).setValue(select20data[i].userid);
      range.offset(i+1, 1).setValue(select20data[i].infodate);
      range.offset(i+1, 2).setValue(select20data[i].nicolink);
      range.offset(i+1, 3).setValue(select20data[i].title);
      range.offset(i+1, 4).setValue(select20data[i].nicomemo);
    }
}

/**
 * htmlテキストからmylstのURLを取得して、動画情報のリストを取得する
 * @param htmlstr {String}
 * @param url {String}
 * @return mylistData {Array}
 */
function getMylistData(htmlstr) {

  //mylistのRssのアドレスリストを取得
  var mylistRssUrlList = getMylistRssUrlTakyuPBlog(htmlstr);

  //取得したリスト分処理を実施
  var mylistData = new Array();
  for( var i = 0; i<mylistRssUrlList.length ; i++ ) {

    //mylistのrssのhtmlデータを取得
    try {
      response = UrlFetchApp.fetch(mylistRssUrlList[i]);
    } catch(e) {
      //MyListが取得できない場合(削除されている場合など)は処理を飛ばす
      continue;
    }
    htmlstr = response.getContentText();

    //useridの抜き出し
    var userid = htmlstr.match(/<dc:creator>.+<\/dc:creator>/g);
    userid = userid.join().replace(/<dc:creator>/g,'').replace(/<\/dc:creator>/g,'').split(",");

    //動画のメモを抜き出し
    var nicomemo = htmlstr.replace(/\n/g,'').match(/<p class="nico-memo">.+<\/p><p class="nico-thumbnail">/g);
    if(nicomemo){
      nicomemo = nicomemo.join().replace(/<p class="nico-memo">/g,'').replace(/<\/p><p class="nico-thumbnail">/g,'').split(",");
    }
 
    //動画のlinkを抜き出し
    var nicolink = htmlstr.match(/<link>http:\/\/www.nicovideo.jp\/watch\/sm\d{8}<\/link>/g);
    nicolink = nicolink.join().replace(/<link>http:\/\/www.nicovideo.jp\/watch\//g,'').replace(/<\/link>/g,'').split(",");

    //動画内のタイトルを抜き出し
    var title = htmlstr.match(/<title>.+<\/title>/g);
    title = title.join().replace(/<title>/g,'').replace(/<\/title>/g,'').split(",");

   //動画の投稿日時を抜き出し
    var infodate = htmlstr.match(/<strong class="nico-info-date">.+<\/strong>/g);
    infodate = infodate.join().replace(/<strong class="nico-info-date">/g,'').replace(/<\/strong>/g,'').split(",");

    //取得したデータ配列に追加(タイトルだけよけいなデータがとれてる・・・ださいけどここで吸収)
    for ( var j = 0;j<nicolink.length ; j++ ){
      var nicodata = new Object();
      nicodata.userid = userid;
      nicodata.infodate = infodate[j];
      nicodata.nicolink = nicolink[j];
      nicodata.title = title[j+1];
      nicodata.nicomemo = "";
      if (nicomemo) {
        if (nicomemo[j]){
         nicodata.nicomemo =  nicomemo[j];
        }
      }
      mylistData.push(nicodata);
    }
  }
  return mylistData;
}

/**
 * 卓球PのブログからマイリストのURLを抜き出し、RSSのURLに加工したリストを返す
 * @param htmlstr {String}
 * @return mylistRssUrl {Array}
 */
function getMylistRssUrlTakyuPBlog(htmlstr){

  //mylistのアドレスを抜き出し
  var mylistUrl = htmlstr.match(/<a href="http:\/\/www.nicovideo.jp\/mylist\/\d{8}" target="_blank" title=/g);

  //不要な部分文字を削除し、rssのURLに編集
  var mylistRssUrl  = mylistUrl.join().replace(/<a href="/g,'').replace(/" target="_blank" title=/g,'?rss=2.0').split(",");

  return mylistRssUrl;

}

/**
 * ぎょPのブログからマイリストのURLを抜き出し、RSSのURLに加工したリストを返す
 * @param htmlstr {String}
 * @return mylistRssUrl {Array}
 */
function getMylistRssUrlGyoPBlog(htmlstr){

  //mylistを抜き出し
  var mylistUrl = htmlstr.match(/<a href="http:\/\/www.nicovideo.jp\/mylist\/\d{8}" rel="nofollow">/g);

  //不要な部分文字を削除し、rssのURLに編集
  var mylistRssUrl = MylistUrl.join().replace(/<a href="/g,'').replace(/" rel="nofollow">/g,'?rss=2.0').split(",");

  return mylistRssUrl;

}


ちなみに、上記の処理を実行した結果はこんな感じ(ヘッダだけ手で追加してます)

https://spreadsheets.google.com/spreadsheet/ccc?key=0Ao_xgV3iKKtddE9ES0U2dzkyTkExQVdXVExzUmtLLVE&hl=ja


gasでスクレイピングしてみて

  • 正規表現でマルチバイト文字を上手く扱えないのが残念。アスキーコードで指定すれば動くかもしれないけど、めんどくさいので試してません
  • トリガー使って時間指定で起動できるのが便利
  • 作ったデータをいろんな形式でダウンロードできるのは便利
  • なんかのサービスからデータを利用しようとすると、めんどいかも(gdataAPIを使う必要がある?)
  • Yahoo Pipesみたいに、文字が多くてエラーになるようなことがないので便利。ページの文字数が多いものをスクレイピングするにはおすすめかも?


20選的な視点で

  • 動画のIDでフィルタかけて、特定の動画のコメントみるのはなかなか楽しい
  • ブログでのエントリーなくして、マイリストオンリーだとデータ取得で楽できるかも、って感じた
  • マイリスト限定にしても、サムネ1選のデータが混じるので、別エントリで別マイリストだともっといい(笑)
  • ここから集計しようとすると、標準版と高画質版を集約したり、シリーズものを集約したりする必要があるので置換リストが必要になりそう
  • ブログのエントリーのスクレイピングはいばらの道な感じ。それぞれ処理かかないとダメそう。コメントはあきらめて、動画のIDだけ取得するならできるかもだけど
  • 2010年下半期も試しに取得してみたけど、マイリストを削除している人が多くて全部のデータとれなかった。締め切り直後に取得することと、集計後削除する人もいるかもしれないから、日々の履歴も念のためあった方がいいかなぁ、って思った