はじめに
本記事は、スクレイピングを推奨するものではありません。 HTMLのParseがRustでも簡単にできる ということを重点において解説したいと思います。
RustでHTMLのParseを行う
Rustは、Servoの開発に使用されている言語で、Servoの開発によってRust言語も進化してきています。Servoは、Mozillaによって開発されているブラウザのレイアウトエンジンのことで、ServoのHTMLパーサも、Rustで書かれています。
このHTMLパーサが、html5everです。本記事では、html5everを使った HTMLパーサについて簡単なコードの紹介を行いたいと思います。
必要なもの
[dependencies]
html5ever = "*"
tendril = "*"
extern crate tendril;
extern crate html5ever;
use std::io::{self, Write};
use std::default::Default;
use tendril::{ByteTendril, ReadExt};
use html5ever::driver::ParseOpts;
use html5ever::tokenizer::Attribute;
use html5ever::tree_builder::TreeBuilderOpts;
use html5ever::{parse as parse_ever, one_input, serialize};
use html5ever::rcdom::{RcDom, Handle, Element, ElementEnum, NodeEnum, Node, Text};
基本的にはこれで十分だと思います。足りないものがあれば、随時追加してください。
Parseする
let text = "<div></div>".to_owned();
let mut source = ByteTendril::new();
text.as_bytes().read_to_tendril(&mut source).unwrap();
let source = source.try_reinterpret().unwrap();
let dom: RcDom = parse_ever(one_input(source), Default::default());
//=> dom.document
これでパースは完了です。dom.document が Handle型になっており、この型に対してDOMの操作を行います。
子要素の走査
let node = handle.borrow();
でhandleからnodeを借用します。この nodeはcore::cell::Ref<'_, html5ever::rcdom::Node>型です。
for handle in &node.children {
   println!("{:?}", handle);
}
nodeは、children を持っており、handleを返します。
要素の属性を取得
let node = handle.borrow();
println!("{:?}", node.node);
//=> Element
nodeには、nodeフィールドがあり、ここにElementが入っています。
if let Element(ref name, ref element_enum, ref attrs) = node.node {
    println!("{:?}\n{:?}\n{:?}", name, element_enum, attrs);
}
または
match node.node {
    Element(ref name, ref element_enum, ref attrs) => {
        println!("--> {:?}\n{:?}\n{:?}", name, element_enum, attrs)
    }
    _ => panic!(),
}
destructuring で、Elementから値を取り出します。
for attr in attrs.iter() {
    let Attribute { ref name, ref value } = *attr;
    println!("{},{}", &*name.local, value.to_string());
 }
後は同じように、 attrs から属性を取り出します。
要素のinner_text(inner_html)を取得する
// "<div><a id=\"hoge\" href=\"hoge\">text</a></div>".to_owned()
let node = handle.borrow();
let child_node = node.children[0].borrow();
if let Text(ref text) = child_node.node {
    println!("{:?}", text.to_string());
}
ちょっと面倒くさいですね。DOMの場合、inner_textは、テキストノード扱いになります。
html5everでは(HTML5のDOMの種類に準じています)、NodeEnumという列挙型で
DOMのノードタイプを分けています。こちらに書かれているだけの種類があります。
<div>こん<i>にち</i>わ</div>
例えばこのHTMLだと、divの中の要素は テキストノード+エレメントノード+テキストノードの組み合わせとなります。childrenのlengthは3になりますね。
HandleをCloneする
借用されたNodeは、Cloneさせることができませんが、HandleはCloneさせることができます。
let hadle = handle.clone();
まだまだいろんな機能があるけれど
html5everは機能が豊富で、すべてを説明しきれませんが、かなり優秀なパーサだと思います。ただ、スクレイピング用途としては少し敷居が高いかもしれません。RubyのNokogiriのようにもっと楽にパースしたい場合もあるでしょう。そこで最後におまけです。
scraper
scrapper
https://github.com/programble/scraper
というスクレイピングに特化したhtml5everのラッパークレートがあります。本当は、自分でもこのQiita記事の元となったスクレイピング用クレートを書いていたのですが、scraperが優秀すぎて入らなくなりそうです。。
追記: rust1.6ではビルドに失敗しました
更に追記: rust1.6でビルドが通る様になりました
最後に
RustでJSONをパースする記事も書いています
RustのJSONシリアライザをいろいろと試してみる(+α)
http://qiita.com/nacika_ins/items/0948fe2e49964dcc4858
よろしければこちらもご覧ください。