
本文介绍如何通过 Python 爬取 MyAnimeList 网站顶部动漫榜单页面,并逐个请求动态生成的悬停框(hoverbox)HTML,从中精准提取“Genres”等结构化字段,适用于需获取非主页面直显数据的 Web 抓取场景。
本文介绍如何通过 python 爬取 myanimelist 网站顶部动漫榜单页面,并逐个请求动态生成的悬停框(hoverbox)html,从中精准提取“genres”等结构化字段,适用于需获取非主页面直显数据的场景。
MyAnimeList 的悬停框(hoverbox)并非内嵌在主页面 HTML 中,而是通过 JavaScript 异步加载的独立资源——每个动漫条目对应一个专属的 /anime/{id}/hover 接口。因此,仅解析 topanime.php 页面无法获取悬停内容,必须额外发起 HTTP 请求获取 hoverbox 的原始 HTML。
以下是一个完整、可运行的教程式实现:
import requests
from bs4 import BeautifulSoup
# 主榜单页面 URL
url = "https://myanimelist.net/topanime.php"
# 悬停框模板 URL({id} 将被替换为实际 anime ID)
hover_url = "https://myanimelist.net/anime/{}/hover"
# 获取主页面 HTML
response = requests.get(url, headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"})
response.raise_for_status()
soup = BeautifulSoup(response.content, "html.parser")
# 提取所有带 id 属性的标题链接(如 <a id="area12345" href="...">)
anime_entries = soup.select("h3 > a[id]")
all_genres = []
for a in anime_entries[:5]: # 示例:仅处理前 5 个以加快调试(可移除切片)
title = a.get_text(strip=True)
# 解析 ID:链接 id 格式通常为 "area12345",需去除前缀 "area"
anime_id = a["id"].replace("area", "")
try:
# 请求对应悬停框 HTML
hover_response = requests.get(hover_url.format(anime_id),
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"})
hover_response.raise_for_status()
soup2 = BeautifulSoup(hover_response.content, "html.parser")
# 查找所有 dark_text 标签(如 "Genres:", "Type:", "Score:" 等)
for span in soup2.select("span.dark_text"):
label = span.get_text(strip=True)
# 获取紧邻的下一个文本节点(即标签后的纯文本内容)
next_sibling = span.next_sibling
if next_sibling and next_sibling.strip():
value = next_sibling.strip()
else:
# 若 next_sibling 为空,尝试找 next_element(兼容换行/空格干扰)
next_elem = span.next_element
while next_elem and (isinstance(next_elem, str) and not next_elem.strip()):
next_elem = next_elem.next_element
value = next_elem.strip() if next_elem and isinstance(next_elem, str) else ""
if label == "Genres:":
# 清洗并标准化分类字符串(去多余空格、分割为列表)
genres = [g.strip() for g in value.split(",") if g.strip()]
all_genres.append({"title": title, "genres": genres})
print(f"{title} → Genres: {genres}")
except requests.exceptions.RequestException as e:
print(f"⚠️ 获取 {title} (ID: {anime_id}) 的悬停框失败:{e}")
continue
print(f"\n✅ 总共成功提取 {len(all_genres)} 部动漫的分类信息。")</a>
关键要点说明:
- ✅ ID 提取逻辑:MyAnimeList 在
标签中使用id="area{number}"标识每部动漫,需用.replace("area", "")或.removeprefix("area")(Python ≥3.9)安全提取数字 ID。 - ✅ 悬停框是独立接口:
/anime/{id}/hover返回轻量级 HTML 片段,不含完整页面结构,适合快速解析。 - ✅ 精准定位值内容:
Genres: Action, Adventure, Fantasy中,分类文本是的下一个兄弟文本节点(next_sibling),而非子节点;find_next_sibling(string=True)是更鲁棒的写法。 - ⚠️ 反爬注意事项:
- 务必添加
User-Agent请求头,否则可能返回 403; - 建议添加
time.sleep(1)避免高频请求触发封禁; - 生产环境应加入异常重试、会话复用(
requests.Session())及状态码校验。
- 务必添加
总结:悬停内容属于动态加载的辅助数据,必须识别其真实 API 路径并单独抓取。本方案不依赖 Selenium,纯 requests + BeautifulSoup 即可高效、稳定地结构化提取 Genres 等关键元数据,是处理类似“hover tooltip 数据”的典型范式。










