LoginSignup
43
42

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-01-27

はじめに

本記事は、スクレイピングを推奨するものではありません。 HTMLのParseがRustでも簡単にできる ということを重点において解説したいと思います。

RustでHTMLのParseを行う

Rustは、Servoの開発に使用されている言語で、Servoの開発によってRust言語も進化してきています。Servoは、Mozillaによって開発されているブラウザのレイアウトエンジンのことで、ServoのHTMLパーサも、Rustで書かれています。

このHTMLパーサが、html5everです。本記事では、html5everを使った HTMLパーサについて簡単なコードの紹介を行いたいと思います。

必要なもの

Cargo.toml
[dependencies]
html5ever = "*"
tendril = "*"
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する

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.documentHandle型になっており、この型に対してDOMの操作を行います。

子要素の走査

let node = handle.borrow();

handleからnodeを借用します。この nodecore::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

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

43
42
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
43
42