我正在尝试使用 tokio、reqwest 和 scraper 板条箱在 Rust 中执行异步和并行 HTML 抓取。我有一个循环遍历 HTML 元素并使用 tokio::spawn 同时处理每个元素。但是,我遇到了 ElementRef 类型未实现 Send 特征的问题,导致无法在异步上下文中使用它。
示例:
<div class="aside">....</div>
<div class="aside">....</div>
<div class="aside">....</div>
代码:
#[tokio::main]
async fn main() -> Res<()> {
...
let response = reqwest::get(url).await?;
...
let document: ElementRef<'_> = document.root_element();
for node in document.select(&ASIDE_SELECTOR) {
tokio::spawn(async move {
// Parse, insert to Db, etc.
// Issue: ElementRef is not Send, preventing async usage
println!("{}", node.inner_html());
});
}
Ok(())
}
如何解决这个问题并在 Rust 中执行异步并行 HTML 抓取?在这种情况下是否可以使用替代类型或方法?
这会失败,因为
ElementRef
包含对文档结构的引用,但独立的 Tokio 任务没有生命周期限制,因此传递给 tokio::spawn
的 future 必须是 'static
。这个矛盾导致了错误。
另请注意,当主任务(在本例中为
main()
)终止时,任何生成的后台任务都将终止,因此此代码将启动一堆后台任务,然后很可能在它们可以执行任何工作之前终止它们。
您实际上不太可能需要为每个元素生成一个任务。考虑将
select
迭代器转换为 stream。完成此操作后,您可以将每个元素映射到未来,并使用buffer_unordered
组合器同时等待它们。这将解决这两个问题。
类似这样的:
futures::stream::iter(document.select(&ASIDE_SELECTOR))
.map(|node| async move {
// Parse, insert to db, etc.
})
// Run up to 16 concurrently.
.buffer_unordered(16)
.await;
for_each_concurrent
组合器代替map
和buffer_unordered
。