gnamiblast-skill:基于技能化与管道化的智能文本处理工具解析
1. 项目概述与核心价值最近在GitHub上闲逛又发现了一个挺有意思的项目叫gabrivardqc123/gnamiblast-skill。光看这个名字可能有点摸不着头脑gnamiblast听起来像是个自造词skill又指向了某种技能或功能。作为一名常年混迹在开源社区喜欢折腾各种自动化工具和效率提升方案的开发者我本能地觉得这背后可能藏着一些能解决实际痛点的东西。经过一番探索和代码研读我发现这个项目本质上是一个基于特定规则或算法的文本处理与自动化增强工具它瞄准了一个非常具体的场景如何高效、智能地处理那些重复、繁琐且有一定模式可循的文本操作任务。简单来说gnamiblast-skill就像给你的文本编辑器或命令行工具装上了一套“组合拳”。它不是一个大而全的IDE而更像是一个高度定制化的“文本处理瑞士军刀”中的某个精密模块。想象一下你每天需要从一堆杂乱无章的日志里提取特定格式的错误码或者需要批量将某种数据格式转换成另一种又或者要对大量文本执行一系列复杂的查找替换规则。手动做这些事不仅枯燥还容易出错。gnamiblast-skill就是为了把开发者从这类重复劳动中解放出来而生的。它通过预定义或可配置的“技能”Skill将多个简单的文本操作原子动作组合成一个强大的处理流程。这个项目适合谁呢首先肯定是开发者尤其是后端开发、运维、数据分析师这些经常和日志、配置文件、数据文件打交道的人。其次对于技术写作、内容运营的同学如果你们需要处理大批量格式调整、关键词标记等任务这个工具也能显著提升效率。哪怕你只是个喜欢用命令行高效处理文本的极客gnamiblast-skill提供的范式也值得借鉴。接下来我就结合自己的实践经验把这个项目的里里外外、从设计思路到实操避坑给大家拆解清楚。2. 核心设计理念与架构解析2.1 “技能”Skill化设计思想gnamiblast-skill最核心的设计理念就是将复杂的文本处理任务“技能化”和“管道化”。什么是“技能”在这里一个技能就是一个独立的、可复用的文本处理单元。它接收一段文本输入经过内部逻辑处理产生一段文本输出。这个逻辑可以非常简单比如“将所有换行符替换为逗号”也可以相对复杂比如“使用正则表达式匹配出所有时间戳并转换为ISO 8601格式”。项目的巧妙之处在于它设计了一套机制让这些技能可以像乐高积木一样被串联起来。一个技能的输出可以直接作为下一个技能的输入。这就形成了一个文本处理管道。例如你可以设计一个管道包含三个技能1. 技能A读取日志文件过滤出包含“ERROR”关键词的行。2. 技能B从这些行中提取出错误代码假设是类似ERR-1234的格式。3. 技能C将这些错误代码整理成逗号分隔的列表并输出到新文件。通过这种组合原本需要编写脚本或多次手动操作的任务被抽象成了一个清晰、可配置的管道。这种设计带来了几个显著优势可维护性每个技能功能单一逻辑内聚。当处理规则需要变化时你通常只需要修改或替换管道中的某一个技能而不是重写整个脚本。可复用性一个写好的“提取错误码”技能既可以用在分析A系统日志的管道里也可以用在分析B系统日志的管道中假设错误码格式一致。可读性管道的配置比如一个YAML或JSON文件清晰地展示了数据处理的完整流程比阅读一段过程式的脚本代码更直观降低了后续理解和修改的门槛。灵活性你可以轻松地调整管道顺序或者在中间插入新的技能进行调试或增强功能。2.2 项目架构与核心模块虽然原始仓库的代码可能比较精简但我们可以推断出其典型的模块划分。一个成熟的gnamiblast-skill类项目通常会包含以下部分技能注册与管理中心这是项目的大脑。它负责发现、加载、管理所有可用的技能。技能可能以Python模块、独立的脚本文件或配置文件的形式存在。这个中心会维护一个技能目录当管道需要某个技能时它能准确找到并实例化。技能抽象基类/接口定义了一个技能必须实现的方法通常是execute(input_text: str, config: dict) - str。这确保了所有技能都有统一的调用方式这是它们能够串联的前提。管道引擎这是项目的心脏。它解析管道定义如从配置文件按照顺序依次调用各个技能并将上一个技能的输出传递给下一个技能作为输入。它还需要处理错误比如某个技能执行失败时是整个管道中止还是记录错误继续执行后续技能。配置解析器负责读取和理解用户定义的管道配置文件。配置文件定义了使用哪些技能、技能的参数是什么、以及它们的执行顺序。输入/输出适配器文本不会凭空产生。这部分负责从各种来源文件、标准输入、剪贴板、HTTP API读取原始文本以及将处理后的结果输出到各种目的地文件、标准输出、数据库、消息队列。这增强了工具的适用性。注意在具体实现中gnamiblast-skill可能并未完全实现上述所有模块它可能更侧重于定义“技能”的规范和提供几个核心示例。但其设计思想必然围绕着可组合的文本处理单元展开。2.3 与类似工具Sed/Awk/PowerShell的对比看到文本处理管道很多人会想到经典的命令行工具sed,awk或者PowerShell的管道。gnamiblast-skill与它们有何不同抽象层次更高sed/awk是“文本流处理器”操作对象是行和字段你需要用特定的领域语言DSL去描述操作。gnamiblast-skill的“技能”可以封装更复杂的逻辑比如调用一个外部API来翻译文本或者使用一个机器学习模型进行情感分析这是sed/awk难以直接做到的。技能的内部实现可以用任何编程语言完成其复杂逻辑对外只暴露简单的文本输入输出接口。配置化与声明式用sed/awk写复杂的处理流程脚本会变得难以阅读和维护。而gnamiblast-skill鼓励将流程以配置文件如YAML的形式声明出来更加直观。管道.yaml文件本身就是一个清晰的文档。生态与共享gnamiblast-skill的理念更容易催生一个“技能商店”。开发者可以编写通用的技能如“Markdown转HTML”、“JSON格式化校验”、“SQL注入关键词过滤”并分享出来其他人直接引用即可无需重复造轮子。而sed/awk的脚本复用性相对较低更依赖于个人或团队的积累。当然这并不意味着gnamiblast-skill要取代传统工具。对于简单的行内替换、字段提取sed/awk一行命令就能搞定显然更轻量快捷。gnamiblast-skill更适合那些需要多个步骤、逻辑相对复杂且希望流程能固化、配置化、团队共享的文本处理场景。3. 核心技能解析与自定义开发指南3.1 内置核心技能剖析一个实用的gnamiblast-skill项目通常会提供一些开箱即用的基础技能。这些技能是构建更复杂管道的基石。我们来深入看看几种常见的内置技能及其实现原理正则匹配与提取技能这可能是使用频率最高的技能。它的配置项通常包括pattern正则表达式和output_format输出模板用于重组匹配到的分组。例如配置pattern: Error: (\d)和output_format: 错误代码是: {1}它就会从输入文本中找出所有类似“Error: 404”的字符串并输出“错误代码是: 404”。其内部实现就是调用编程语言的标准正则库如Python的re模块进行findall或sub操作。文本替换技能功能类似于sed s/old/new/g但可能支持更丰富的配置。比如可以配置多个替换规则有序执行可以区分大小写可以只替换前N次匹配。关键在于它把替换逻辑从命令行参数移到了结构化的配置里更易于管理复杂的替换规则集。行过滤技能根据条件过滤行。条件可以是“包含某关键词”、“匹配某正则”、“行号范围”等。这其实就是grep的功能配置化。内部实现就是按行读取文本对每一行应用判断条件决定保留还是丢弃。格式转换技能这是一个大类可能包含多个子技能。例如JSON格式化/压缩接收一个JSON字符串可能压缩在一行输出美化缩进后的JSON或者反过来。内部调用json.loads()和json.dumps()。CSV转Markdown表格解析CSV数据生成对应的Markdown表格语法。这需要处理引号、转义符等细节。字符编码转换如将GBK编码的文本转换为UTF-8。这在处理来自不同系统的文件时非常有用。聚合与统计技能对处理后的文本进行简单统计。例如统计输出行数、计算特定单词出现的频率、找出最长/最短的行等。这类技能通常放在管道的最后用于生成摘要报告。实操心得在自定义或使用这些技能时一定要特别注意技能的“纯净性”。一个设计良好的技能应该只做一件事并且其输出应该尽可能“干净”为后续技能提供便利。例如一个“提取URL”的技能输出应该就是每行一个URL而不是夹杂着原始文本的其他部分。如果技能产生了非预期的空格、换行或特殊标记可能会让下游技能解析失败。3.2 如何开发一个自定义技能当内置技能无法满足需求时你就需要自己动手开发了。以下是基于常见实践例如假设项目使用Python的步骤和要点步骤一遵循技能接口规范首先你需要找到项目中定义技能接口的地方。通常是一个基类BaseSkill。你的新技能类需要继承它并实现execute方法。# 假设的基类定义 class BaseSkill: def execute(self, input_text: str, config: dict) - str: 执行技能的核心方法。 :param input_text: 上游技能传来的文本。 :param config: 该技能的配置字典从管道配置文件中读取。 :return: 处理后的文本。 raise NotImplementedError # 你的自定义技能 class MyCustomSkill(BaseSkill): def execute(self, input_text: str, config: dict) - str: # 1. 从config中读取你的技能需要的参数 my_param config.get(my_param, default_value) # 2. 实现你的核心处理逻辑 processed_text self._my_processing_logic(input_text, my_param) # 3. 返回处理后的文本 return processed_text def _my_processing_logic(self, text, param): # 这里是你的具体业务逻辑 # 例如将文本中所有数字乘以一个系数假设param是系数 import re def multiply_match(match): num int(match.group()) return str(num * int(param)) return re.sub(r\d, multiply_match, text)步骤二处理配置与错误execute方法收到的config字典包含了用户在管道配置文件中为这个技能设置的参数。你的代码应该能优雅地处理缺失的配置提供默认值和无效的配置抛出清晰的异常或记录错误。良好的错误信息能极大降低调试成本。步骤三确保技能的无状态性尽可能理想情况下一个技能应该是无状态的纯函数。即相同的输入和配置永远产生相同的输出不依赖外部变量或上一次的执行结果。这保证了技能在管道中的行为可预测也便于测试和并行化如果未来管道引擎支持的话。如果技能必须有状态例如需要一个计数器那么这个状态应该通过config传入或者在技能类初始化时设定而不是在execute方法内部隐式修改类属性。步骤四注册技能你需要让管道引擎知道这个新技能的存在。通常有两种方式自动发现将你的技能类文件放在特定的目录如skills/下项目启动时会自动扫描并注册。这要求你的类有特定的装饰器或遵循特定的命名规则。手动注册在一个中心注册文件如__init__.py或一个专门的注册表文件中导入你的类并添加到全局技能字典中。例如SKILL_REGISTRY[my_custom] MyCustomSkill。步骤五编写单元测试为你的技能编写测试用例覆盖正常情况、边界情况和异常情况。测试应模拟execute方法传入不同的input_text和config断言输出是否符合预期。这是保证技能质量、防止后续修改引入BUG的关键。4. 完整管道配置与实战演练4.1 管道配置文件详解管道通常通过一个配置文件来定义。YAML因其可读性好是这类配置的热门选择。下面我们通过一个复杂的实战案例来解析配置文件的各个部分。假设我们有一个任务监控一个应用程序的日志文件app.log实时提取出所有“交易失败”的记录从中解析出用户ID、失败原因和交易金额然后将这些信息格式化为JSON发送到一个模拟的HTTP接口用于告警或分析同时将原始失败行保存到一个本地文件failures.log中。对应的管道配置文件monitor_pipeline.yaml可能如下所示# pipeline_config.yaml name: “交易失败监控与上报管道” version: “1.0” # 输入源定义这里使用一个“文件监听”适配器持续读取新行 input: adapter: file_tail config: file_path: “/var/log/app.log” encoding: “utf-8” # 定义处理管道技能按顺序执行 pipeline: - skill: filter_lines # 技能1过滤行 id: filter_errors # 给这个技能实例一个ID便于调试和日志追踪 config: condition: contains # 过滤条件包含关键词 keyword: “交易失败” case_sensitive: false # 不区分大小写 - skill: regex_extract # 技能2正则提取关键字段 id: extract_fields config: # 假设日志格式为[时间] 用户[UID] 交易失败原因{原因}金额{金额}元 pattern: “用户(\d).*?原因([^]).*?金额(\d(?:\.\d)?)元” output_format: “{1}|{2}|{3}” # 输出格式用户ID|原因|金额用竖线分隔 - skill: fork # 技能3分支技能。将数据流复制成两份分别进入两个子管道 id: fork_stream branches: - # 分支A格式化为JSON并上报HTTP - skill: to_json # 技能3A1: 转换为JSON id: format_json config: # 指定字段名对应regex_extract输出的三个部分 fields: [“user_id”, “reason”, “amount”] delimiter: “|” # 指定输入的分隔符 - skill: http_post # 技能3A2: 发送HTTP POST请求 id: report_api config: url: “https://api.internal.com/alert” headers: Content-Type: “application/json” Authorization: “Bearer ${API_TOKEN}” # 支持环境变量 timeout: 5 - # 分支B直接保存原始失败行到文件 - skill: write_file # 技能3B1: 写入文件 id: archive_failures config: file_path: “./failures.log” mode: “append” # 追加模式 add_timestamp: true # 在行首添加时间戳 # 全局配置如并发、错误处理策略 settings: max_workers: 2 # 并发处理的工作线程数如果支持 on_error: continue # 某个技能出错时是继续(continue)还是停止(stop)整个管道 log_level: INFO配置文件关键点解析结构化整个配置层次清晰input、pipeline、settings各司其职。技能参数化每个技能的config部分是其专属的参数这使得同一个技能如regex_extract可以在不同管道中被复用通过不同的配置实现不同的提取规则。分支与并行fork技能展示了管道的强大之处允许数据流分叉进行不同的后续处理非常适合“一读多写”的场景。环境变量${API_TOKEN}这样的语法支持从环境变量中读取敏感信息避免将密码、密钥等硬编码在配置文件中这是生产环境必备的安全实践。错误处理策略settings.on_error提供了管道级别的容错控制。4.2 实战从零搭建一个日志分析管道让我们手把手搭建一个简单的管道用于分析Nginx访问日志统计每个IP的访问次数和总流量。步骤1准备输入数据假设我们有一段Nginx日志片段access.log192.168.1.1 - - [10/Oct/2023:14:32:01 0800] “GET /api/user HTTP/1.1” 200 1234 “-” “Mozilla/5.0” 192.168.1.2 - - [10/Oct/2023:14:32:02 0800] “GET /static/css.css HTTP/1.1” 200 5678 “-” “Mozilla/5.0” 192.168.1.1 - - [10/Oct/2023:14:32:03 0800] “GET /api/order HTTP/1.1” 200 9876 “-” “Mozilla/5.0”步骤2设计管道流程输入从文件读取日志。技能1正则提取从每行日志中提取IP地址和响应体大小字节数。Nginx日志的默认组合格式下可以使用正则^(\S).*?“\S \S \S“ \d (\d)。技能2聚合统计按IP分组累加访问次数和流量大小。技能3格式化输出将统计结果格式化为一个清晰的Markdown表格。输出将Markdown表格输出到控制台和文件。步骤3编写管道配置创建nginx_analysis.yamlname: “Nginx访问日志分析” input: adapter: file_read config: file_path: “access.log” pipeline: - skill: regex_extract id: extract_ip_and_size config: pattern: ‘^(\S).*?“\S \S \S“ \d (\d)’ output_format: “{1},{2}” # 输出 IP,流量 - skill: aggregate # 假设有一个内置的聚合技能 id: stats_by_ip config: key_index: 0 # 以第一列IP作为分组键 value_indices: [1] # 对第二列流量进行聚合 operations: [“count”, “sum”] # 聚合操作计数和求和 output_header: [“IP”, “访问次数”, “总流量(Bytes)”] - skill: to_markdown_table id: format_output config: delimiter: “,”步骤4运行与结果使用项目的命令行工具运行管道假设命令是gnamiblast -c nginx_analysis.yaml。预期的最终输出可能是一个Markdown字符串| IP | 访问次数 | 总流量(Bytes) | | :--- | :--- | :--- | | 192.168.1.1 | 2 | 11110 | | 192.168.1.2 | 1 | 5678 |这个结果可以直接粘贴到文档中或者由下游技能进一步处理如转换成HTML邮件发送。5. 性能调优、常见问题与排查技巧5.1 性能瓶颈分析与优化策略当处理大量数据如GB级别的日志文件时性能会成为关键问题。以下是几个常见的瓶颈点及优化思路输入/输出I/O瓶颈问题频繁读写小文件或者从网络等慢速源读取数据。优化批量处理对于文件读取尽量使用缓冲读取如一次读入多行到内存而不是逐行读取。对于输出可以将结果在内存中累积到一定量后再一次性写入。使用高效适配器如果内置的文件读取适配器效率低可以考虑自定义一个使用mmap内存映射文件或asyncio异步IO的适配器。管道并行化如果管道中的技能是CPU密集型的且彼此间无严格顺序依赖除了首尾可以探索让多个技能实例并行处理数据流的不同片段。这需要管道引擎的支持。技能执行效率瓶颈问题某个自定义技能内部逻辑复杂或者使用了低效的算法如嵌套循环处理大量数据。优化代码剖析使用性能分析工具如Python的cProfile找出技能中的热点函数。算法优化例如在文本匹配中如果模式固定预编译正则表达式re.compile能带来显著提升。避免在循环内重复创建对象。考虑外部工具对于极其复杂的文本处理如大型XML/JSON解析可以考虑在技能内部调用用C/C/Rust编写的高性能命令行工具如jq处理JSON而不是用纯脚本语言实现。内存消耗瓶颈问题管道试图一次性将整个大文件加载到内存中导致内存溢出OOM。优化流式处理这是此类工具设计的核心优势。确保整个管道是“流式”的即技能处理完一行或一小批数据后立即传递给下一个技能并释放内存。避免在技能内部使用read()读取整个文件或进行accumulate所有结果再输出的操作。技能设计原则编写技能时时刻考虑内存友好性。对于execute方法输入是一段文本可能是一行也可能是一块输出也是一段文本处理完即可丢弃。5.2 常见问题与解决方案速查表在实际使用和开发gnamiblast-skill类项目时你肯定会遇到各种问题。下面是我踩过的一些坑和解决方案问题现象可能原因排查步骤与解决方案管道执行无任何输出1. 输入适配器未读取到数据。2. 第一个技能过滤掉了所有数据。3. 输出适配器配置错误如文件路径无写权限。1. 检查输入文件路径、权限。在第一个技能前添加一个debug_print技能打印原始输入。2. 检查过滤技能的规则是否过于严格。3. 检查输出文件目录是否存在或尝试输出到标准输出(stdout)进行测试。正则提取技能匹配不到内容1. 正则表达式错误。2. 文本编码问题导致特殊字符乱码。3. 输入文本格式与预期不符。1. 使用在线正则测试工具如 regex101.com验证你的表达式。注意转义字符。2. 确保输入适配器使用了正确的编码如utf-8。对于来源复杂的文本可先使用一个detect_encoding技能。3. 在正则技能前加一个debug_print确认传入的文本具体是什么样子。自定义技能抛出异常管道中止1. 技能代码存在BUG如除零错误、键错误。2. 配置参数缺失或类型错误。3. 技能依赖的外部资源如数据库、API不可用。1. 查看详细的错误堆栈信息定位到技能代码的具体行。为技能添加完善的日志和异常捕获。2. 在技能的execute方法开头验证config字典中的必要参数是否存在且有效。3. 实现重试机制或更优雅的降级处理如返回空字符串或默认值并将settings.on_error设置为continue以跳过当前错误数据。处理大量数据时速度很慢1. 单个技能处理慢见性能瓶颈分析。2. 管道串行执行无法利用多核。3. 频繁的日志输出到控制台。1. 对技能进行性能剖析和优化。2. 如果项目支持尝试启用并行处理配置max_workers。注意技能必须是无状态的才能安全并行。3. 将日志级别从DEBUG或INFO调整为WARNING或ERROR减少IO等待。分支Fork技能后某个分支没有执行1. 分支配置语法错误。2. 分支内的技能有错误且该分支的错误处理策略是stop。3. 管道引擎对分支的支持有BUG。1. 仔细检查YAML缩进确保分支列表格式正确。2. 单独测试有问题的分支管道。3. 查阅项目文档或Issue列表看是否有已知问题。可以尝试将分支拆分成两个独立的管道分别运行。5.3 调试与日志技巧高效的调试能节省大量时间。除了上面表格中的方法还有一些通用技巧善用“直通”技能创建一个什么都不做直接返回输入文本的pass_through技能。把它插入到管道的任何位置可以帮助你确认数据流到了哪里格式是什么。分级日志在开发自定义技能时合理使用不同级别的日志DEBUG, INFO, WARNING, ERROR。DEBUG级别可以打印详细的中间结果INFO级别记录关键步骤ERROR级别记录不可恢复的错误。通过调整管道全局的log_level来控制输出量。单元测试是基石为每个技能编写全面的单元测试模拟各种边界输入空字符串、超长字符串、异常字符。这不仅能保证技能质量在修改代码后运行测试也是最快的回归验证方式。可视化管道如果项目不提供可以自己写一个小脚本读取管道配置文件生成一个简单的流程图使用Graphviz的DOT语言。这有助于在复杂管道中理清数据流向特别是在有分支和合并的情况下。6. 扩展思路与高级应用场景掌握了基础之后我们可以看看gnamiblast-skill这类设计模式还能玩出什么花样应用到哪些更高级的场景。场景一持续集成/持续部署CI/CD中的文本处理在CI/CD流水线中经常需要解析测试报告、检查代码风格、更新版本号等。你可以创建一个“CI文本处理”技能包包含以下技能parse_junit_xml: 解析JUnit格式的XML测试报告提取失败用例名和错误信息格式化为简明的Markdown。check_commit_msg: 检查Git提交信息是否符合约定格式如Angular Convention。bump_version: 根据当前Git分支名如release/v1.2.3自动更新package.json或pyproject.toml中的版本号。 将这些技能组合成管道集成到Jenkins、GitLab CI或GitHub Actions的脚本中实现自动化。场景二安全扫描与敏感信息过滤处理用户提交的内容或内部文档时需要过滤敏感信息。技能1使用正则表达式和关键词列表匹配潜在的手机号、身份证号、邮箱。技能2对匹配到的内容进行脱敏处理如保留前3后4位中间用*代替。技能3将脱敏后的文本与原始文本进行对比生成一份脱敏报告。 这个管道可以作为一个服务在内容发布前自动调用。场景三多语言翻译与内容本地化虽然直接调用翻译API如Google Translate, DeepL本身可以是一个技能但结合管道可以做得更强大。输入原始英文Markdown文档。技能A提取所有需要翻译的文本块排除代码块、URL等。技能B调用翻译API批量翻译。技能C将翻译后的文本块插回原Markdown结构。技能D对比中英文文本长度调整表格列宽等格式。 这比单纯全文丢给API翻译能更好地保留文档的原始格式和结构。场景四与流处理平台集成gnamiblast-skill的管道本质是一个数据流处理器。它可以很容易地与现代流处理平台集成。作为Kafka Streams或Flink的一个UDF用户自定义函数将你的技能逻辑打包在流处理作业中调用实时处理消息流。作为Apache NiFi的一个处理器NiFi本身就是一个强大的数据流工具你可以将技能包装成NiFi的Processor利用NiFi的UI进行编排和监控。作为命令行工具嵌入Shell脚本这是最直接的用法通过管道符|将gnamiblast-skill与其他Unix工具grep,sort,uniq结合发挥更大威力。开发自己的“技能市场”这是最具想象力的扩展。如果项目生态发展起来可以建立一个中心化的技能仓库。开发者可以提交自己编写的技能通过Pull Request或打包上传经过审核后纳入官方仓库。其他用户可以通过简单的配置引用这些技能比如skill: official/parse_csv。这需要项目定义清晰的技能打包、版本管理和依赖声明规范。回过头看gnamiblast-skill这个项目标题下的创意其价值远不止于它当前实现的代码量。它代表了一种解决特定领域问题可配置的、组合式的文本处理的优雅范式。这种将复杂流程分解为可复用、可编排的原子操作的思想在软件工程的很多领域都有体现比如函数式编程、微服务架构、工作流引擎。通过亲手实践这样一个项目你不仅能获得一个提高日常效率的利器更能深入理解“解耦”和“组合”这两个强大的设计原则。在实际操作中最大的体会是前期花时间设计好技能的接口和管道的配置规范后期维护和扩展的效率会成倍提升。不要急于编写处理逻辑先想清楚数据如何流动技能之间如何契约这往往是项目能否变得清晰、健壮的关键。

相关新闻

最新新闻

日新闻

周新闻

月新闻