Firefox拡張入門第4回(某占いサイトをスクレイピング)

Firefox拡張はスクレイピングの最終兵器です。
今日は、某占いサイトをスクレイピングして、自分の星座のお告げを抜き出します。
http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard15.png

某占いサイト
スイーツ(笑)テーブルレイアウトです。
http://eva-lu-ator.net/~gemma/geocities/matchfox/c0.jpg

先にFirebugで、抜き出したいところのXPathを調べておきます。
http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard12.png

個人的にCSSセレクタのほうが好きなので、XPathCSSセレクタの対応表をあげておきます。

次に、拙作のMatchfox拡張で骨組みを作ります。名前はFortuneにします。
http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard13.png

XPathを使ったスクレイピング

新しいタブに占いサイトを開いて、onloadイベントでスクレイピングが走ります。
chrome/content/sidebar/00-foobar0.jsに書いてください。

function getMainWindow() {
  return getTopWin();
}

function $x(aDocument, aXPathString) {
  var nodes = aDocument.evaluate(aXPathString,
                aDocument, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  var arr = [];
  for (var i = 0, len = nodes.snapshotLength; i < len; i++) {
    arr.push(nodes.snapshotItem(i));
  }
  return arr;
}

function scrapeFortune(aDocument) {
  //contains(@alt, '座'). XPath 1.0 には end-with()がないのでcontainsでお茶を濁す。
  var constes = $x(aDocument, "//img[contains(@alt, '\u5EA7')]").map(function(x) {return x.alt;});
  var texts   = $x(aDocument, "//td[contains(@class,'text')]")  .map(function(x) {return x.innerHTML.replace(/<br>/g, "");});
  var luckys  = $x(aDocument, "//td[contains(@class,'lucky')]") .map(function(x) {return x.innerHTML;});
  var data = {};
  data[constes[0]]  = {rank: 1,  text: texts[0],  advice: luckys[0],  lucky: luckys[1]};
  data[constes[11]] = {rank: 12, text: texts[11], advice: luckys[12], lucky: luckys[13]};
  for (var i = 1; i < 11; i++) {
    data[constes[i]] = {rank: i + 1, text: texts[i], lucky: luckys[i + 1]};
  }
  return data;
}

function sidebarFoobar0() {
  const url = "http://www.fujitv.co.jp/meza/uranai/index.html";
  var mw = getMainWindow();
  var newTab = mw.gBrowser.getBrowserForTab(mw.gBrowser.addTab(url));
  newTab.addEventListener("load", function(e) {
    Application.console.log(scrapeFortune(newTab.contentDocument));
  }, true);
}

scrapeFortuneの結果はこのようなオブジェクトです。

{
 "おうし座": {"rank": 1,
             "text": "全てが順調に進んでHAPPY。悩んでいた課題に打開策を発見。新たな仲間も増え団結できそう。",
             "advice": "お気に入りの服を着る",
             "lucky": "デジタルカメラ"},
 "しし座":   {"rank": 12,
             "text": "自己中心の言動に冷たい視線。努力してきたことまで裏目に。理想ばかり追わず現実を見て。",
             "advice": "レジで一番長い列に並ぶ",
             "lucky": "アクセサリーショップ"},
 "おとめ座":  {"rank": 2,
             "text": "ロマンスがすぐそこに!初対面の人には常に笑顔。",
             "lucky": "アジアン小物"},
 "うお座":    {"rank": 3,
              "text": "実力を発揮するチャンス。攻めの姿勢で一歩成功に。",
              "lucky": "細長いピアス"},
 (以下略)
}

星座の設定画面に挑戦

http://eva-lu-ator.net/~gemma/geocities/matchfox/Clipboard14.png
extensions.fortune.consteに"いて座"などと設定を保存することにします。
以下の変更を加えます。
chrome/content/config.xul (設定画面のXULです)

  <prefpane id="prefpane0" image="chrome://fortune/skin/images/transparent.png" label="&Fortune.config.pane0;" flex="1">
    <preferences>
      <preference id="extensions.fortune.conste" name="extensions.fortune.conste" type="string" />
    </preferences>
    <vbox>
      <groupbox orient="vertical">
        <caption label="&Fortune.config.conste;"/>
        <radiogroup preference="extensions.fortune.conste">
          <radio value="おひつじ座" label="&Fortune.config.radio.aries;" />
          <radio value="おうし座"   label="&Fortune.config.radio.taurus;" />
          <radio value="ふたご座"   label="&Fortune.config.radio.gemini;" />
          <radio value="かに座"     label="&Fortune.config.radio.cancer;" />
          <radio value="しし座"     label="&Fortune.config.radio.leo;" />
          <radio value="おとめ座"   label="&Fortune.config.radio.virgo;" />
          <radio value="てんびん座" label="&Fortune.config.radio.libra;" />
          <radio value="さそり座"   label="&Fortune.config.radio.scorpio;" />
          <radio value="いて座"     label="&Fortune.config.radio.sagittarius;" />
          <radio value="やぎ座"     label="&Fortune.config.radio.capricorn;" />
          <radio value="みずがめ座" label="&Fortune.config.radio.aquarius;" />
          <radio value="うお座"     label="&Fortune.config.radio.pisces;" />
        </radiogroup>
      </groupbox>
    </vbox>
  </prefpane>

chrome/locale/ja/config.dtd (設定画面の日本語ラベルです)

<!ENTITY Fortune.config.conste "あなたの星座">
<!ENTITY Fortune.config.radio.aries "おひつじ座">
<!ENTITY Fortune.config.radio.taurus "おうし座">
<!ENTITY Fortune.config.radio.gemini "ふたご座">
<!ENTITY Fortune.config.radio.cancer "かに座">
<!ENTITY Fortune.config.radio.leo "しし座">
<!ENTITY Fortune.config.radio.virgo "おとめ座">
<!ENTITY Fortune.config.radio.libra "てんびん座">
<!ENTITY Fortune.config.radio.scorpio "さそり座">
<!ENTITY Fortune.config.radio.sagittarius "いて座">
<!ENTITY Fortune.config.radio.capricorn "やぎ座">
<!ENTITY Fortune.config.radio.aquarius "みずがめ座">
<!ENTITY Fortune.config.radio.pisces "うお座">

defaults/preferences/prefs.js (デフォルト設定です。私のいて座です)

pref("extensions.fortune.conste", "いて座");

chrome/content/sidebar/00-foobar0.js (設定を読むコードです)

function getMyConste() {
  var prefs = new Fortune.Prefs("extensions.fortune.");
  return prefs.get("conste");
}

http://eva-lu-ator.net/~gemma/geocities/matchfox/c1.jpg

最終的に00-foobar0.jsはこのようになります。
show関数がスクレイピングしたデータから自分の星座をとりだして表示します。

const EXPORT = ["sidebarFoobar0"];

function getMainWindow() {
  return getTopWin();
}

function $x(aDocument, aXPathString) {
  var nodes = aDocument.evaluate(aXPathString,
                aDocument, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  var arr = [];
  for (var i = 0, len = nodes.snapshotLength; i < len; i++) {
    arr.push(nodes.snapshotItem(i));
  }
  return arr;
}

function scrapeFortune(aDocument) {
  //contains(@alt, '座'). XPath 1.0 には end-with()がないのでcontainsでお茶を濁す。
  var constes = $x(aDocument, "//img[contains(@alt, '\u5EA7')]").map(function(x) {return x.alt;});
  var texts   = $x(aDocument, "//td[contains(@class,'text')]")  .map(function(x) {return x.innerHTML.replace(/<br>/g, "");});
  var luckys  = $x(aDocument, "//td[contains(@class,'lucky')]") .map(function(x) {return x.innerHTML;});
  var data = {};
  data[constes[0]]  = {rank: 1,  text: texts[0],  advice: luckys[0],  lucky: luckys[1]};
  data[constes[11]] = {rank: 12, text: texts[11], advice: luckys[12], lucky: luckys[13]};
  for (var i = 1; i < 11; i++) {
    data[constes[i]] = {rank: i + 1, text: texts[i], lucky: luckys[i + 1]};
  }
  return data;
}

function getMyConste() {
  var prefs = new Fortune.Prefs("extensions.fortune.");
  return prefs.get("conste");
}

function show(aData, aMyConste) {
  alert([aMyConste,
         "rank:"  + aData[aMyConste].rank,
         "text:"  + aData[aMyConste].text,
         "lucky:" + aData[aMyConste].lucky,
         aData[aMyConste].advice ? "advice:" + aData[aMyConste].advice : ""].join("\n"));
}

function sidebarFoobar0() {
  const url = "http://www.fujitv.co.jp/meza/uranai/index.html";
  var mw = getMainWindow();
  var newTab = mw.gBrowser.getBrowserForTab(mw.gBrowser.addTab(url));
  newTab.addEventListener("load", function(e) {
    show(scrapeFortune(newTab.contentDocument),
         getMyConste());
  }, true);
}

ここに置いときますね。 http://eva-lu-ator.net/~gemma/geocities/matchfox/fortune.xpi
GOODLUCK!!BABY!!