はじめに
本記事は、スクレイピングを推奨するものではありません。 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
よろしければこちらもご覧ください。