15

我正在尝试解析 HTTP 请求的 HTML 响应。我使用hyper进行请求,使用html5ever进行解析。HTML 将非常大,我不需要完全解析它——我只需要从标签中识别一些数据,所以我更喜欢流式传输它。从概念上讲,我想做类似的事情:

# bash
curl url | read_dom

/* javascript */
http.get(url).pipe(parser);
parser.on("tag", /* check tag name, attributes, and act */)

到目前为止,我想出的是:

extern crate hyper;
extern crate html5ever;

use std::default::Default
use hyper::Client;
use html5ever::parse_document;
use html5ever::rcdom::{RcDom};

fn main() {
    let client = Client::new();

    let res = client.post(WEBPAGE)
        .header(ContentType::form_url_encoded())
        .body(BODY)
        .send()
        .unwrap();

    res.read_to_end(parse_document(RcDom::default(),
      Default::default().from_utf8().unwrap()));
}

这似乎read_to_end是我想调用响应以读取字节的方法,但我不清楚如何将其通过管道传输到 HTML 文档阅读器......如果这甚至可能的话。

文档parse_document说要使用from_utf8或者from_bytes输入是否以字节为单位(它是)。

似乎我需要从响应中创建一个接收器,但这就是我卡住的地方。我也不清楚如何创建事件来监听我感兴趣的标签开始。

我已经查看了 html5ever 的这个示例,它似乎可以执行我想要的操作并遍历 DOM,但是我无法让这个示例本身运行——要么它已经过时,要么卷须/html5ever 太新。这似乎也将 HTML 解析为一个整体而不是一个流,但我不确定。

是否可以对这些库的当前实现做我想做的事情?

4

3 回答 3

8

很抱歉缺少 html5ever 和卷须的类似教程的文档……</p>

除非您 100% 确定您的内容是 UTF-8,否则请使用from_bytes而不是from_utf8. 他们返回一些实现的东西,TendrilSink允许您增量地(或不)提供输入。

std::io::Read::read_to_end方法需要 a &mut Vec<u8>,因此它不适用于TendrilSink

在最低级别,您可以为TendrilSink::process每个&[u8]块调用一次该方法,然后调用TendrilSink::finish.

为避免手动执行此操作,还有TendrilSink::read_from采用&mut R where R: std::io::Read. 由于hyper::client::Responseimplements Read,您可以使用:

parse_document(RcDom::default(), Default::default()).from_bytes().read_from(&mut res)

超越你的问题,RcDom它是非常少的,并且主要是为了测试 html5ever 而存在的。我建议改用Kuchiki。它具有更多功能(用于树遍历、CSS 选择器匹配……),包括可选的 Hyper 支持。

在你的Cargo.toml

[dependencies]
kuchiki = {version = "0.3.1", features = ["hyper"]}

在您的代码中:

let document = kuchiki::parse_html().from_http(res).unwrap();
于 2016-02-26T20:02:26.987 回答
1

除非我误解了某些东西,否则处理 HTML 标记非常复杂(不幸的是,原子常量的名称远非完美)。此代码演示了如何使用html5ever版本0.25.1来处理令牌。

首先,我们想要一个String带有 HTML 正文的:

let body = {
    let mut body = String::new();
    let client = Client::new();

    client.post(WEBPAGE)
        .header(ContentType::form_url_encoded())
        .body(BODY)
        .send()?
        .read_to_string(&mut body);

    body
};

其次,我们需要定义自己的Sink,其中包含“回调”并让您保持所需的任何状态。对于这个例子,我将检测<a>标签并将它们作为 HTML 打印回来(这需要我们检测开始标签、结束标签、文本并找到一个属性;希望是一个足够完整的例子):

use html5ever::tendril::StrTendril;
use html5ever::tokenizer::{
    BufferQueue, Tag, TagKind, Token, TokenSink, TokenSinkResult, Tokenizer,
};
use html5ever::{ATOM_LOCALNAME__61 as TAG_A, ATOM_LOCALNAME__68_72_65_66 as ATTR_HREF};

// Define your own `TokenSink`. This is how you keep state and your "callbacks" run.
struct Sink {
    text: Option<String>,
}

impl TokenSink for Sink {
    type Handle = ();

    fn process_token(&mut self, token: Token, _line_number: u64) -> TokenSinkResult<()> {
        match token {
            Token::TagToken(Tag {
                kind: TagKind::StartTag,
                name,
                self_closing: _,
                attrs,
            }) => match name {
                // Check tag name, attributes, and act.
                TAG_A => {
                    let url = attrs
                        .into_iter()
                        .find(|a| a.name.local == ATTR_HREF)
                        .map(|a| a.value.to_string())
                        .unwrap_or_else(|| "".to_string());

                    print!("<a href=\"{}\">", url);
                    self.text = Some(String::new());
                }
                _ => {}
            },
            Token::TagToken(Tag {
                kind: TagKind::EndTag,
                name,
                self_closing: _,
                attrs: _,
            }) => match name {
                TAG_A => {
                    println!(
                        "{}</a>",
                        self.text.take().unwrap()
                    );
                }
                _ => {}
            },
            Token::CharacterTokens(string) => {
                if let Some(text) = self.text.as_mut() {
                    text.push_str(&string);
                }
            }
            _ => {}
        }
        TokenSinkResult::Continue
    }
}


let sink = {
    let sink = Sink {
        text: None,
    };

    // Now, feed the HTML `body` string to the tokenizer.
    // This requires a bit of setup (buffer queue, tendrils, etc.).
    let mut input = BufferQueue::new();
    input.push_back(StrTendril::from_slice(&body).try_reinterpret().unwrap());
    let mut tok = Tokenizer::new(sink, Default::default());
    let _ = tok.feed(&mut input);
    tok.end();
    tok.sink
};

// `sink` is your `Sink` after all processing was done.
assert!(sink.text.is_none());
于 2020-12-19T20:46:27.490 回答
-3

尝试添加这个:

let mut result: Vec<u8> = Vec::new();

res.read_to_end(&mut result);

let parse_result = parse_document(RcDom::default(), Default::default())
    . //read parameters
    .unwrap();

参数符合板条箱文档...

于 2016-02-26T20:03:41.330 回答