使用 hyper 和 html5ever 解析流中的 HTML 页面内容
Parsing HTML page content in a stream with hyper and html5ever
我正在尝试解析 HTTP 请求的 HTML 响应。我正在使用 hyper for the requests and 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 文档 reader ...如果这甚至可能的话。
The documentation for parse_document
表示如果输入以字节为单位(它是)使用 from_utf8
或 from_bytes
。
看来我需要根据响应创建接收器,但这就是我卡住的地方。我也不清楚如何创建事件来监听我感兴趣的标签开始。
我查看了 this example of html5ever,它似乎在做我想做的事并走 DOM,但我无法将这个例子本身带到 运行——要么是已过时或 tendril/html5ever 太新。这似乎也将 HTML 作为一个整体而不是一个流来解析,但我不确定。
是否可以使用这些库的当前实现来做我想做的事情?
很抱歉缺少 html5ever 和 tendril 的类似教程的文档…
除非您 100% 确定您的内容是 UTF-8,否则请使用 from_bytes
而不是 from_utf8
。他们 return 实现了 TendrilSink
的东西,它允许你递增地(或不递增地)提供输入。
std::io::Read::read_to_end
方法采用 &mut Vec<u8>
,因此不适用于 TendrilSink
。
在最底层,你可以对每个&[u8]
块调用一次TendrilSink::process
方法,然后调用TendrilSink::finish
.
为了避免手动执行此操作,还有采用 &mut R where R: std::io::Read
的 TendrilSink::read_from
方法。由于 hyper::client::Response
实现了 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();
尝试添加这个:
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();
根据板条箱文档的参数...
除非我误解了什么,否则处理 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());
我正在尝试解析 HTTP 请求的 HTML 响应。我正在使用 hyper for the requests and 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 文档 reader ...如果这甚至可能的话。
The documentation for parse_document
表示如果输入以字节为单位(它是)使用 from_utf8
或 from_bytes
。
看来我需要根据响应创建接收器,但这就是我卡住的地方。我也不清楚如何创建事件来监听我感兴趣的标签开始。
我查看了 this example of html5ever,它似乎在做我想做的事并走 DOM,但我无法将这个例子本身带到 运行——要么是已过时或 tendril/html5ever 太新。这似乎也将 HTML 作为一个整体而不是一个流来解析,但我不确定。
是否可以使用这些库的当前实现来做我想做的事情?
很抱歉缺少 html5ever 和 tendril 的类似教程的文档…
除非您 100% 确定您的内容是 UTF-8,否则请使用 from_bytes
而不是 from_utf8
。他们 return 实现了 TendrilSink
的东西,它允许你递增地(或不递增地)提供输入。
std::io::Read::read_to_end
方法采用 &mut Vec<u8>
,因此不适用于 TendrilSink
。
在最底层,你可以对每个&[u8]
块调用一次TendrilSink::process
方法,然后调用TendrilSink::finish
.
为了避免手动执行此操作,还有采用 &mut R where R: std::io::Read
的 TendrilSink::read_from
方法。由于 hyper::client::Response
实现了 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();
尝试添加这个:
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();
根据板条箱文档的参数...
除非我误解了什么,否则处理 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());