2014年04月03日

CSSBoxでCSSの情報を含めたHTMLを落とす

WebからHTMLを取得して保存しておく際に、スタイルの情報も含めて記録しておきたい時がある。CSSBoxを使ってstylesToDomとかすればうまく記録できそうだったのでやってみた。

具体的には下記のようなコードで、org.fit.cssbox.css.DOMAnalyzerのstylesToDom()、stylesToDomInherited()なんかを設定することでうまくいった。

import java.io.FileOutputStream
import java.io.OutputStreamWriter

import org.apache.xml.serialize.OutputFormat
import org.apache.xml.serialize.XMLSerializer
import org.fit.cssbox.css.CSSNorm
import org.fit.cssbox.css.DOMAnalyzer
import org.fit.cssbox.io.DefaultDOMSource
import org.fit.cssbox.io.DefaultDocumentSource
import org.fit.cssbox.layout.BrowserCanvas

def download(url: java.net.URL) {

  // URLからDOMを取る
  val doc = managed(new DefaultDocumentSource(url)) { source =>
    new DefaultDOMSource(source).parse()
  }

  // DOMAnalyzerでCSSを読むように設定
  val analyzer = new DOMAnalyzer(doc, url)
  analyzer.attributesToStyles()
  // CSS周りの設定
  analyzer.addStyleSheet(null, CSSNorm.stdStyleSheet(), DOMAnalyzer.Origin.AGENT)
  analyzer.addStyleSheet(null, CSSNorm.userStyleSheet(), DOMAnalyzer.Origin.AGENT)
  analyzer.addStyleSheet(null, CSSNorm.formsStyleSheet(), DOMAnalyzer.Origin.AGENT)
  // 設定したCSSの読み込み(メソッド名はgetだけどvoid型)
  analyzer.getStyleSheets()
  // スタイルシートの内容をDOMのstyle属性に入れる設定
  analyzer.stylesToDom()
  analyzer.stylesToDomInherited()

  // 編集したDOMを出力
  managed(new FileOutputStream("result.html")) { os =>
    val serializer = new XMLSerializer()
    val outputFormat = new OutputFormat()
    outputFormat.setIndenting(false);
    serializer.setOutputFormat(outputFormat)
    serializer.setOutputCharStream(new OutputStreamWriter(os))
    serializer.serialize(doc)
  }
}

これで試しにうちのサイトを読み込んでみると、analyzer.stylesToDom()を削った場合は、下記のような出力になる。(一部抜粋)

  <div class="section">
<h2>メニュー</h2>

<div class="menu_link"><a class="menu" href="/programming/">プログラミングメモ</a></div>
<div class="menu_description">形態素解析やScala等、雑多な内容のメモ書き</div>

analyzer.stylesToDom()を入れた場合は、下記のようにスタイル等の情報がわんさか記述された状態になる。

  <div class="section" style="background-color: #ffffff;border-bottom-color: #70a0a0;border-bottom-left-radius: 15.0px 15.0px;border-bottom-right-radius: 15.0px 15.0px;border-bottom-style: solid;border-bottom-width: 1.0px;border-left-color: #70a0a0;border-left-style: solid;border-left-width: 1.0px;border-right-color: #70a0a0;border-right-style: solid;border-right-width: 1.0px;border-top-color: #70a0a0;border-top-left-radius: 15.0px 15.0px;border-top-right-radius: 15.0px 15.0px;border-top-style: solid;border-top-width: 1.0px;color: #111111;display: block;font-family: sans-serif;line-height: 1.12;margin-bottom: 10.0px;margin-left: 5.0px;margin-right: 5.0px;margin-top: 10.0px;padding-bottom: 5.0px;padding-left: 15.0px;padding-right: 15.0px;padding-top: 15.0px;width: 100.0%;">
<h2 style="color: #004060;display: block;font-family: sans-serif;font-size: 120.0%;font-weight: bolder;line-height: 1.12;margin-bottom: 0.0px;margin-left: 0.0px;margin-right: 0.0px;margin-top: 0.0px;padding-bottom: 0.0px;padding-left: 0.0px;padding-right: 0.0px;padding-top: 0.0px;">メニュー</h2>

<div class="menu_link" style="color: #111111;display: block;font-family: sans-serif;font-size: 100.0%;font-weight: bold;line-height: 1.12;margin-bottom: 5.0px;margin-left: 0.0px;margin-right: 0.0px;margin-top: 20.0px;"><a class="menu" href="/programming/" style="color: #406060;font-family: sans-serif;font-weight: bold;line-height: 1.12;text-decoration: none;">プログラミングメモ</a></div>
<div class="menu_description" style="color: #606060;display: block;font-family: sans-serif;line-height: 1.12;margin-bottom: 10.0px;margin-left: 20.0px;margin-right: 20.0px;margin-top: 0.0px;">形態素解析やScala等、雑多な内容のメモ書き</div>

スタイルの情報が付いているので、そのままブラウザで開いてもあまり崩れずに表示してくれる。例えばYahooのニュースページなんかは下記のようにそこそこ原型を保って表示される。

gihyo.jpあたりもきっちり再現できた。

JavaScriptは読み込めないので、そっちでグリグリ書いてるページはダメで、Googleやはてなブックマークなんかはまったく表示できなかった。

あと、通信して、HTMLを取ってきて、パースして、CSSも取ってきて、DOMに適用して、といった処理をしているので実行時間はそれなりにかかる。重いサイトだと5秒以上かかることも。

個人的にはこうやって取得したHTMLを再度CSSBoxで読み込んで、width, height, absoluteXPos, absoluteYPosなんかを取り出す用途で使用した。それなりに現物に近い各情報が取れている。情報としてないよりあった方がだいぶ良いはず。