【问题标题】:Using Rust libraries reqwest and select in conjunction结合使用 Rust 库 reqwest 和 select
【发布时间】:2020-05-25 05:25:16
【问题描述】:

我正在尝试按照此处的示例进行操作: https://rust-lang-nursery.github.io/rust-cookbook/web/scraping.html,它利用 Reqwest 和 Select 来获取 html 响应,然后解析数据。

我使用的是 Reqwest 版本 0.10.4 和选择版本 0.4.3,它们是示例中显示的版本。但是,我收到一个错误:

error[E0277]: the trait bound `reqwest::Response: std::io::Read` is not satisfied
  --> src/main.rs:19:25
   |
19 |     Document::from_read(res)?
   |                         ^^^ the trait `std::io::Read` is not implemented for `reqwest::Response`
   | 
  ::: /root/.cargo/registry/src/github.com-1ecc6299db9ec823/select-0.4.3/src/document.rs:31:25
   |
31 |     pub fn from_read<R: io::Read>(mut readable: R) -> io::Result<Document> {
   |                         -------- required by this bound in `select::document::Document::from_read`

似乎 from_read 方法接受了 Read 类型,但 reqwest::get 方法返回了不同的类型。在将响应传递给 from_read 方法之前,是否必须先进行某种转换?

这是一个例子:

#[macro_use]
extern crate error_chain;
extern crate reqwest;
extern crate select;

use select::document::Document;
use select::predicate::Name;

error_chain! {
   foreign_links {
       ReqError(reqwest::Error);
       IoError(std::io::Error);
   }
}

fn main() -> Result<()> {
    let res = reqwest::get("https://www.rust-lang.org/en-US/").await?;

    Document::from_read(res)?
        .find(Name("a"))
        .filter_map(|n| n.attr("href"))
        .for_each(|x| println!("{}", x));

    Ok(())
}

【问题讨论】:

    标签: select rust reqwest


    【解决方案1】:

    reqwest::get 返回一个Result&lt;Response&gt;,然后使用?,您将解开Result,这意味着您现在有一个Response 对象,如文档中的here 所示。由于网络调用可以成功发生但仍然失败(请参阅 HTTP 非 200 代码),您应该检查响应代码,但由于这是为了学习,我们将忽略它。您想要的是一个实现 std::io::Read 特征的结构,继续阅读表明 String 实现了该特征。回到reqwest::Response 表明我们可以使用text() 方法获得返回的字符串。所以你的代码现在变成了

    let res = reqwest::get("https://www.rust-lang.org/en-US/")
                  .await?
                  .text()
                  .await?;
    

    来自 cmets 的更新:现在的问题是 Document::from_read 只接受 std::io::Read 作为参数,而 std::string::String 没有实现该特征,而是使用像 @987654337 这样的板条箱@ 我们可以简单地使用Document::from,因为文档实现了From&lt;&amp;'a str&gt; trait。

    而且,与几乎所有事情一样,有多种方法可以解决这个问题。你也可以

    1. 直接使用Document::from(res)从字符串创建文档,或者

    2. 将字符串转换为字节切片,实现读取,并使用Document::from_read(res.as_bytes())创建文档

    【讨论】:

    • 感谢您的回复。在进行一些测试时,我也尝试了这种方法。我收到了类似的错误“没有为 'std::string::String' 实现特征 'std::io::Read'”。我查看了文档中的一些 Read 文档:doc.rust-lang.org/std/io/trait.Read.html#implementors。底部的实现者部分没有将 String 显示为实现者——我不确定这些是否是唯一可以与 Read 一起使用的类型,因为我对 Rust 还是很陌生。
    • 啊,是的,我现在明白了,我盲目地假设 String 实现了 Read。浏览我看到的实现者&amp;'a [u8],所以你可以在字符串上调用as_bytes
    • 或者,代替Document::from_read(),使用Document::from(res),因为 Document 结构实现了From&lt;&amp;'a str&gt; 特征,如记录的here
    • 感谢您的帮助。我最终将其转换为 &[u8] 并编译!
    • 很好,但您也可以试试Document::from 的方式吗?因为将我们的字符串转换为字节数组会丢弃任何编码信息,这充其量是次优的