#web-scraping #rust #rust-tokio #reqwest
Вопрос:
Изучая ржавчину, я пытаюсь создать простой веб-скребок. Моя цель-наскрести https://news.ycombinator.com/ и получите заголовок, гиперссылку, голоса и имя пользователя. Я использую для этого внешние библиотеки reqwest и scraper и написал программу, которая удаляет HTML-ссылку с этого сайта.
Груз.томл
[package]
name = "stackoverflow_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(amp;selector) {
println!("{:?}",element.value().attr("href").unwrap());
// todo println!("Title");
// todo println!("Votes");
// todo println!("User");
}
Ok(())
}
Как мне получить соответствующее название, голоса и имя пользователя?
Ответ №1:
Элементы на первой странице хранятся в table
классе with .itemlist
.
Поскольку каждый элемент состоит из трех последовательных <tr>
элементов , вам придется перебирать их частями по три. Я решил сначала собрать все узлы.
Первая строка содержит:
- Название
- Домен
Вторая строка содержит:
- Очки
- Автор
- Пост-возраст
Третий ряд-это распорка, которую следует игнорировать.
Примечание:
- Сообщения, созданные в течение последнего часа, по-видимому, не отображают никаких точек, поэтому с этим необходимо обращаться соответствующим образом.
- Рекламные объявления не содержат имени пользователя.
- Последние две строки таблицы
tr.morespace
иtr
содержащиеa.morelink
их следует игнорировать. Вот почему я решил сначала.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(amp;selector_items).collect::<Vec<_>>();
let list = nodes
.chunks_exact(3)
.map(|rows| {
let title_elem = rows[0].select(amp;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(amp;selector_score)
.next()
.and_then(|n| n.text().nth(0))
.unwrap_or("0 points");
let user_text = rows[1]
.select(amp;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, который можно использовать:
Ответ №2:
Это скорее вопрос селекторов, и он зависит от html-кода очищаемого сайта. В этом случае легко получить титул, но сложнее получить очки и пользователя. Поскольку используемый вами селектор выбирает ссылку, содержащую как href, так и заголовок, вы можете получить заголовок с помощью метода .text()
let title = element.text().collect::<Vec<_>>();
где элемент такой же, как и для href
Однако, чтобы получить другие значения, было бы проще изменить первый селектор и получить данные из него. Начиная с заголовка и ссылки новостной статьи на news.ycombinator.com находится в элементе с классом .athing, а голоса и пользователь находятся в следующем элементе, у которого нет класса (что затрудняет выбор), возможно, лучше всего выбрать "table.itemlist tr.athing"
и повторить эти результаты. Из каждого найденного элемента вы можете затем "a.storylink"
выбрать элемент и отдельно получить следующий элемент tr и выбрать точки и пользовательские элементы
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(amp;select_item) {
// Get the link element that contains the href and title
let link_el = element.select(amp;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(amp;select_score).next().unwrap();
println!("{:?}", score.text().collect::<Vec<_>>());
}
Это показывает только получение href и оценку. Я оставлю это вам, чтобы вы получили пользователя от details_el