إضافة عقدة كود جاهزة للنمط الذي وصفه @Deepankar، حيث أن الجزء القريب المكرر هو المكان الذي عادة ما يصبح فيه الأمر معقدًا.
الإعداد: جدول بيانات “الحصول على الصفوف” باسم Seen (آخر 7 أيام، الأعمدة canonicalUrl و normTitle، إخراج البيانات دائمًا مفعل)، وعناصر RSS/Reddit المدمجة في عقدة باسم New Items. وضع عقدة الكود “التشغيل مرة واحدة لجميع العناصر”:
// عقدة كود n8n، "التشغيل مرة واحدة لجميع العناصر".
// حلقة على العقدة "New Items" (عناصر الخلاصة التي تحتوي على `title` و `link`).
// العقدة السابقة "Seen" = صفوف جدول البيانات التي تحتوي على canonicalUrl و normTitle (آخر ~7 أيام).
const TRACKING = /^(utm_|fbclid$|gclid$|mc_|ref$|ref_src$|igshid$|si$)/i;
const STOP = new Set(['the','a','an','of','to','in','on','for','and','is','are','with','at','by','from','as','its','new']);
function canonicalUrl(u) {
try {
const x = new URL(u);
x.hash = '';
x.hostname = x.hostname.replace(/^(www|m|amp)\./, '');
for (const k of [...x.searchParams.keys()]) if (TRACKING.test(k)) x.searchParams.delete(k);
x.pathname = x.pathname.replace(/\/amp\/?$/, '/').replace(/\/+$/, '') || '/';
return (x.hostname + x.pathname + (x.search || '')).toLowerCase();
} catch { return (u || '').toLowerCase(); }
}
function normTitle(t) {
return (t || '').toLowerCase().replace(/\s[-|:–—]\s[^-|:–—]{2,40}$/, '') // إسقاط " - اسم الموقع"
.replace(/[^\p{L}\p{N}\s]/gu, ' ').split(/\s+/).filter(w => w && !STOP.has(w)).join(' ');
}
function sameStory(a, b) { // Jaccard على مجموعات الكلمات + حد أدنى على الكلمات المشتركة
const A = new Set(a.split(' ')), B = new Set(b.split(' '));
let inter = 0; for (const w of A) if (B.has(w)) inter++;
return inter >= MIN_SHARED && inter / (A.size + B.size - inter) >= THRESHOLD;
}
const THRESHOLD = 0.6, MIN_SHARED = 4; // اضبط على خلاصاتك الخاصة
const seen = $('Seen').all().map(i => i.json);
const urls = new Set(seen.map(r => r.canonicalUrl));
const titles = seen.map(r => r.normTitle).filter(Boolean);
const out = [];
for (const item of $('New Items').all()) {
const cu = canonicalUrl(item.json.link), nt = normTitle(item.json.title);
if (urls.has(cu)) continue;
if (nt && titles.some(t => sameStory(t, nt))) continue;
urls.add(cu); titles.push(nt); // يقوم أيضًا بإلغاء التكرار ضمن هذه الدفعة
out.push({ json: { ...item.json, canonicalUrl: cu, normTitle: nt } });
}
return out;
قمت بتشغيله على عينة صغيرة من الاختراع: تنهار نسخ The Verge و TechCrunch من نفس قصة DevDay إلى واحدة، وتنهار قصة Shopify من TechCrunch و Reuters إلى واحدة، وكلا منشوري “Show HN” المختلفين ينجوان. الحالة الأخيرة هي السبب وراء وجود MIN_SHARED، والعناوين القصيرة تشارك “show hn” و Jaccard البسيط يدمجها.
التعديل: الحلقة الآن تقرأ $('New Items')، شكراً @Deepankar. مع Seen مباشرة في المنحة، كان $input سيكون صفوف الجدول.
لمنشورات روابط Reddit، اربط حقل url للمنشور (المقالة المرتبطة) بـ link، وليس permalink، وإلا فإن نسخة Reddit لن تطابق نسخة RSS أبدًا.
وأعد كتابة canonicalUrl و normTitle إلى جدول البيانات فقط بعد نجاح عقدة LLM، لذلك ستحاول التشغيل الفاشل العنصر مرة أخرى بدلاً من وضع علامة صامتة عليه كما تم عرضه.