mcpField
D
deduplication

deduplication

github.com/dadbodgeoff数据与数据库79248 次安装10 周前更新
代码仓库

关于 deduplication

用于去重多来源数据的处理技能。它会按语义相似度分组,并依据来源声誉和记录完整度等条件选择规范版本。

主要功能

  1. 01按 ID 处理重复项
  2. 02语义内容分组
  3. 03选择规范记录
  4. 04来源声誉评分
  5. 05去重缩减指标

适用场景

  1. 01清理多来源数据流中的重复项
  2. 02为同一新闻选择规范版本
  3. 03ID 冲突时保留信息更完整的记录

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dadbodgeoff/drift --skill deduplication

安装后在 Claude Code 中输入/deduplication确认已加载。

详细介绍

事件去重

使用信誉评分和基于哈希的分组,从多个来源中选择规范版本。

何时使用此技能

  • 汇总来自多个来源的数据(新闻、事件、产品)
  • 同一内容出现在不同媒体或来源中
  • 需要从重复内容中选出“最佳”版本
  • 跟踪去重指标以进行优化

核心概念

简单的 URL 去重还不够。生产环境还需要:

  • 按语义相似度分组(同一事件,不同媒体)
  • 选择规范版本(选出“最佳”版本)
  • 信誉评分(优先选择权威来源)
  • 同时支持基于 ID 和基于内容的去重

两种模式:

  1. 基于 ID:来源具有唯一 ID 时,在 ID 冲突时保留“最佳”版本
  2. 基于内容:按语义相似度分组,并从每组中选出规范版本

实现

TypeScript
import { createHash } from 'crypto';

interface DeduplicationResult<T> {
  items: T[];
  originalCount: number;
  dedupedCount: number;
  reductionPercent: number;
  duplicateGroups?: number;
}

// ============================================
// ID-Based Deduplication
// ============================================

function deduplicateById<T extends { id: string }>(
  items: T[],
  preferFn: (existing: T, candidate: T) => T
): DeduplicationResult<T> {
  const seen = new Map<string, T>();
  
  for (const item of items) {
    const existing = seen.get(item.id);
    if (existing) {
      seen.set(item.id, preferFn(existing, item));
    } else {
      seen.set(item.id, item);
    }
  }
  
  const dedupedItems = Array.from(seen.values());
  const reductionPercent = items.length > 0
    ? Math.round((1 - dedupedItems.length / items.length) * 100)
    : 0;
  
  return {
    items: dedupedItems,
    originalCount: items.length,
    dedupedCount: dedupedItems.length,
    reductionPercent,
  };
}

// ============================================
// Content-Based Deduplication
// ============================================

interface Article {
  title: string;
  url: string;
  domain: string;
  publishedAt: string;
  tone?: number;
}

/**
 * Generate deduplication key from content
 * Groups by: normalized title + source country + date
 */
function generateDedupKey(article: Article): string {
  const normalizedTitle = article.title
    .toLowerCase()
    .replace(/[^\w\s]/g, '')
    .trim()
    .slice(0, 50);

  const dateStr = article.publishedAt?.slice(0, 10).replace(/-/g, '') || 'unknown';

  return `${normalizedTitle}|${dateStr}`;
}

/**
 * Generate unique ID from URL
 */
function generateEventId(url: string): string {
  return createHash('md5').update(url).digest('hex').slice(0, 12);
}

/**
 * Source reputation scoring
 */
function getReputationScore(domain: string): number {
  // Tier 1: Wire services and major international
  const tier1 = ['reuters.com', 'apnews.com', 'bbc.com', 'bbc.co.uk', 
                 'aljazeera.com', 'france24.com', 'dw.com'];
  if (tier1.some(r => domain.includes(r))) return 100;
  
  // Tier 2: Major newspapers
  const tier2 = ['nytimes.com', 'washingtonpost.com', 'theguardian.com', 
                 'ft.com', 'economist.com', 'wsj.com'];
  if (tier2.some(r => domain.includes(r))) return 75;
  
  // Tier 3: Regional/national
  const tier3 = ['cnn.com', 'foxnews.com', 'nbcnews.com', 'abcnews.go.com'];
  if (tier3.some(r => domain.includes(r))) return 50;
  
  return 10;
}

