use reqwest::blocking::Client; use scraper::{Html, Selector}; use serde::{Deserialize, Serialize}; use std::fs; const BASE: &str = "https://letterboxd.com"; const LIST: &str = "https://letterboxd.com/mimuuu/list/2026/by/reverse/"; #[derive(Deserialize)] struct FilmJson { name: String, released: Released, directors: Vec, } #[derive(Deserialize)] struct Released { year: u16, } #[derive(Deserialize)] struct Person { name: String, } #[derive(Serialize)] struct Film { title: String, year: u16, director: String, url: String, } fn main() -> Result<(), Box> { let client = Client::builder().user_agent("Mozilla/5.0").build()?; // One request: list page (only to discover the 5 film endpoints) let html = client.get(LIST).send()?.text()?; let doc = Html::parse_document(&html); let selector = Selector::parse("li.posteritem div.react-component")?; let mut films = Vec::new(); for node in doc.select(&selector).take(5) { let details = node.value().attr("data-details-endpoint").unwrap(); let slug = node.value().attr("data-item-link").unwrap(); // One request per film: JSON metadata let film: FilmJson = client.get(format!("{BASE}{details}")).send()?.json()?; films.push(Film { title: film.name, year: film.released.year, director: film .directors .first() .map(|d| d.name.clone()) .unwrap_or_default(), url: format!("{BASE}{slug}"), }); } fs::create_dir_all("data")?; fs::write("data/films.json", serde_json::to_string_pretty(&films)?)?; println!("Saved {} films", films.len()); Ok(()) }