fix(anime): parse otakudesu search results from .chivsrc li
The search parser targeted the index layout (.venz ul li, .thumbz h2.jdlflm, img, .epz, genre-tag/status/rating classes) but otakudesu search results live in <ul class=chivsrc><li> with <h2><a>Title</a></h2> plus .set label/value rows. Search silently returned 0 items. Rewrote to parse .chivsrc li, extracting title + url from h2 a and Status/Rating/Genre from .set rows. No poster on the search page (empty).
This commit is contained in:
@@ -410,6 +410,16 @@ pub fn parse_latest_anime_document(
|
|||||||
// SEARCH ANIME PAGE
|
// SEARCH ANIME PAGE
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
/// Normalise scraped text: collapse newlines/tabs into single spaces and trim.
|
||||||
|
fn clean_text(text: String) -> String {
|
||||||
|
text.replace(['\n', '\t'], " ")
|
||||||
|
.split_whitespace()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ")
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_search_anime_document(
|
pub fn parse_search_anime_document(
|
||||||
html: &str,
|
html: &str,
|
||||||
page: &str,
|
page: &str,
|
||||||
@@ -417,44 +427,74 @@ pub fn parse_search_anime_document(
|
|||||||
let document = parse_html(html);
|
let document = parse_html(html);
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
|
|
||||||
let venz_sel = selector(".venz ul li")
|
// Otakudesu search results live under `<ul class="chivsrc"><li>...`, one
|
||||||
.ok_or_else(|| ScrapingError::Parse("Failed to parse .venz ul li selector".into()))?;
|
// result per `<li>` with `<h2><a href="...">Title</a></h2>` plus a series
|
||||||
let title_sel = selector(".thumbz h2.jdlflm")
|
// of `<div class="set"><b>Label</b> : value</div>` rows (Status, Rating,
|
||||||
.ok_or_else(|| ScrapingError::Parse("Failed to parse title selector".into()))?;
|
// Genre...). The old parser targeted the index layout (`.venz ul li`,
|
||||||
let link_sel = selector("a")
|
// `.thumbz h2.jdlflm`, `img`, `.epz`) which does NOT appear on the search
|
||||||
.ok_or_else(|| ScrapingError::Parse("Failed to parse link selector".into()))?;
|
// page, so search silently returned 0 items.
|
||||||
let img_sel = selector("img")
|
let item_sel = selector(".chivsrc li")
|
||||||
.ok_or_else(|| ScrapingError::Parse("Failed to parse img selector".into()))?;
|
.ok_or_else(|| ScrapingError::Parse("Failed to parse .chivsrc li selector".into()))?;
|
||||||
let ep_sel = selector(".epz")
|
let title_link_sel = selector("h2 a")
|
||||||
.ok_or_else(|| ScrapingError::Parse("Failed to parse epz selector".into()))?;
|
.ok_or_else(|| ScrapingError::Parse("Failed to parse h2 a selector".into()))?;
|
||||||
let genre_sel = selector(".genre-tag")
|
let set_row_sel = selector(".set")
|
||||||
.ok_or_else(|| ScrapingError::Parse("Failed to parse genre-tag selector".into()))?;
|
.ok_or_else(|| ScrapingError::Parse("Failed to parse .set selector".into()))?;
|
||||||
let status_sel = selector(".status")
|
|
||||||
.ok_or_else(|| ScrapingError::Parse("Failed to parse status selector".into()))?;
|
|
||||||
let rating_sel = selector(".rating")
|
|
||||||
.ok_or_else(|| ScrapingError::Parse("Failed to parse rating selector".into()))?;
|
|
||||||
|
|
||||||
for element in document.select(&venz_sel) {
|
let status_sel = selector(".set b:first-child, .set b")
|
||||||
let title = text_from_or(&element, &title_sel, "");
|
.ok_or_else(|| ScrapingError::Parse("Failed to parse status label selector".into()))?;
|
||||||
let anime_url = attr_from_or(&element, &link_sel, "href", "");
|
|
||||||
|
for element in document.select(&item_sel) {
|
||||||
|
let title = text_from_or(&element, &title_link_sel, "");
|
||||||
|
let anime_url = attr_from_or(&element, &title_link_sel, "href", "");
|
||||||
let slug = extract_slug(&anime_url);
|
let slug = extract_slug(&anime_url);
|
||||||
let poster = attr_from_or(&element, &img_sel, "src", "");
|
|
||||||
let episode = text_from_or(&element, &ep_sel, "N/A");
|
|
||||||
|
|
||||||
let mut genres = Vec::new();
|
if title.is_empty() {
|
||||||
for genre_elem in element.select(&genre_sel) {
|
continue;
|
||||||
genres.push(text(&genre_elem));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let status = text_from_or(&element, &status_sel, "");
|
// Extract label → value pairs from each `.set` row (e.g. "Status : Ongoing",
|
||||||
let rating = text_from_or(&element, &rating_sel, "");
|
// "Rating : 8.0", "Genre : Action, Drama"). Falls back gracefully when the
|
||||||
|
// value is blank or the row label is absent.
|
||||||
|
let mut status = String::new();
|
||||||
|
let mut rating = String::new();
|
||||||
|
let mut genres = Vec::new();
|
||||||
|
for row in element.select(&set_row_sel) {
|
||||||
|
let row_text = text(&row);
|
||||||
|
let lower = row_text.to_lowercase();
|
||||||
|
if lower.contains("status") {
|
||||||
|
if let Some(label) = row.select(&status_sel).next() {
|
||||||
|
let label_len = text(&label).len();
|
||||||
|
status = clean_text(row_text[label_len..].to_string());
|
||||||
|
} else {
|
||||||
|
status = clean_text(row_text.trim_start_matches("Status").to_string());
|
||||||
|
}
|
||||||
|
} else if lower.contains("rating") {
|
||||||
|
let label_len = row
|
||||||
|
.select(&status_sel)
|
||||||
|
.next()
|
||||||
|
.map(|label| text(&label).len())
|
||||||
|
.unwrap_or("Rating".len());
|
||||||
|
rating = clean_text(row_text[label_len..].to_string());
|
||||||
|
} else if lower.contains("genre") {
|
||||||
|
let label_len = row
|
||||||
|
.select(&status_sel)
|
||||||
|
.next()
|
||||||
|
.map(|label| text(&label).len())
|
||||||
|
.unwrap_or("Genre".len());
|
||||||
|
let raw = clean_text(row_text[label_len..].to_string());
|
||||||
|
genres = raw
|
||||||
|
.split(',')
|
||||||
|
.map(|g| g.trim().to_string())
|
||||||
|
.filter(|g| !g.is_empty())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !title.is_empty() {
|
|
||||||
items.push(SearchAnimeItem {
|
items.push(SearchAnimeItem {
|
||||||
title,
|
title,
|
||||||
slug,
|
slug,
|
||||||
poster,
|
poster: String::new(),
|
||||||
episode,
|
episode: String::new(),
|
||||||
anime_url,
|
anime_url,
|
||||||
genres,
|
genres,
|
||||||
status,
|
status,
|
||||||
@@ -464,7 +504,6 @@ pub fn parse_search_anime_document(
|
|||||||
season: String::new(),
|
season: String::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let pagination = parse_pagination(page, &document)?;
|
let pagination = parse_pagination(page, &document)?;
|
||||||
Ok((items, pagination))
|
Ok((items, pagination))
|
||||||
|
|||||||
Reference in New Issue
Block a user