Update app.py
Browse files
app.py
CHANGED
|
@@ -64,9 +64,15 @@ def extract_synopsis(description: str) -> str:
|
|
| 64 |
|
| 65 |
|
| 66 |
def extract_title_and_description(html: str) -> tuple[str, str]:
|
| 67 |
-
"""Pull the best available title and synopsis/description from a page's HTML.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
soup = BeautifulSoup(html, "html.parser")
|
| 69 |
|
|
|
|
| 70 |
title = ""
|
| 71 |
og_title = soup.find("meta", property="og:title")
|
| 72 |
if og_title and og_title.get("content"):
|
|
@@ -74,20 +80,47 @@ def extract_title_and_description(html: str) -> tuple[str, str]:
|
|
| 74 |
if not title and soup.title and soup.title.string:
|
| 75 |
title = soup.title.string.strip()
|
| 76 |
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
og_desc = soup.find("meta", property="og:description")
|
| 83 |
if og_desc and og_desc.get("content"):
|
| 84 |
-
|
| 85 |
|
| 86 |
-
#
|
| 87 |
-
title = re.sub(r"\
|
| 88 |
-
|
| 89 |
|
| 90 |
-
synopsis = extract_synopsis(description)
|
| 91 |
return title, synopsis
|
| 92 |
|
| 93 |
|
|
|
|
| 64 |
|
| 65 |
|
| 66 |
def extract_title_and_description(html: str) -> tuple[str, str]:
|
| 67 |
+
"""Pull the best available title and synopsis/description from a page's HTML.
|
| 68 |
+
|
| 69 |
+
For WebNovel pages the meta description is deliberately truncated with '...'.
|
| 70 |
+
The full synopsis lives inside a <div class="... j_synopsis ..."> in the body.
|
| 71 |
+
We prefer that element; the meta tags are only a fallback for other sites.
|
| 72 |
+
"""
|
| 73 |
soup = BeautifulSoup(html, "html.parser")
|
| 74 |
|
| 75 |
+
# --- Title ---
|
| 76 |
title = ""
|
| 77 |
og_title = soup.find("meta", property="og:title")
|
| 78 |
if og_title and og_title.get("content"):
|
|
|
|
| 80 |
if not title and soup.title and soup.title.string:
|
| 81 |
title = soup.title.string.strip()
|
| 82 |
|
| 83 |
+
# --- Synopsis: try full body element first (WebNovel and similar sites) ---
|
| 84 |
+
synopsis = ""
|
| 85 |
+
|
| 86 |
+
# 1. WebNovel: <div class="... j_synopsis ...">
|
| 87 |
+
j_syn = soup.find(class_=lambda c: c and "j_synopsis" in c.split())
|
| 88 |
+
if j_syn:
|
| 89 |
+
# Replace <br> tags with newlines before extracting text
|
| 90 |
+
for br in j_syn.find_all("br"):
|
| 91 |
+
br.replace_with("\n")
|
| 92 |
+
# Remove the "read more" toggle span that WebNovel appends
|
| 93 |
+
for rm in j_syn.find_all("span", class_="_readmore"):
|
| 94 |
+
rm.decompose()
|
| 95 |
+
synopsis = j_syn.get_text(separator=" ").strip()
|
| 96 |
+
|
| 97 |
+
# 2. Fallback: any element whose class contains common synopsis keywords
|
| 98 |
+
if not synopsis:
|
| 99 |
+
for cls_hint in ("book-intro", "book_intro", "bookIntro",
|
| 100 |
+
"detail-desc", "book-desc", "bookDesc",
|
| 101 |
+
"synopsis", "j_intro"):
|
| 102 |
+
el = soup.find(class_=lambda c: c and cls_hint in c)
|
| 103 |
+
if el:
|
| 104 |
+
for br in el.find_all("br"):
|
| 105 |
+
br.replace_with("\n")
|
| 106 |
+
synopsis = el.get_text(separator=" ").strip()
|
| 107 |
+
if synopsis:
|
| 108 |
+
break
|
| 109 |
+
|
| 110 |
+
# 3. Last resort: meta tags (always truncated on WebNovel, but better than nothing)
|
| 111 |
+
if not synopsis:
|
| 112 |
+
meta_desc = soup.find("meta", attrs={"name": "description"})
|
| 113 |
+
if meta_desc and meta_desc.get("content"):
|
| 114 |
+
synopsis = extract_synopsis(meta_desc["content"].strip())
|
| 115 |
+
if not synopsis:
|
| 116 |
og_desc = soup.find("meta", property="og:description")
|
| 117 |
if og_desc and og_desc.get("content"):
|
| 118 |
+
synopsis = extract_synopsis(og_desc["content"].strip())
|
| 119 |
|
| 120 |
+
# Normalise whitespace: collapse runs of spaces/tabs but keep newlines
|
| 121 |
+
title = re.sub(r"[ \t]+", " ", title).strip()
|
| 122 |
+
synopsis = re.sub(r"[ \t]+", " ", synopsis).strip()
|
| 123 |
|
|
|
|
| 124 |
return title, synopsis
|
| 125 |
|
| 126 |
|