/**
 * Select canonical article from duplicate group
 */
function selectCanonical<T extends Article>(
  group: { item: T; source: string }[]
): { item: T; source: string } {
  return group.reduce((best, current) => {
    const bestScore = getReputationScore(best.item.domain) + 
                      Math.abs(best.item.tone || 0);
    const currentScore = getReputationScore(current.item.domain) + 
                         Math.abs(current.item.tone || 0);
    
    return currentScore > bestScore ? current : best;
  });
}

/**
 * Deduplicate articles from multiple sources
 */
function deduplicateArticles<T extends Article>(
  sourceResults: { sourceName: string; articles: T[] }[]
): DeduplicationResult<T & { source: string }> {
  const groups = new Map<string, { item: T; source: string }[]>();
  let totalArticles = 0;

  // Group articles by dedup key
  for (const { sourceName, articles } of sourceResults) {
    for (const article of articles) {
      totalArticles++;
      const key = generateDedupKey(article);
      
      if (!groups.has(key)) {
        groups.set(key, []);
      }
      groups.get(key)!.push({ item: article, source: sourceName });
    }
  }

  // Select canonical article from each group
  const items: (T & { source: string })[] = [];
  
  for (const group of groups.values()) {
    const canonical = selectCanonical(group);
    items.push({ ...canonical.item, source: canonical.source });
  }

  const reductionPercent = totalArticles > 0 
    ? Math.round((1 - items.length / totalArticles) * 100)
    : 0;

  console.log(`[Dedup] ${totalArticles} → ${items.length} (${reductionPercent}% reduction)`);

  return {
    items,
    originalCount: totalArticles,
    dedupedCount: items.length,
    reductionPercent,
    duplicateGroups: groups.size,
  };
}

使用示例

基于 ID 的去重
const events = await fetchEvents();

const result = deduplicateById(events, (existing, candidate) => {
  // Prefer events with coordinates
  if (!existing.lat && candidate.lat) return candidate;
  // Prefer higher sentiment magnitude
  if (Math.abs(candidate.sentiment) > Math.abs(existing.sentiment)) {
    return candidate;
  }
  return existing;
});

console.log(`Reduced ${result.reductionPercent}% duplicates`);
多来源汇总
const results = await Promise.all([
  fetchFromSourceA(),
  fetchFromSourceB(),
  fetchFromSourceC(),
]);

const { items, reductionPercent } = deduplicateArticles([
  { sourceName: 'source-a', articles: results[0] },
  { sourceName: 'source-b', articles: results[1] },
  { sourceName: 'source-c', articles: results[2] },
]);

// items now contains canonical articles with source attribution

最佳实践

  1. 语义分组 - 按规范化内容分组,而不只是按 URL
  2. 信誉评分 - 优先选择权威来源作为规范来源
  3. 选择最佳版本 - ID 冲突时,保留数据最完整的版本
  4. 跟踪缩减情况 - 记录去重带来的效果
  5. 标注来源 - 跟踪规范版本来自哪个来源

常见错误

  • 仅按 URL 去重(无法识别来自不同媒体的同一事件)
  • 随机选择重复项(丢失质量信号)
  • 不进行规范化(大小写或标点差异会造成漏判)
  • 不跟踪缩减指标(无法进行优化)
  • 硬编码来源列表(应使其可配置)

相关模式

  • batch-processing - 高效处理去重后的条目
  • validation-quarantine - 去重前进行验证
  • checkpoint-resume - 跟踪哪些文件已完成去重

评论

支持 Markdown