XSLT でスクレイピング

動機

ラリー・ウォールプログラマの三大美徳として無精・短気・傲慢を挙げたそうだ。 このうちで最も簡単に積める徳は無精であろう。 機械に出来ることは機械にさせるのがプログラマの基本思想であるべきだと主張しても反論は無いと思う。 そういうわけで、自分が日常的にやっている操作なんかはなるべく自動化したいわけだ。

さて、今日ではパソコンと名のつくものはネットワークに繋っていることが当然という雰囲気である。 インターネットを通じた情報収集は特に頻繁にやっている操作と言えるだろう。 もはやソフトも出揃っているし、ブログサイトは概ね RSS を提供するようになっているし、巡回のためにブラウザのブックマークをひとつひとつクリックするようなマネはしなくてよくなったのは非常にありがたいことである。

しかし、何もかも自分の思い通りのソフトが揃っているというわけにもいかない。 プログラマなら簡単なスクリプトで解決することは珍しくないだろう。

ここで話は変わって先日のことだが、ブログの一覧を取得してカテゴリ別に並べ変えるプログラムを PHP で書いている記事を読んだ。

「みそ録 | 「みそ文」カテゴリ別目次(自動生成)」出来たよ! - どんなジレンマ

ウェブから必要な情報を抽出することにスクレイピングなどという気取った名前がついていることをここで初めて知ったわけだが、そんなことは置いといて、対象にしている FC2 ブログの記事一覧ページが valid な xhtml になっていることに気付いた。 xhtml として valid ということは xml として valid であるということだ。 (だよね?)

xml を相手にするなら xslt であろう…。 ということから xslt スタイルシートを書いてみたのでありました。

成果

<?xml version='1.0' encoding='utf-8'?>
<xsl:stylesheet version='1.0'
                xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
                xmlns:xhtml="http://www.w3.org/1999/xhtml"
                xmlns="http://www.w3.org/1999/xhtml">
  <xsl:output method="xml" version="1.0" encoding="UTF-8"
              doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
              doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
              />
  <xsl:key name='list' match="xhtml:h3" use="xhtml:a[position()=2]/@href" />
  <xsl:template match="/">
    <html> <head> <title>
      <xsl:value-of select="/xhtml:html/xhtml:head/xhtml:title[1]"/>
      (カテゴリ別目次)
      </title> </head> <body>
      <xsl:variable
          name="baseurl"
          select=".//xhtml:div[@class='boxposition_left']/xhtml:a/@href"/>
      <xsl:for-each
          select="//xhtml:div[@class='alllog']/xhtml:h3[generate-id(.)=generate-id(key('list',./xhtml:a[position()=2]/@href))]">
        <xsl:sort select="./xhtml:a[position()=2]/@href"/>
        <h3> <xsl:copy-of select="./xhtml:a[position()=2]/text()" /> </h3>
        <xsl:for-each select="key('list', ./xhtml:a[position()=2]/@href)">
          <xsl:apply-templates select=".">
            <xsl:with-param name="baseurl" select="$baseurl"/>
          </xsl:apply-templates>
        </xsl:for-each>
      </xsl:for-each>
    </body></html>
  </xsl:template>

  <xsl:template match="xhtml:h3">
    <xsl:param name="baseurl" />
    <p>
      <xsl:apply-templates select="./xhtml:a[position()=1]">
        <xsl:with-param name="baseurl" select="$baseurl"/>
      </xsl:apply-templates>
    </p>
  </xsl:template>

  <xsl:template match="xhtml:a">
    <xsl:param name="baseurl" />
    <a href="{$baseurl}{@href}">
      <xsl:value-of select="." />
    </a>
  </xsl:template>
  
</xsl:stylesheet>

使い方

上述の xslt スタイルシートを fc2index.xsl という名前で保存したとするとこんな風にコマンドランから入力すればいい。

xsltproc -o index.xhtml fc2index.xsl http://douyaramiso.blog59.fc2.com/archives.html

これで index.xhtml という出力結果が得られる。 xsltproc の導入の仕方までは説明しない。

注意点としては、このままだと実行の度に dtd を取得するので遅くなってしまう。 適切に設定した上で --catalogs オプションを付けるとローカルファイルに読み換えてくれるのでもしも日常的に使おうという人がいたならその程度の手間はかけておくべきだろう。

前回のネタ

xslt を弄ったのはずいぶんと久しぶりであったので、前にネタにしたのはいつだったかと検索してみると2007年の正月だったようだ。

XSLTプログラミング - 主題のない日記

このときのネタはブラウザの xslt プロセッサで処理することを前提にして書いているので、ブラウザで開くだけで結果を確認することが出来る。 (IEFirefoxGoogleChrome でしか動作確認していないが。)

http://saito.s4.zmx.jp/junk/calendar.xml

xslt スタイルシートはここに置いておく。

http://saito.s4.zmx.jp/junk/calendar.xsl

このふたつをローカルに置いて、年の指定だけを変えれば毎年使えるので意外に実用的かもしれない。

感想等

カレンダー生成に比べると今回の目次作成は真っ当すぎて面白味の無い使い方だ。 記事にするほどでは無いとも思ったのだが、クライアントサイドでは xsltrss に適用する程度のことにしか使われていない現状がある。 存在自体が知られていないようなので紹介の意味であえて書くことにした。 強いて見どころを言えばグルーピングに使っている Muenchian 法だろうか。

今回もブラウザで処理するようにしようかと思ったのだが、クロスドメイン制限等の諸々の壁に挟まれて簡単には出来ないようだったので面倒になって諦めてしまった。
そういえば久々に xml を書く環境を整備しようかと思ったところ、最近の emacs には nxml-mode が同梱されている。 emacs の最新リリースを導入したばかりなので何もしなくて済んだ。 アマチュアには豪華すぎる開発環境がタダで手に入る現代が恐しくさえあるが、活用することもひとつの貢献と思って有難く使わせてもらっている。

Document ID: 59eaac880f8cb5f6770168a1558c4cc6