Rustでスクレイピング(html5ever)

## はじめに

本記事は、[スクレイピング](https://ja.wikipedia.org/wiki/%E3%82%A6%E3%82%A7%E3%83%96%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0)を推奨するものではありません。 **HTMLのParseがRustでも簡単にできる** ということを重点において解説したいと思います。

## RustでHTMLのParseを行う
Rustは、[Servo](https://github.com/servo/servo)の開発に使用されている言語で、Servoの開発によってRust言語も進化してきています。Servoは、Mozillaによって開発されているブラウザのレイアウトエンジンのことで、ServoのHTMLパーサも、Rustで書かれています。

このHTMLパーサが、[html5ever](https://github.com/servo/html5ever)です。本記事では、html5everを使った HTMLパーサについて簡単なコードの紹介を行いたいと思います。

## 必要なもの

```rust:Cargo.toml
[dependencies]
html5ever = "*"
tendril = "*"
```

```rust:use
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する
```rust
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の操作を行います。

## 子要素の走査
```rust
let node = handle.borrow();
```
で`handle`から`node`を借用します。この `node`は`core::cell::Ref<'_, html5ever::rcdom::Node>`型です。

```rust
for handle in &node.children {
   println!("{:?}", handle);
}
```

`node`は、`children` を持っており、handleを返します。

## 要素の属性を取得

```rust
let node = handle.borrow();
println!("{:?}", node.node);
//=> Element
```

`node`には、`node`フィールドがあり、ここに`Element`が入っています。

```rust
if let Element(ref name, ref element_enum, ref attrs) = node.node {
    println!("{:?}\n{:?}\n{:?}", name, element_enum, attrs);
}
```
または

```rust
match node.node {
    Element(ref name, ref element_enum, ref attrs) => {
        println!("--> {:?}\n{:?}\n{:?}", name, element_enum, attrs)
    }
    _ => panic!(),
}
```

[destructuring](https://doc.rust-lang.org/book/patterns.html#destructuring) で、`Element`から値を取り出します。


```rust
for attr in attrs.iter() {
    let Attribute { ref name, ref value } = *attr;
    println!("{},{}", &*name.local, value.to_string());
 }
```

後は同じように、 `attrs` から属性を取り出します。

## 要素のinner_text(inner_html)を取得する

```rust
// "<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のノードタイプを分けています。[こちら](http://doc.servo.org/html5ever/rcdom/enum.NodeEnum.html)に書かれているだけの種類があります。

```html
<div>こん<i>にち</i>わ</div>
```
例えばこのHTMLだと、divの中の要素は テキストノード+エレメントノード+テキストノードの組み合わせとなります。childrenのlengthは3になりますね。


## HandleをCloneする
借用されたNodeは、Cloneさせることができませんが、HandleはCloneさせることができます。

```rust
let hadle = handle.clone();
```

## まだまだいろんな機能があるけれど
html5everは機能が豊富で、すべてを説明しきれませんが、かなり優秀なパーサだと思います。ただ、スクレイピング用途としては少し敷居が高いかもしれません。RubyのNokogiriのようにもっと楽にパースしたい場合もあるでしょう。そこで最後におまけです。

## scraper

[scrapper](https://github.com/programble/scraper)
https://github.com/programble/scraper

というスクレイピングに特化したhtml5everのラッパークレートがあります。本当は、自分でもこのQiita記事の元となったスクレイピング用クレートを書いていたのですが、`scraper`が優秀すぎて入らなくなりそうです。。

追記: rust1.6ではビルドに失敗しました
更に追記: rust1.6でビルドが通る様になりました

## 最後に

RustでJSONをパースする記事も書いています

RustのJSONシリアライザをいろいろと試してみる(+α)
http://qiita.com/nacika_ins/items/0948fe2e49964dcc4858

よろしければこちらもご覧ください。