94 lines
2.7 KiB
Rust
94 lines
2.7 KiB
Rust
use reqwest::blocking::Client;
|
|
use scraper::{Html, Selector};
|
|
use serde::Serialize;
|
|
use std::{collections::HashSet, fs};
|
|
|
|
const BASE: &str = "https://letterboxd.com";
|
|
const LIST: &str = "https://letterboxd.com/mimuuu/list/2026/by/reverse/";
|
|
|
|
#[derive(Serialize)]
|
|
struct Film {
|
|
title: String,
|
|
year: String,
|
|
director: String,
|
|
url: String,
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
let client = Client::builder().user_agent("Mozilla/5.0").build()?;
|
|
|
|
// Get latest 5 film URLs
|
|
let html = client.get(LIST).send()?.text()?;
|
|
let doc = Html::parse_document(&html);
|
|
|
|
let poster_sel = Selector::parse("li.posteritem div.react-component")?;
|
|
|
|
let mut urls = Vec::new();
|
|
let mut seen = HashSet::new();
|
|
|
|
for node in doc.select(&poster_sel) {
|
|
if let Some(path) = node.value().attr("data-item-link") {
|
|
let url = format!("{BASE}{path}");
|
|
if seen.insert(url.clone()) {
|
|
urls.push(url);
|
|
}
|
|
}
|
|
if urls.len() == 5 {
|
|
break;
|
|
}
|
|
}
|
|
|
|
let mut films = Vec::new();
|
|
let original_title_sel = Selector::parse(".productioninfo .quoted-creative-work-title")?;
|
|
let title_sel = Selector::parse(".details .primaryname .name")?;
|
|
let year_sel = Selector::parse(".productioninfo .releasedate a")?;
|
|
let directors_sel = Selector::parse(".credits .contributorlist")?;
|
|
let a_sel = Selector::parse("a")?;
|
|
|
|
for url in urls {
|
|
let html = client.get(&url).send()?.text()?;
|
|
let doc = Html::parse_document(&html);
|
|
|
|
let mut title = doc
|
|
.select(&original_title_sel)
|
|
.next()
|
|
.map(|node| node.text().collect::<String>().trim().to_string())
|
|
.unwrap_or_default();
|
|
|
|
if title.is_empty() {
|
|
title = doc
|
|
.select(&title_sel)
|
|
.next()
|
|
.map(|node| node.text().collect::<String>().trim().to_string())
|
|
.unwrap_or_default();
|
|
}
|
|
|
|
let year = doc
|
|
.select(&year_sel)
|
|
.next()
|
|
.map(|node| node.text().collect::<String>().trim().to_string())
|
|
.unwrap_or_default();
|
|
|
|
let director = doc
|
|
.select(&directors_sel)
|
|
.flat_map(|node| node.select(&a_sel))
|
|
.map(|a| a.text().collect::<String>().trim().to_string())
|
|
.collect::<Vec<_>>()
|
|
.join(", ");
|
|
|
|
films.push(Film {
|
|
title,
|
|
year,
|
|
director,
|
|
url,
|
|
});
|
|
}
|
|
|
|
fs::create_dir_all("data")?;
|
|
fs::write("data/films.json", serde_json::to_string_pretty(&films)?)?;
|
|
|
|
println!("Saved {} films", films.len());
|
|
|
|
Ok(())
|
|
}
|