fix(anime): repair genre & latest endpoints for the new otakudesu layout
Deploy Scraper / build-and-deploy (push) Canceled after 0s
Deploy Scraper / build-and-deploy (push) Canceled after 0s
The current otakudesu site changed paths and card layout, breaking two APIs:
1. genre pages: singular /genre/{slug}/page/{n}/ 301-redirects to a dead
otakudesu.io placeholder → 0 items. Fix: plural /genres/{slug}/page/{n}/.
Also re-target parse_genre_anime_document from the retired .venz ul li /
.thumbz h2.jdlflm layout to the new .col-anime card layout
(.col-anime-title a, .col-anime-eps, .col-anime-rating, .col-anime-cover img).
2. latest: /latest-anime/ was removed (301 → otakudesu.io). The homepage IS
the latest-episodes feed (.venz ul li with .thumbz h2.jdlflm/.epz), so
fetch_latest_anime_page now uses base_url() for page 1 and /page/N/ for
later pages instead of the dead path.
This commit is contained in:
@@ -56,7 +56,10 @@ impl OtakudesuRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn genre_page_url(&self, genre_slug: &str, page: &str) -> String {
|
pub fn genre_page_url(&self, genre_slug: &str, page: &str) -> String {
|
||||||
format!("{}/genre/{}/page/{}/", self.base_url(), genre_slug, page)
|
// Current otakudesu uses the plural `/genres/{slug}/` path (archive
|
||||||
|
// layout). The singular `/genre/{slug}/page/{page}/` path 301-redirects
|
||||||
|
// to otakudesu.io (a 404 placeholder), which made genre pages return 0.
|
||||||
|
format!("{}/genres/{}/page/{}/", self.base_url(), genre_slug, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn full_episode_url(&self, slug: &str) -> String {
|
pub fn full_episode_url(&self, slug: &str) -> String {
|
||||||
@@ -144,7 +147,14 @@ impl OtakudesuRepository {
|
|||||||
&self,
|
&self,
|
||||||
slug: &str,
|
slug: &str,
|
||||||
) -> Result<(Vec<LatestAnimeItem>, Pagination), ScrapingError> {
|
) -> Result<(Vec<LatestAnimeItem>, Pagination), ScrapingError> {
|
||||||
let url = self.page_url("latest-anime", slug);
|
// The site's `/latest-anime/` path was removed (301 → otakudesu.io
|
||||||
|
// placeholder). The homepage IS the latest-episodes page (`.venz ul li`
|
||||||
|
// with `.thumbz h2.jdlflm`, `.epz`), paginated via WordPress `/page/N/`.
|
||||||
|
let url = if slug == "1" {
|
||||||
|
format!("{}/", self.base_url())
|
||||||
|
} else {
|
||||||
|
format!("{}/page/{}/", self.base_url(), slug)
|
||||||
|
};
|
||||||
let html = self.fetch_html(&url).await?;
|
let html = self.fetch_html(&url).await?;
|
||||||
let slug_owned = slug.to_string();
|
let slug_owned = slug.to_string();
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
|
|||||||
@@ -520,23 +520,29 @@ pub fn parse_genre_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")
|
// Current otakudesu genre archive pages use the `.col-anime` card layout
|
||||||
.ok_or_else(|| ScrapingError::Parse("Failed to parse .venz ul li selector".into()))?;
|
// (`.col-md-4 .col-anime`), each with `.col-anime-title a`, `.col-anime-eps`,
|
||||||
let title_sel = selector(".thumbz h2.jdlflm")
|
// `.col-anime-rating`, `.col-anime-cover img` and a synopsis. The old
|
||||||
|
// `.venz ul li` / `.thumbz h2.jdlflm` / `.epz` layout no longer appears on
|
||||||
|
// these pages, which made genre filtering silently return 0 items.
|
||||||
|
let card_sel = selector(".col-anime")
|
||||||
|
.ok_or_else(|| ScrapingError::Parse("Failed to parse .col-anime selector".into()))?;
|
||||||
|
let title_sel = selector(".col-anime-title a")
|
||||||
.ok_or_else(|| ScrapingError::Parse("Failed to parse title selector".into()))?;
|
.ok_or_else(|| ScrapingError::Parse("Failed to parse title selector".into()))?;
|
||||||
let link_sel = selector("a")
|
let eps_sel = selector(".col-anime-eps")
|
||||||
.ok_or_else(|| ScrapingError::Parse("Failed to parse link selector".into()))?;
|
.ok_or_else(|| ScrapingError::Parse("Failed to parse eps selector".into()))?;
|
||||||
let img_sel = selector("img")
|
let rating_sel = selector(".col-anime-rating")
|
||||||
|
.ok_or_else(|| ScrapingError::Parse("Failed to parse rating selector".into()))?;
|
||||||
|
let img_sel = selector(".col-anime-cover img")
|
||||||
.ok_or_else(|| ScrapingError::Parse("Failed to parse img selector".into()))?;
|
.ok_or_else(|| ScrapingError::Parse("Failed to parse img selector".into()))?;
|
||||||
let ep_sel = selector(".epz")
|
|
||||||
.ok_or_else(|| ScrapingError::Parse("Failed to parse epz selector".into()))?;
|
|
||||||
|
|
||||||
for element in document.select(&venz_sel) {
|
for element in document.select(&card_sel) {
|
||||||
let title = text_from_or(&element, &title_sel, "");
|
let title = text_from_or(&element, &title_sel, "");
|
||||||
let anime_url = attr_from_or(&element, &link_sel, "href", "");
|
let anime_url = attr_from_or(&element, &title_sel, "href", "");
|
||||||
let slug = extract_slug(&anime_url);
|
let slug = extract_slug(&anime_url);
|
||||||
let poster = attr_from_or(&element, &img_sel, "src", "");
|
let poster = attr_from_or(&element, &img_sel, "src", "");
|
||||||
let episode = text_from_or(&element, &ep_sel, "N/A");
|
let episode = text_from_or(&element, &eps_sel, "N/A");
|
||||||
|
let score = text_from_or(&element, &rating_sel, "");
|
||||||
|
|
||||||
if !title.is_empty() {
|
if !title.is_empty() {
|
||||||
items.push(GenreAnimeItem {
|
items.push(GenreAnimeItem {
|
||||||
@@ -544,7 +550,7 @@ pub fn parse_genre_anime_document(
|
|||||||
slug,
|
slug,
|
||||||
poster,
|
poster,
|
||||||
episode,
|
episode,
|
||||||
score: String::new(),
|
score,
|
||||||
status: String::new(),
|
status: String::new(),
|
||||||
anime_url,
|
anime_url,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user