使用 scraper crate 检索兄弟元素
Retrieve sibling elements using the scraper crate
在学习 Rust 的同时,我正在尝试构建一个简单的网络抓取工具。我的目标是抓取 https://news.ycombinator.com/ 并获得标题、hyperlink、投票和用户名。我正在为此使用外部库 reqwest 和 scraper 并编写了一个程序从中抓取 HTML link网站。
Cargo.toml
[package]
name = "Whosebug_scraper"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
scraper = "0.12.0"
reqwest = "0.11.2"
tokio = { version = "1", features = ["full"] }
futures = "0.3.13"
src/main.rs
use scraper::{Html, Selector};
use reqwest;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = "https://news.ycombinator.com/";
let html = reqwest::get(url).await?.text().await?;
let fragment = Html::parse_fragment(html.as_str());
let selector = Selector::parse("a.storylink").unwrap();
for element in fragment.select(&selector) {
println!("{:?}",element.value().attr("href").unwrap());
// todo println!("Title");
// todo println!("Votes");
// todo println!("User");
}
Ok(())
}
如何获取对应的标题、投票数和用户名?
这更像是一个 select 的问题,它取决于被抓取的网站的 html。在这种情况下,获得称号很容易,但获得积分和用户就更难了。由于 select 或者您正在使用 select 包含 href 和标题的 link,您可以使用 .text() 方法
获取标题
let title = element.text().collect::<Vec<_>>();
其中元素与 href 相同
然而,要获取其他值,更改第一个 selector 并从中获取数据会更容易。由于 news.ycombinator.com 上新闻项的标题和 link 位于带有 .athing class 的元素中,而投票和用户位于下一个元素中,该元素没有class(使 select 更难),最好是 select "table.itemlist tr.athing"
并迭代这些结果。从找到的每个元素中,您可以将 select 子"a.storylink"
元素,并分别获取以下 tr 元素和子select 点和用户元素
let select_item = Selector::parse("table.itemlist tr.athing").unwrap();
let select_link = Selector::parse("a.storylink").unwrap();
let select_score = Selector::parse("span.score").unwrap();
for element in fragment.select(&select_item) {
// Get the link element that contains the href and title
let link_el = element.select(&select_link).next().unwrap();
println!("{:?}", link_el.value().attr("href").unwrap());
// Get the next tr element that follows the first, with score and user
let details_el = ElementRef::wrap(element.next_sibling().unwrap()).unwrap();
// Get the score element from within the second row element
let score = details_el.select(&select_score).next().unwrap();
println!("{:?}", score.text().collect::<Vec<_>>());
}
这只显示获取 href 和分数。我会留给你从 details_el
获取用户
首页上的项目存储在 table
和 class .itemlist
中。
由于每个项目都由三个连续的 <tr>
组成,您必须以三个为一组对它们进行迭代。我选择先收集所有节点。
第一行包含:
- 标题
- 域
第二行包含:
- 积分
- 作者
- Post 年龄
第三行是应该忽略的间隔符。
注:
- Post最近一小时内创建的似乎没有显示任何积分,因此需要相应处理。
- 广告不包含用户名。
- 最后两行 table,
tr.morespace
和包含 a.morelink
的 tr
应该被忽略。这就是为什么我选择先 .collect()
节点然后使用 .chunks_exact()
.
use reqwest;
use scraper::{Html, Selector};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = "https://news.ycombinator.com/";
let html = reqwest::get(url).await?.text().await?;
let fragment = Html::parse_fragment(html.as_str());
let selector_items = Selector::parse(".itemlist tr").unwrap();
let selector_title = Selector::parse("a.storylink").unwrap();
let selector_score = Selector::parse("span.score").unwrap();
let selector_user = Selector::parse("a.hnuser").unwrap();
let nodes = fragment.select(&selector_items).collect::<Vec<_>>();
let list = nodes
.chunks_exact(3)
.map(|rows| {
let title_elem = rows[0].select(&selector_title).next().unwrap();
let title_text = title_elem.text().nth(0).unwrap();
let title_href = title_elem.value().attr("href").unwrap();
let score_text = rows[1]
.select(&selector_score)
.next()
.and_then(|n| n.text().nth(0))
.unwrap_or("0 points");
let user_text = rows[1]
.select(&selector_user)
.next()
.and_then(|n| n.text().nth(0))
.unwrap_or("Unknown user");
[title_text, title_href, score_text, user_text]
})
.collect::<Vec<_>>();
println!("links: {:#?}", list);
Ok(())
}
你应该得到以下列表:
[
[
"Docker for Mac M1 RC",
"https://docs.docker.com/docker-for-mac/apple-m1/",
"327 points",
"mikkelam",
],
[
"A Mind Is Born – A 256 byte demo for the Commodore 64 (2017)",
"https://linusakesson.net/scene/a-mind-is-born/",
"226 points",
"matthewsinclair",
],
[
"Show HN: Video Game in a Font",
"https://www.coderelay.io/fontemon.html",
"416 points",
"ghub-mmulet",
],
...
]
或者,可以使用 API:
在学习 Rust 的同时,我正在尝试构建一个简单的网络抓取工具。我的目标是抓取 https://news.ycombinator.com/ 并获得标题、hyperlink、投票和用户名。我正在为此使用外部库 reqwest 和 scraper 并编写了一个程序从中抓取 HTML link网站。
Cargo.toml
[package]
name = "Whosebug_scraper"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
scraper = "0.12.0"
reqwest = "0.11.2"
tokio = { version = "1", features = ["full"] }
futures = "0.3.13"
src/main.rs
use scraper::{Html, Selector};
use reqwest;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = "https://news.ycombinator.com/";
let html = reqwest::get(url).await?.text().await?;
let fragment = Html::parse_fragment(html.as_str());
let selector = Selector::parse("a.storylink").unwrap();
for element in fragment.select(&selector) {
println!("{:?}",element.value().attr("href").unwrap());
// todo println!("Title");
// todo println!("Votes");
// todo println!("User");
}
Ok(())
}
如何获取对应的标题、投票数和用户名?
这更像是一个 select 的问题,它取决于被抓取的网站的 html。在这种情况下,获得称号很容易,但获得积分和用户就更难了。由于 select 或者您正在使用 select 包含 href 和标题的 link,您可以使用 .text() 方法
获取标题let title = element.text().collect::<Vec<_>>();
其中元素与 href 相同
然而,要获取其他值,更改第一个 selector 并从中获取数据会更容易。由于 news.ycombinator.com 上新闻项的标题和 link 位于带有 .athing class 的元素中,而投票和用户位于下一个元素中,该元素没有class(使 select 更难),最好是 select "table.itemlist tr.athing"
并迭代这些结果。从找到的每个元素中,您可以将 select 子"a.storylink"
元素,并分别获取以下 tr 元素和子select 点和用户元素
let select_item = Selector::parse("table.itemlist tr.athing").unwrap();
let select_link = Selector::parse("a.storylink").unwrap();
let select_score = Selector::parse("span.score").unwrap();
for element in fragment.select(&select_item) {
// Get the link element that contains the href and title
let link_el = element.select(&select_link).next().unwrap();
println!("{:?}", link_el.value().attr("href").unwrap());
// Get the next tr element that follows the first, with score and user
let details_el = ElementRef::wrap(element.next_sibling().unwrap()).unwrap();
// Get the score element from within the second row element
let score = details_el.select(&select_score).next().unwrap();
println!("{:?}", score.text().collect::<Vec<_>>());
}
这只显示获取 href 和分数。我会留给你从 details_el
首页上的项目存储在 table
和 class .itemlist
中。
由于每个项目都由三个连续的 <tr>
组成,您必须以三个为一组对它们进行迭代。我选择先收集所有节点。
第一行包含:
- 标题
- 域
第二行包含:
- 积分
- 作者
- Post 年龄
第三行是应该忽略的间隔符。
注:
- Post最近一小时内创建的似乎没有显示任何积分,因此需要相应处理。
- 广告不包含用户名。
- 最后两行 table,
tr.morespace
和包含a.morelink
的tr
应该被忽略。这就是为什么我选择先.collect()
节点然后使用.chunks_exact()
.
use reqwest;
use scraper::{Html, Selector};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = "https://news.ycombinator.com/";
let html = reqwest::get(url).await?.text().await?;
let fragment = Html::parse_fragment(html.as_str());
let selector_items = Selector::parse(".itemlist tr").unwrap();
let selector_title = Selector::parse("a.storylink").unwrap();
let selector_score = Selector::parse("span.score").unwrap();
let selector_user = Selector::parse("a.hnuser").unwrap();
let nodes = fragment.select(&selector_items).collect::<Vec<_>>();
let list = nodes
.chunks_exact(3)
.map(|rows| {
let title_elem = rows[0].select(&selector_title).next().unwrap();
let title_text = title_elem.text().nth(0).unwrap();
let title_href = title_elem.value().attr("href").unwrap();
let score_text = rows[1]
.select(&selector_score)
.next()
.and_then(|n| n.text().nth(0))
.unwrap_or("0 points");
let user_text = rows[1]
.select(&selector_user)
.next()
.and_then(|n| n.text().nth(0))
.unwrap_or("Unknown user");
[title_text, title_href, score_text, user_text]
})
.collect::<Vec<_>>();
println!("links: {:#?}", list);
Ok(())
}
你应该得到以下列表:
[
[
"Docker for Mac M1 RC",
"https://docs.docker.com/docker-for-mac/apple-m1/",
"327 points",
"mikkelam",
],
[
"A Mind Is Born – A 256 byte demo for the Commodore 64 (2017)",
"https://linusakesson.net/scene/a-mind-is-born/",
"226 points",
"matthewsinclair",
],
[
"Show HN: Video Game in a Font",
"https://www.coderelay.io/fontemon.html",
"416 points",
"ghub-mmulet",
],
...
]
或者,可以使用 API: