"""
# 处理失败ID错误信息
if report_data["failed_ids"]:
html += """
⚠️ 请求失败的平台
"""
for id_value in report_data["failed_ids"]:
html += f'{html_escape(id_value)} '
html += """
"""
# 生成热点词汇统计部分的HTML
stats_html = ""
tab_bar_html = ""
if report_data["stats"]:
total_count = len(report_data["stats"])
# 生成 Tab 栏 HTML
tab_bar_html = '
'
for tab_i, tab_stat in enumerate(report_data["stats"]):
escaped_tab_word = html_escape(tab_stat["word"])
tab_count = tab_stat["count"]
tab_bar_html += f'{escaped_tab_word}{tab_count} '
tab_bar_html += '全部 '
tab_bar_html += '
'
for i, stat in enumerate(report_data["stats"], 1):
count = stat["count"]
# 确定热度等级
if count >= 10:
count_class = "hot"
elif count >= 5:
count_class = "warm"
else:
count_class = ""
escaped_word = html_escape(stat["word"])
stats_html += f"""
"""
# 处理每个词组下的新闻标题,给每条新闻标上序号
for j, title_data in enumerate(stat["titles"], 1):
is_new = title_data.get("is_new", False)
new_class = "new" if is_new else ""
stats_html += f"""
{j}
"""
# 处理标题和链接
escaped_title = html_escape(title_data["title"])
link_url = title_data.get("mobile_url") or title_data.get("url", "")
if link_url:
escaped_url = html_escape(link_url)
stats_html += f'
{escaped_title} '
else:
stats_html += escaped_title
stats_html += """
"""
stats_html += """
"""
# 给热榜统计添加外层包装
if stats_html:
stats_html = f"""
{tab_bar_html}{stats_html}
"""
# 生成新增新闻区域的HTML
new_titles_html = ""
if show_new_section and report_data["new_titles"]:
new_titles_html += f"""
本次新增热点 (共 {report_data['total_new_count']} 条)
"""
for source_data in report_data["new_titles"]:
escaped_source = html_escape(source_data["source_name"])
titles_count = len(source_data["titles"])
new_titles_html += f"""
{escaped_source} · {titles_count}条
"""
# 为新增新闻也添加序号
for idx, title_data in enumerate(source_data["titles"], 1):
ranks = title_data.get("ranks", [])
# 处理新增新闻的排名显示
rank_class = ""
if ranks:
min_rank = min(ranks)
if min_rank <= 3:
rank_class = "top"
elif min_rank <= title_data.get("rank_threshold", 10):
rank_class = "high"
if len(ranks) == 1:
rank_text = str(ranks[0])
else:
rank_text = f"{min(ranks)}-{max(ranks)}"
else:
rank_text = "?"
new_titles_html += f"""
{idx}
{rank_text}
"""
# 处理新增新闻的链接
escaped_title = html_escape(title_data["title"])
link_url = title_data.get("mobile_url") or title_data.get("url", "")
if link_url:
escaped_url = html_escape(link_url)
new_titles_html += f'
{escaped_title} '
else:
new_titles_html += escaped_title
new_titles_html += """
"""
new_titles_html += """
"""
new_titles_html += """
"""
# 生成 RSS 统计内容
def render_rss_stats_html(stats: List[Dict], title: str = "RSS 订阅更新") -> str:
"""渲染 RSS 统计区块 HTML
Args:
stats: RSS 分组统计列表,格式与热榜一致:
[
{
"word": "关键词",
"count": 5,
"titles": [
{
"title": "标题",
"source_name": "Feed 名称",
"time_display": "12-29 08:20",
"url": "...",
"is_new": True/False
}
]
}
]
title: 区块标题
Returns:
渲染后的 HTML 字符串
"""
if not stats:
return ""
# 计算总条目数
total_count = sum(stat.get("count", 0) for stat in stats)
if total_count == 0:
return ""
rss_html = f"""
"""
return rss_html
# 生成独立展示区内容
def render_standalone_html(data: Optional[Dict]) -> str:
"""渲染独立展示区 HTML(复用热点词汇统计区样式)
Args:
data: 独立展示数据,格式:
{
"platforms": [
{
"id": "zhihu",
"name": "知乎热榜",
"items": [
{
"title": "标题",
"url": "链接",
"rank": 1,
"ranks": [1, 2, 1],
"first_time": "08:00",
"last_time": "12:30",
"count": 3,
}
]
}
],
"rss_feeds": [
{
"id": "hacker-news",
"name": "Hacker News",
"items": [
{
"title": "标题",
"url": "链接",
"published_at": "2025-01-07T08:00:00",
"author": "作者",
}
]
}
]
}
Returns:
渲染后的 HTML 字符串
"""
if not data:
return ""
platforms = data.get("platforms", [])
rss_feeds = data.get("rss_feeds", [])
if not platforms and not rss_feeds:
return ""
# 计算总条目数
total_platform_items = sum(len(p.get("items", [])) for p in platforms)
total_rss_items = sum(len(f.get("items", [])) for f in rss_feeds)
total_count = total_platform_items + total_rss_items
if total_count == 0:
return ""
# 收集所有分组信息用于生成 tab
all_groups = []
for p in platforms:
items = p.get("items", [])
if items:
all_groups.append({"name": p.get("name", p.get("id", "")), "count": len(items)})
for f in rss_feeds:
items = f.get("items", [])
if items:
all_groups.append({"name": f.get("name", f.get("id", "")), "count": len(items)})
standalone_html = f"""
"""
# 生成 tab 栏(2+ 分组时)
if len(all_groups) >= 2:
standalone_html += """
"""
for idx, g in enumerate(all_groups):
active = ' active' if idx == 0 else ''
standalone_html += f"""
{html_escape(g["name"])}{g["count"]} """
standalone_html += f"""
全部{total_count}
"""
standalone_html += """
"""
group_idx = 0
# 渲染热榜平台(复用 word-group 结构)
for platform in platforms:
platform_name = platform.get("name", platform.get("id", ""))
items = platform.get("items", [])
if not items:
continue
standalone_html += f"""
{html_escape(platform_name)}
{len(items)} 条
"""
# 渲染每个条目(复用 news-item 结构)
for j, item in enumerate(items, 1):
title = item.get("title", "")
url = item.get("url", "") or item.get("mobileUrl", "")
rank = item.get("rank", 0)
ranks = item.get("ranks", [])
first_time = item.get("first_time", "")
last_time = item.get("last_time", "")
count = item.get("count", 1)
standalone_html += f"""
{j}
"""
# 标题和链接(复用 news-link 样式)
escaped_title = html_escape(title)
if url:
escaped_url = html_escape(url)
standalone_html += f'
{escaped_title} '
else:
standalone_html += escaped_title
standalone_html += """
"""
standalone_html += """
"""
group_idx += 1
# 渲染 RSS 源(复用相同结构)
for feed in rss_feeds:
feed_name = feed.get("name", feed.get("id", ""))
items = feed.get("items", [])
if not items:
continue
standalone_html += f"""
{html_escape(feed_name)}
{len(items)} 条
"""
for j, item in enumerate(items, 1):
title = item.get("title", "")
url = item.get("url", "")
published_at = item.get("published_at", "")
author = item.get("author", "")
standalone_html += f"""
{j}
"""
escaped_title = html_escape(title)
if url:
escaped_url = html_escape(url)
standalone_html += f'
{escaped_title} '
else:
standalone_html += escaped_title
standalone_html += """
"""
standalone_html += """
"""
group_idx += 1
standalone_html += """
"""
return standalone_html
# 生成 RSS 统计和新增 HTML
rss_stats_html = render_rss_stats_html(rss_items, "RSS 订阅更新") if rss_items else ""
rss_new_html = render_rss_stats_html(rss_new_items, "RSS 新增更新") if rss_new_items else ""
# 生成独立展示区 HTML
standalone_html = render_standalone_html(standalone_data)
# 生成 AI 分析 HTML
ai_html = render_ai_analysis_html_rich(ai_analysis) if ai_analysis else ""
# 准备各区域内容映射
region_contents = {
"hotlist": stats_html,
"rss": rss_stats_html,
"new_items": (new_titles_html, rss_new_html), # 元组,分别处理
"standalone": standalone_html,
"ai_analysis": ai_html,
}
def add_section_divider(content: str) -> str:
"""为内容的外层 div 添加 section-divider 类"""
if not content or 'class="' not in content:
return content
first_class_pos = content.find('class="')
if first_class_pos != -1:
insert_pos = first_class_pos + len('class="')
return content[:insert_pos] + "section-divider " + content[insert_pos:]
return content
# 按 region_order 顺序组装内容,动态添加分割线
has_previous_content = False
for region in region_order:
content = region_contents.get(region, "")
if region == "new_items":
# 特殊处理 new_items 区域(包含热榜新增和 RSS 新增两部分)
new_html, rss_new = content
if new_html:
if has_previous_content:
new_html = add_section_divider(new_html)
html += new_html
has_previous_content = True
if rss_new:
if has_previous_content:
rss_new = add_section_divider(rss_new)
html += rss_new
has_previous_content = True
elif content:
if has_previous_content:
content = add_section_divider(content)
html += content
has_previous_content = True
html += """