代码之家  ›  专栏  ›  技术社区  ›  Josh Mcoch

锈蚀所有权与时效问题

  •  1
  • Josh Mcoch  · 技术社区  · 7 月前
    use scraper::html::Select;
    
    fn get_doc(url: String) -> scraper::Html {
        let response = reqwest::blocking::get(url);
        let html_content = response.unwrap().text().unwrap();
        scraper::Html::parse_document(&html_content)
    }
    
    fn get_item(doc: &scraper::Html, css_selector: String) -> Select<'static, 'static> {
        let html_product_selector = scraper::Selector::parse(&css_selector).unwrap();
        doc.select(&html_product_selector)
    }
    
    fn main() {
        let doc = get_doc("https://www.zenrows.com/blog/rust-web-scraping#get-target-webpage".to_string());
        let item = get_item(&doc, String::from("li.product"));
    }
    

    我对Rust相当陌生(因此对生命周期等并不完全满意),我正在阅读一篇文章来构建一个scraper,并将每个部分拆分为自己的函数,以便在更大的项目中使用。我的问题是 scraper::Html.select 用于 get_item 似乎分配了属于的内存 获取项目 .

    错误如下:

     cargo run                                              
       Compiling ws v0.1.0 (/Users/calebcosta/coding/ws)
    error: lifetime may not live long enough
      --> src/main.rs:12:5
       |
    10 | fn get_item(doc: &scraper::Html, css_selector: String) -> Select<'static, 'static> {
       |                  - let's call the lifetime of this reference `'1`
    11 |     let html_product_selector = scraper::Selector::parse(&css_selector).unwrap();
    12 |     doc.select(&html_product_selector)
       |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'static`
    
    error[E0515]: cannot return value referencing local variable `html_product_selector`
      --> src/main.rs:12:5
       |
    12 |     doc.select(&html_product_selector)
       |     ^^^^^^^^^^^----------------------^
       |     |          |
       |     |          `html_product_selector` is borrowed here
       |     returns a value referencing data owned by the current function
       |
       = help: use `.collect()` to allocate the iterator
    
    warning: unused variable: `item`
      --> src/main.rs:17:9
       |
    17 |     let item = get_item(&doc, String::from("li.product"));
       |         ^^^^ help: if this is intentional, prefix it with an underscore: `_item`
       |
       = note: `#[warn(unused_variables)]` on by default
    
    For more information about this error, try `rustc --explain E0515`.
    warning: `ws` (bin "ws") generated 1 warning
    error: could not compile `ws` (bin "ws") due to 2 previous errors; 1 warning emitted
    

    我问ChatGPT如何解决这个问题,但它给了我很多答案,我想知道一个真正的rust开发人员将如何处理这个问题,以及如何借此机会更熟悉生命周期和所有权。

    1 回复  |  直到 7 月前
        1
  •  2
  •   Chayim Friedman    7 月前

    Html::select() 两者均从 Html Selector The Html 是一个参数,所以你可以借用它(但你没有告诉编译器你这样做了——这是第一个错误),但 选择器 是一个局部变量,因此您无法返回从中借用的内容。幸运的是,有一个解决方案: Html::select() 返回a Select ,它只是一个迭代器 ElementRef ,从 Html 但不是从 选择器 。因此,您可以将迭代器收集到 Vec :

    fn get_item<'a>(doc: &'a scraper::Html, css_selector: String) -> Vec<ElementRef<'a>> {
        let html_product_selector = scraper::Selector::parse(&css_selector).unwrap();
        doc.select(&html_product_selector).collect()
    }
    

    或者,你可以省略生命周期,因为这是唯一的生命周期:

    fn get_item(doc: &scraper::Html, css_selector: String) -> Vec<ElementRef<'_>> {
        // ...
    }
    

    或者,如果你只想要第一场比赛:

    fn get_item(doc: &scraper::Html, css_selector: String) -> Option<ElementRef<'_>> {
        let html_product_selector = scraper::Selector::parse(&css_selector).unwrap();
        doc.select(&html_product_selector).next()
    }