rss-parser实现RSS推送时重复发送相同内容问题求助
RSS推送重复问题排查与修复
问题复现
基于rss-parser开发Discord RSS自动推送脚本时,已推送过的RSS条目会被反复推送到目标频道,不符合新条目仅推送一次的预期,原实现代码如下:
(async function main() { // Make a new RSS Parser const parser = new Parser(); // Get all the items in the RSS feed const feed = await parser.parseURL("https://imaginescan.com.br/feed"); // https://www.reddit.com/.rss let items = []; // Clean up the string and replace reserved characters const fileName = `${feed.title.replace(/\s+/g, "-").replace(/[/\\?%*:|"<>]/g, '').toLowerCase()}.json`; if (fs.existsSync(fileName)) { items = require(`./${fileName}`); } // Add the items to the items array await Promise.all(feed.items.map(async (currentItem) => { // Add a new item if it doesn't already exist if (items.filter((item) => isEquivalent(item, currentItem)).length <= 0) { items.push(currentItem); } })); // Save the file fs.writeFileSync(fileName, JSON.stringify(items)); feed.items.reverse().forEach(async (item) => { const channel = client.channels.cache.get('980990991865626634'); let embed = new Discord.MessageEmbed() .setTitle("New Cap") .setDescription(item.title) .setColor("PURPLE") .setImage() channel.send(embed) }) })(); function isEquivalent(a, b) { // Create arrays of property names let aProps = Object.getOwnPropertyNames(a); let bProps = Object.getOwnPropertyNames(b); // if number of properties is different, objects are not equivalent if (aProps.length != bProps.length) { return false; } for (let i = 0; i < aProps.length; i++) { let propName = aProps[i]; // if values of same property are not equal, objects are not equivalent if (a[propName] !== b[propName]) { return false; } } // if we made it this far, objects are considered equivalent return true; }
根本原因
- 无差别全量推送:代码在完成新旧条目判断、写入存储文件后,直接遍历
feed.items下的所有条目执行发送逻辑,完全没有过滤已推送过的旧条目,每次脚本运行都会把RSS源返回的所有条目重新发一遍。 - 对象对比逻辑失效:
isEquivalent通过全属性匹配判断条目是否重复,但rss-parser返回的条目对象每次解析可能生成临时动态属性,且require加载JSON文件得到的对象和解析生成的对象属性数量、原型不一致,会把已推送过的旧条目误判为新条目,导致已推送记录完全不可靠。 require加载缓存问题:Node.js对require加载的文件有默认缓存,如果脚本是定时循环执行,第二次及之后运行时require会直接返回第一次加载的缓存数据,读不到上一次运行写入的最新已推送记录,新旧判断逻辑完全失效。- 存储逻辑无上限:每次检测到"新条目"就直接全量推入数组存储,不清理过期的旧条目,随着运行时间增加存储文件会无限膨胀,对比效率持续降低。
修复后代码
修复点对应如下:
- 改用
fs.readFileSync+JSON.parse读取已推送记录,规避require缓存问题 - 放弃全对象对比,直接使用RSS条目的固定唯一标识(
guid/link字段,RSS规范中每个条目的这两个字段全局唯一且不会变更)判断重复,准确率100% - 仅对检测到的未推送新条目执行发送逻辑,发送成功后再把唯一标识加入已推送列表存储
- 对已推送ID列表做长度截断,仅保留最近50条记录,避免文件无限增大
const fs = require('fs'); const { Client, Intents, MessageEmbed } = require('discord.js'); const Parser = require('rss-parser'); const client = new Client({ intents: [Intents.FLAGS.GUILDS] }); client.on('ready', async () => { console.log(`Logged in as ${client.user.tag}`); // 定时执行,可自行调整拉取间隔,示例为10分钟拉取一次 setInterval(runRSSPush, 10 * 60 * 1000); // 启动时先执行一次 runRSSPush(); }); async function runRSSPush() { const parser = new Parser(); const feed = await parser.parseURL("https://imaginescan.com.br/feed"); const fileName = `${feed.title.replace(/\s+/g, "-").replace(/[/\\?%*:|"<>]/g, '').toLowerCase()}.json`; // 读取已推送条目ID列表,规避require缓存 let pushedItemIds = []; if (fs.existsSync(fileName)) { try { pushedItemIds = JSON.parse(fs.readFileSync(fileName, 'utf8')); } catch (e) { pushedItemIds = []; } } const channel = client.channels.cache.get('980990991865626634'); if (!channel) return; // 按发布时间正序排列,先发较早发布的新条目 const sortedItems = [...feed.items].reverse(); for (const item of sortedItems) { // 优先用guid作为唯一标识,不存在则用link const itemId = item.guid || item.link; // 已经推送过直接跳过 if (pushedItemIds.includes(itemId)) continue; // 构造消息发送 const embed = new MessageEmbed() .setTitle("New Cap") .setDescription(item.title) .setColor("PURPLE"); // 自动识别RSS附件中的图片,若图片存在其他字段可自行调整取值逻辑 if (item.enclosure?.url) embed.setImage(item.enclosure.url); try { await channel.send({ embeds: [embed] }); // 发送成功再把ID加入已推送列表,避免推送失败漏发 pushedItemIds.push(itemId); } catch (err) { console.error('推送消息失败:', err); } } // 仅保留最近50条ID,避免文件过大 if (pushedItemIds.length > 50) { pushedItemIds = pushedItemIds.slice(-50); } // 写入最新的已推送列表 fs.writeFileSync(fileName, JSON.stringify(pushedItemIds, null, 2)); } // 替换为你自己的Discord Bot Token client.login('YOUR_BOT_TOKEN');
注意:原代码中
.setImage()未传参数属于无效调用,修复版已补充自动识别RSS图片地址的逻辑,可根据实际RSS字段结构调整图片取值规则。
内容的提问来源于stack exchange,提问作者felipe
相关产品推荐
相关产品推荐

