Ver código fonte

v3.2.0: 新增关键词排序优先级配置,显示数量控制

sansan 5 meses atrás
pai
commit
e31a20a13a
8 arquivos alterados com 328 adições e 55 exclusões
  1. 131 17
      README-EN.md
  2. 140 34
      README.md
  3. 2 0
      config/config.yaml
  4. 4 0
      docker/.env
  5. 2 0
      docker/docker-compose-build.yml
  6. 2 0
      docker/docker-compose.yml
  7. 46 3
      main.py
  8. 1 1
      version

+ 131 - 17
README-EN.md

@@ -1,7 +1,7 @@
 <div align="center" id="trendradar">
 
 <a href="https://github.com/sansan0/TrendRadar" title="TrendRadar">
-  <img src="/_image/banner.webp" alt="TrendRadar Banner" width="80%">
+  <img src="/_image/banner.webp" alt="TrendRadar Banner" width="90%">
 </a>
 
 🚀 Deploy in <strong>30 seconds</strong> — Your Smart Trending News Assistant
@@ -13,7 +13,7 @@
 [![GitHub Stars](https://img.shields.io/github/stars/sansan0/TrendRadar?style=flat-square&logo=github&color=yellow)](https://github.com/sansan0/TrendRadar/stargazers)
 [![GitHub Forks](https://img.shields.io/github/forks/sansan0/TrendRadar?style=flat-square&logo=github&color=blue)](https://github.com/sansan0/TrendRadar/network/members)
 [![License](https://img.shields.io/badge/license-GPL--3.0-blue.svg?style=flat-square)](LICENSE)
-[![Version](https://img.shields.io/badge/version-v3.1.1-blue.svg)](https://github.com/sansan0/TrendRadar)
+[![Version](https://img.shields.io/badge/version-v3.2.0-blue.svg)](https://github.com/sansan0/TrendRadar)
 [![MCP](https://img.shields.io/badge/MCP-v1.0.2-green.svg)](https://github.com/sansan0/TrendRadar)
 
 [![WeWork](https://img.shields.io/badge/WeWork-Notification-00D4AA?style=flat-square)](https://work.weixin.qq.com/)
@@ -45,10 +45,9 @@
 
 <div align="center">
 
-| [🎯 Core Features](#-core-features) | [🚀 Quick Start](#-quick-start) | [⚙️ Configuration Guide](#-configuration-guide) | [🐳 Docker Deployment](#-docker-deployment) |
-|:---:|:---:|:---:|:---:|
-| [🤖 AI Analysis](#-ai-analysis) | [🔌 MCP Clients](#-mcp-clients) | [📝 Changelog](#-changelog) | [❓ FAQ & Support](#-faq--support) |
-| [⭐ Related Projects](#-related-projects) | [🪄 Sponsors](#-sponsors) | | |
+| [🚀 Quick Start](#-quick-start) | [🤖 AI Analysis](#-ai-analysis) | [⚙️ Configuration Guide](#configuration-guide) | [📝 Changelog](#-changelog) | [❓ FAQ & Support](#-faq--support) |
+|:---:|:---:|:---:|:---:|:---:|
+| [🐳 Docker Deployment](#-docker-deployment) | [🔌 MCP Clients](#-mcp-clients) | [⭐ Related Projects](#-related-projects) | [🪄 Sponsors](#-sponsors) | |
 
 </div>
 
@@ -162,10 +161,23 @@ Default monitoring of 11 mainstream platforms, with support for adding custom pl
 
 Set personal keywords (e.g., AI, BYD, Education Policy) to receive only relevant trending news, filtering out noise.
 
-- Supports normal words, required words (+), and filter words (!)
-- Group-based management with independent statistics for different topics
+**Basic Syntax** (4 types):
+- Normal words: Basic matching
+- Required words `+`: Narrow scope
+- Filter words `!`: Exclude noise
+- Count limit `@`: Control display count (v3.2.0 new)
 
-> 💡 Keyword configuration tutorial: [Configuration Guide - Keyword Configuration](#2-keyword-configuration)
+**Advanced Features** (v3.2.0 new):
+- 🔢 **Keyword Sorting Control**: Sort by popularity or config order
+- 📊 **Display Count Limit**: Global config + individual override for flexible control
+
+**Group-based Management**:
+- Separate with blank lines, independent statistics for different topics
+
+> 💡 **Basic Configuration**: [Keyword Configuration - Basic Syntax](#keyword-basic-syntax)
+>
+> 💡 **Advanced Configuration**: [Keyword Configuration - Advanced Settings](#keyword-advanced-settings)
+>
 > 💡 You can also skip filtering and receive all trending news (leave frequency_words.txt empty)
 
 
@@ -251,6 +263,30 @@ Transform from "algorithm recommendation captivity" to "actively getting the inf
 - **Major Version Upgrade**: Upgrading from v1.x to v2.y, recommend deleting existing fork and re-forking to save effort and avoid config conflicts
 
 
+### 2025/11/23 - v3.2.0
+
+**🎯 New Advanced Customization Features**
+
+1. **Keyword Sorting Priority Configuration**
+   - Two sorting strategies: Popularity first vs Config order first
+   - For different use cases: Hot topic tracking or personalized focus
+
+2. **Display Count Precise Control**
+   - Global config: Unified limit for all keywords
+   - Individual config: Use `@number` syntax to set specific limits
+   - Effectively control push length, highlight key content
+
+> 📖 **Detailed Tutorial**: [Keyword Configuration - Advanced Settings](#keyword-advanced-settings)
+
+**🔧 Upgrade Instructions**:
+- **GitHub Fork Users**: Update `main.py`, `config/config.yaml`
+
+### 2025/11/18 - mcp-v1.0.2
+
+  **MCP Module Update:**
+  - Fix issue where today's news query may return articles from past dates
+
+
 ### 2025/11/22 - v3.1.1
 
 - **Fixed data anomaly crash issue**: Resolved `'float' object has no attribute 'lower'` error encountered by some users in GitHub Actions environment
@@ -1076,6 +1112,8 @@ frequency_words.txt file added **required word** feature, using + sign
    👉 **Learn More**: [AI Analysis](#-ai-analysis) — Unlock hidden capabilities and make trend tracking more efficient!
 
 
+<a name="configuration-guide"></a>
+
 ## ⚙️ Configuration Guide
 
 > **📖 Reminder**: This chapter provides detailed configuration explanations. Suggest completing [Quick Start](#-quick-start) basic configuration first, then refer to detailed options here as needed.
@@ -1106,21 +1144,22 @@ If you don't know how to look, you can directly copy the partially organized [Pl
 
 ### 2. Keyword Configuration
 
-<details id="frequencywordstxt-configuration-tutorial">
-<summary>👉 Click to expand: <strong>frequency_words.txt Configuration Tutorial</strong></summary>
-<br>
-
-Configure monitoring keywords in `frequency_words.txt` with three syntax types and grouping features.
-
-Keywords at the top have higher priority. Adjust keyword order based on your interests.
+Configure monitoring keywords in `frequency_words.txt` with four syntax types and grouping features.
 
 | Syntax Type | Symbol | Purpose | Example | Matching Logic |
 |------------|--------|---------|---------|----------------|
 | **Normal** | None | Basic matching | `Huawei` | Match any one |
 | **Required** | `+` | Scope limiting | `+phone` | Must include both |
 | **Filter** | `!` | Noise exclusion | `!ad` | Exclude if included |
+| **Count Limit** | `@` | Control display count | `@10` | Max 10 news (v3.2.0 new) |
+
+#### 2.1 Basic Syntax
 
-#### 📋 Basic Syntax
+<a name="keyword-basic-syntax"></a>
+
+<details>
+<summary>👉 Click to expand: <strong>Basic Syntax Tutorial</strong></summary>
+<br>
 
 ##### 1. **Normal Keywords** - Basic Matching
 ```txt
@@ -1147,6 +1186,18 @@ Huawei
 ```
 **Effect:** News containing filter words will be **excluded**, even if it contains keywords
 
+##### 4. **Count Limit** `@number` - Control Display Count (v3.2.0 new)
+```txt
+Tesla
+Musk
+@5
+```
+**Effect:** Limit maximum news count for this keyword group
+
+**Priority:** `@number` > Global config > Unlimited
+
+---
+
 #### 🔗 Group Feature - Importance of Empty Lines
 
 **Core Rule:** Use **empty lines** to separate different groups, each group is independently counted
@@ -1264,6 +1315,69 @@ sales
 
 </details>
 
+#### 2.2 Advanced Settings (v3.2.0 new)
+
+<a name="keyword-advanced-settings"></a>
+
+<details>
+<summary>👉 Click to expand: <strong>Advanced Settings Tutorial</strong></summary>
+<br>
+
+##### Keyword Sorting Priority
+
+**Config Location:** `config/config.yaml`
+
+```yaml
+report:
+  sort_by_position_first: false  # Sorting priority config
+```
+
+| Value | Sorting Rule | Use Case |
+|-------|-------------|----------|
+| `false` (default) | News count ↓ → Config position ↑ | Focus on popularity trends |
+| `true` | Config position ↑ → News count ↓ | Focus on personal priority |
+
+**Example:** Config order A, B, C, news count A(3), B(10), C(5)
+- `false`: B(10) → C(5) → A(3)
+- `true`: A(3) → B(10) → C(5)
+
+##### Global Display Count Limit
+
+```yaml
+report:
+  max_news_per_keyword: 10  # Max 10 per keyword (0=unlimited)
+```
+
+**Docker Environment Variables:**
+```bash
+SORT_BY_POSITION_FIRST=true
+MAX_NEWS_PER_KEYWORD=10
+```
+
+**Combined Example:**
+```yaml
+# config.yaml
+report:
+  sort_by_position_first: true   # Config order priority
+  max_news_per_keyword: 10       # Global default max 10 per keyword
+```
+
+```txt
+# frequency_words.txt
+Tesla
+Musk
+@20              # Key focus, show 20 (override global)
+
+Huawei           # Use global config, show 10
+
+BYD
+@5               # Limit to 5
+```
+
+**Final Effect:** Display in config order: Tesla(20) → Huawei(10) → BYD(5)
+
+</details>
+
 ### 3. Push Mode Details
 
 <details>

+ 140 - 34
README.md

@@ -1,7 +1,7 @@
 <div align="center" id="trendradar">
 
 <a href="https://github.com/sansan0/TrendRadar" title="TrendRadar">
-  <img src="/_image/banner.webp" alt="TrendRadar Banner" width="80%">
+  <img src="/_image/banner.webp" alt="TrendRadar Banner" width="90%">
 </a>
 
 🚀 最快<strong>30秒</strong>部署的热点助手 —— 告别无效刷屏,只看真正关心的新闻资讯
@@ -13,7 +13,7 @@
 [![GitHub Stars](https://img.shields.io/github/stars/sansan0/TrendRadar?style=flat-square&logo=github&color=yellow)](https://github.com/sansan0/TrendRadar/stargazers)
 [![GitHub Forks](https://img.shields.io/github/forks/sansan0/TrendRadar?style=flat-square&logo=github&color=blue)](https://github.com/sansan0/TrendRadar/network/members)
 [![License](https://img.shields.io/badge/license-GPL--3.0-blue.svg?style=flat-square)](LICENSE)
-[![Version](https://img.shields.io/badge/version-v3.1.1-blue.svg)](https://github.com/sansan0/TrendRadar)
+[![Version](https://img.shields.io/badge/version-v3.2.0-blue.svg)](https://github.com/sansan0/TrendRadar)
 [![MCP](https://img.shields.io/badge/MCP-v1.0.2-green.svg)](https://github.com/sansan0/TrendRadar)
 
 [![企业微信通知](https://img.shields.io/badge/企业微信-通知-00D4AA?style=flat-square)](https://work.weixin.qq.com/)
@@ -62,10 +62,9 @@
 
 <div align="center">
 
-| [🎯 核心功能](#-核心功能) | [🚀 快速开始](#-快速开始) | [⚙️ 配置详解](#-配置详解) | [🐳 Docker部署](#-docker-部署) |
-|:---:|:---:|:---:|:---:|
-| [🤖 AI 智能分析](#-ai-智能分析) | [🔌 MCP客户端](#-mcp-客户端) | [📝 更新日志](#-更新日志) | [❓ 答疑与交流](#问题答疑与交流) |
-| [⭐ 项目相关](#项目相关) | [🪄 赞助商](#-赞助商) | | |
+| [🚀 快速开始](#-快速开始) | [🤖 AI 智能分析](#-ai-智能分析) | [⚙️ 配置详解](#配置详解) | [📝 更新日志](#-更新日志) | [❓ 答疑与交流](#问题答疑与交流) |
+|:---:|:---:|:---:|:---:|:---:|
+| [🐳 Docker部署](#-docker-部署) | [🔌 MCP客户端](#-mcp-客户端) | [⭐ 项目相关](#项目相关) | [🪄 赞助商](#-赞助商) | |
 
 </div>
 
@@ -226,10 +225,23 @@
 
 设置个人关键词(如:AI、比亚迪、教育政策),只推送相关热点,过滤无关信息
 
-- 支持普通词、必须词(+)、过滤词(!)三种语法
-- 词组化管理,独立统计不同主题热点
+**基础语法**(4种):
+- 普通词:基础匹配
+- 必须词 `+`:限定范围
+- 过滤词 `!`:排除干扰
+- 数量限制 `@`:控制显示数量(v3.2.0 新增)
 
-> 💡 关键词配置教程见 [配置详解 - 关键词配置](#2-关键词配置)  
+**高级功能**(v3.2.0 新增):
+- 🔢 **关键词排序控制**:按热度优先 or 配置顺序优先
+- 📊 **显示数量精准限制**:全局配置 + 单独配置,灵活控制推送长度
+
+**词组化管理**:
+- 空行分隔,独立统计不同主题热点
+
+> 💡 **基础配置教程**:[关键词配置 - 基础语法](#关键词基础语法)
+>
+> 💡 **高级配置教程**:[关键词配置 - 高级配置](#关键词高级配置)
+>
 > 💡 也可以不做筛选,完整推送所有热点(将 frequency_words.txt 留空)
 
 ### **热点趋势分析**
@@ -315,16 +327,23 @@ GitHub 一键 Fork 即可使用,无需编程基础。
 - **大版本升级**:从 v1.x 升级到 v2.y,建议删除现有 fork 后重新 fork,这样更省力且避免配置冲突
 
 
-### 2025/11/22 - v3.1.1
+### 2025/11/23 - v3.2.0
 
-- **修复数据异常导致的崩溃问题**:解决部分用户在 GitHub Actions 环境中遇到的 `'float' object has no attribute 'lower'` 错误
-- 新增双重防护机制:在数据获取阶段过滤无效标题(None、float、空字符串),同时在函数调用处添加类型检查
-- 提升系统稳定性,确保在数据源返回异常格式时仍能正常运行
+**🎯 新增高级定制功能**
 
-**升级说明**(GitHub Fork 用户):
-- 必须更新:`main.py`
-- 建议使用小版本升级方式:复制替换上述文件
+1. **关键词排序优先级配置**
+   - 支持两种排序策略:热度优先 vs 配置顺序优先
+   - 满足不同使用场景:热点追踪 or 个性化关注
+
+2. **显示数量精准控制**
+   - 全局配置:统一限制所有关键词显示数量
+   - 单独配置:使用 `@数字` 语法为特定关键词设置限制
+   - 有效控制推送长度,突出重点内容
 
+> 📖 **详细配置教程**:[关键词配置 - 高级配置](#关键词高级配置)
+
+**🔧 升级说明**:
+- **GitHub Fork 用户**:更新 `main.py`、`config/config.yaml`
 
 ### 2025/11/18 - mcp-v1.0.2
 
@@ -335,6 +354,18 @@ GitHub 一键 Fork 即可使用,无需编程基础。
 <details>
 <summary>👉 点击展开:<strong>历史更新</strong></summary>
 
+
+### 2025/11/22 - v3.1.1
+
+- **修复数据异常导致的崩溃问题**:解决部分用户在 GitHub Actions 环境中遇到的 `'float' object has no attribute 'lower'` 错误
+- 新增双重防护机制:在数据获取阶段过滤无效标题(None、float、空字符串),同时在函数调用处添加类型检查
+- 提升系统稳定性,确保在数据源返回异常格式时仍能正常运行
+
+**升级说明**(GitHub Fork 用户):
+- 必须更新:`main.py`
+- 建议使用小版本升级方式:复制替换上述文件
+
+
 ### 2025/11/20 - v3.1.0
 
 - **新增个人微信推送支持**:企业微信应用可推送到个人微信,无需安装企业微信 APP
@@ -1125,6 +1156,8 @@ frequency_words.txt 文件增加了一个【必须词】功能,使用 + 号
    👉 **了解更多**:[AI 智能分析](#-ai-智能分析) — 解锁项目的隐藏能力,让热点追踪更高效!
 
 
+<a name="配置详解"></a>
+
 ## ⚙️ 配置详解
 
 > **📖 提醒**:本章节提供详细的配置说明,建议先完成 [快速开始](#-快速开始) 的基础配置,再根据需要回来查看详细选项。
@@ -1155,21 +1188,22 @@ platforms:
 
 ### 2. 关键词配置
 
-<details id="frequencywordstxt-配置教程">
-<summary>👉 点击展开:<strong>frequency_words.txt 配置教程</strong></summary>
-<br>
-
-在 `frequency_words.txt` 文件中配置监控的关键词,支持三种语法和词组功能。
-
-关键词越靠前,新闻的优先级越高,你可以根据自己的关注度调整关键词顺序
+在 `frequency_words.txt` 文件中配置监控的关键词,支持四种语法和词组功能。
 
 | 语法类型 | 符号 | 作用 | 示例 | 匹配逻辑 |
 |---------|------|------|------|---------|
 | **普通词** | 无 | 基础匹配 | `华为` | 包含任意一个即可 |
 | **必须词** | `+` | 限定范围 | `+手机` | 必须同时包含 |
 | **过滤词** | `!` | 排除干扰 | `!广告` | 包含则直接排除 |
+| **数量限制** | `@` | 控制显示数量 | `@10` | 最多显示10条新闻(v3.2.0新增) |
+
+#### 2.1 基础语法
 
-#### 📋 基础语法说明
+<a name="关键词基础语法"></a>
+
+<details>
+<summary>👉 点击展开:<strong>基础语法教程</strong></summary>
+<br>
 
 ##### 1. **普通关键词** - 基础匹配
 ```txt
@@ -1196,6 +1230,18 @@ OPPO
 ```
 **作用:** 包含过滤词的新闻会被**直接排除**,即使包含关键词
 
+##### 4. **数量限制** `@数字` - 控制显示数量(v3.2.0 新增)
+```txt
+特斯拉
+马斯克
+@5
+```
+**作用:** 限制该关键词组最多显示的新闻条数
+
+**配置优先级:** `@数字` > 全局配置 > 不限制
+
+---
+
 #### 🔗 词组功能 - 空行分隔的重要作用
 
 **核心规则:** 用**空行**分隔不同的词组,每个词组独立统计
@@ -1236,27 +1282,23 @@ A股
 - 关键词:A股、上证、深证
 - 必须词:涨跌
 - 过滤词:预测
-- 效果:包含股市相关词,同时包含"涨跌",但排除包含"预测"的内容
+- 效果:关注股市涨跌实况,排除预测类内容
 
 **匹配示例:**
 - ✅ "A股今日大幅涨跌分析" ← 有"A股"+"涨跌"
-- ✅ "上证指数涨跌原因解读" ← 有"上证"+"涨跌"
+- ✅ "上证指数涨跌幅创新高" ← 有"上证"+"涨跌"
 - ❌ "专家预测A股涨跌趋势" ← 有"A股"+"涨跌"但包含"预测"
-- ❌ "A股成交量创新高" ← 有"A股"但缺少"涨跌"
 
 **第3组 - 足球赛事类:**
 - 关键词:世界杯、欧洲杯、亚洲杯
 - 必须词:比赛
-- 效果:必须包含杯赛名称,同时包含"比赛"
+- 效果:只关注比赛相关新闻
 
-**匹配示例:**
-- ✅ "世界杯小组赛比赛结果" ← 有"世界杯"+"比赛"
-- ✅ "欧洲杯决赛比赛时间" ← 有"欧洲杯"+"比赛"
-- ❌ "世界杯门票开售" ← 有"世界杯"但缺少"比赛"
+---
 
-#### 🎯 配置技巧
+#### 📝 配置技巧
 
-##### 1. **从宽到严的配置策略**
+##### 1. **从宽到严**
 ```txt
 # 第一步:先用宽泛关键词测试
 人工智能
@@ -1279,6 +1321,7 @@ ChatGPT
 ```
 
 ##### 2. **避免过度复杂**
+
 ❌ **不推荐:** 一个词组包含太多词汇
 ```txt
 华为
@@ -1313,6 +1356,69 @@ OPPO
 
 </details>
 
+#### 2.2 高级配置(v3.2.0 新增)
+
+<a name="关键词高级配置"></a>
+
+<details>
+<summary>👉 点击展开:<strong>高级配置教程</strong></summary>
+<br>
+
+##### 关键词排序优先级
+
+**配置位置:** `config/config.yaml`
+
+```yaml
+report:
+  sort_by_position_first: false  # 排序优先级配置
+```
+
+| 配置值 | 排序规则 | 适用场景 |
+|--------|---------|---------|
+| `false`(默认) | 热点条数 ↓ → 配置位置 ↑ | 关注热度趋势 |
+| `true` | 配置位置 ↑ → 热点条数 ↓ | 关注个人优先级 |
+
+**示例:** 配置顺序 A、B、C,热点数 A(3条)、B(10条)、C(5条)
+- `false`:B(10条) → C(5条) → A(3条)
+- `true`:A(3条) → B(10条) → C(5条)
+
+##### 全局显示数量限制
+
+```yaml
+report:
+  max_news_per_keyword: 10  # 每个关键词最多显示10条(0=不限制)
+```
+
+**Docker 环境变量:**
+```bash
+SORT_BY_POSITION_FIRST=true
+MAX_NEWS_PER_KEYWORD=10
+```
+
+**综合示例:**
+```yaml
+# config.yaml
+report:
+  sort_by_position_first: true   # 按配置顺序优先
+  max_news_per_keyword: 10       # 全局默认每个关键词最多10条
+```
+
+```txt
+# frequency_words.txt
+特斯拉
+马斯克
+@20              # 重点关注,显示20条(覆盖全局配置)
+
+华为            # 使用全局配置,显示10条
+
+比亚迪
+@5               # 限制5条
+```
+
+**最终效果:** 按配置顺序显示 特斯拉(20条) → 华为(10条) → 比亚迪(5条)
+
+</details>
+
 ### 3. 推送模式详解
 
 <details>

+ 2 - 0
config/config.yaml

@@ -27,6 +27,8 @@ crawler:
 report:
   mode: "daily" # 可选: "daily"|"incremental"|"current"
   rank_threshold: 5 # 排名高亮阈值
+  sort_by_position_first: false # 排序优先级:true=先按配置位置排序,false=先按热点条数排序
+  max_news_per_keyword: 0 # 每个关键词最大显示数量,0=不限制
 
 notification:
   enable_notification: true # 是否启用通知功能,如果 false,则不发送手机通知

+ 4 - 0
docker/.env

@@ -8,6 +8,10 @@ ENABLE_CRAWLER=
 ENABLE_NOTIFICATION=
 # 报告模式(daily|incremental|current)
 REPORT_MODE=
+# 排序优先级 (true=先按配置位置排序,false=先按热点条数排序)
+SORT_BY_POSITION_FIRST=
+# 每个关键词最大显示数量 (0=不限制,>0=限制数量)
+MAX_NEWS_PER_KEYWORD=
 
 # ============================================
 # 推送时间窗口配置

+ 2 - 0
docker/docker-compose-build.yml

@@ -16,6 +16,8 @@ services:
       - ENABLE_CRAWLER=${ENABLE_CRAWLER:-}
       - ENABLE_NOTIFICATION=${ENABLE_NOTIFICATION:-}
       - REPORT_MODE=${REPORT_MODE:-}
+      - SORT_BY_POSITION_FIRST=${SORT_BY_POSITION_FIRST:-}
+      - MAX_NEWS_PER_KEYWORD=${MAX_NEWS_PER_KEYWORD:-}
       # 推送时间窗口
       - PUSH_WINDOW_ENABLED=${PUSH_WINDOW_ENABLED:-}
       - PUSH_WINDOW_START=${PUSH_WINDOW_START:-}

+ 2 - 0
docker/docker-compose.yml

@@ -14,6 +14,8 @@ services:
       - ENABLE_CRAWLER=${ENABLE_CRAWLER:-}
       - ENABLE_NOTIFICATION=${ENABLE_NOTIFICATION:-}
       - REPORT_MODE=${REPORT_MODE:-}
+      - SORT_BY_POSITION_FIRST=${SORT_BY_POSITION_FIRST:-}
+      - MAX_NEWS_PER_KEYWORD=${MAX_NEWS_PER_KEYWORD:-}
       # 推送时间窗口
       - PUSH_WINDOW_ENABLED=${PUSH_WINDOW_ENABLED:-}
       - PUSH_WINDOW_START=${PUSH_WINDOW_START:-}

+ 46 - 3
main.py

@@ -20,7 +20,7 @@ import requests
 import yaml
 
 
-VERSION = "3.1.1"
+VERSION = "3.2.0"
 
 
 # === SMTP邮件配置 ===
@@ -74,6 +74,14 @@ def load_config():
         "REPORT_MODE": os.environ.get("REPORT_MODE", "").strip()
         or config_data["report"]["mode"],
         "RANK_THRESHOLD": config_data["report"]["rank_threshold"],
+        "SORT_BY_POSITION_FIRST": os.environ.get("SORT_BY_POSITION_FIRST", "").strip().lower()
+        in ("true", "1")
+        if os.environ.get("SORT_BY_POSITION_FIRST", "").strip()
+        else config_data["report"].get("sort_by_position_first", False),
+        "MAX_NEWS_PER_KEYWORD": int(
+            os.environ.get("MAX_NEWS_PER_KEYWORD", "").strip() or "0"
+        )
+        or config_data["report"].get("max_news_per_keyword", 0),
         "USE_PROXY": config_data["crawler"]["use_proxy"],
         "DEFAULT_PROXY": config_data["crawler"]["default_proxy"],
         "ENABLE_CRAWLER": os.environ.get("ENABLE_CRAWLER", "").strip().lower()
@@ -639,9 +647,18 @@ def load_frequency_words(
         group_required_words = []
         group_normal_words = []
         group_filter_words = []
+        group_max_count = 0  # 默认不限制
 
         for word in words:
-            if word.startswith("!"):
+            if word.startswith("@"):
+                # 解析最大显示数量(只接受正整数)
+                try:
+                    count = int(word[1:])
+                    if count > 0:
+                        group_max_count = count
+                except (ValueError, IndexError):
+                    pass  # 忽略无效的@数字格式
+            elif word.startswith("!"):
                 filter_words.append(word[1:])
                 group_filter_words.append(word[1:])
             elif word.startswith("+"):
@@ -660,6 +677,7 @@ def load_frequency_words(
                     "required": group_required_words,
                     "normal": group_normal_words,
                     "group_key": group_key,
+                    "max_count": group_max_count,  # 新增字段
                 }
             )
 
@@ -1323,6 +1341,14 @@ def count_word_frequency(
             )
 
     stats = []
+    # 创建 group_key 到位置和最大数量的映射
+    group_key_to_position = {
+        group["group_key"]: idx for idx, group in enumerate(word_groups)
+    }
+    group_key_to_max_count = {
+        group["group_key"]: group.get("max_count", 0) for group in word_groups
+    }
+
     for group_key, data in word_stats.items():
         all_titles = []
         for source_id, title_list in data["titles"].items():
@@ -1338,10 +1364,20 @@ def count_word_frequency(
             ),
         )
 
+        # 应用最大显示数量限制(优先级:单独配置 > 全局配置)
+        group_max_count = group_key_to_max_count.get(group_key, 0)
+        if group_max_count == 0:
+            # 使用全局配置
+            group_max_count = CONFIG.get("MAX_NEWS_PER_KEYWORD", 0)
+
+        if group_max_count > 0:
+            sorted_titles = sorted_titles[:group_max_count]
+
         stats.append(
             {
                 "word": group_key,
                 "count": data["count"],
+                "position": group_key_to_position.get(group_key, 999),
                 "titles": sorted_titles,
                 "percentage": (
                     round(data["count"] / total_titles * 100, 2)
@@ -1351,7 +1387,14 @@ def count_word_frequency(
             }
         )
 
-    stats.sort(key=lambda x: x["count"], reverse=True)
+    # 根据配置选择排序优先级
+    if CONFIG.get("SORT_BY_POSITION_FIRST", False):
+        # 先按配置位置,再按热点条数
+        stats.sort(key=lambda x: (x["position"], -x["count"]))
+    else:
+        # 先按热点条数,再按配置位置(原逻辑)
+        stats.sort(key=lambda x: (-x["count"], x["position"]))
+
     return stats, total_titles
 
 

+ 1 - 1
version

@@ -1 +1 @@
-3.1.1
+3.2.0