火车头采集器通过脚本轻松实现双标题内容去重

当我们用火车头采集器采集标题时,如果要实现双标题,比如原标题+百度下拉词或其他词,这种组合的标题有可能会有重复的部分,

比如标题:适合五一出游的地方 适合五一出游的地方推荐

那么如何实现自动去重呢?

先说结论,直接上代码:

// 双标题去重 双标题是用空格隔开
using System;
using System.Collections.Generic;
using SpiderInterface;

class LocoyCode {
public string Run(string content, ResponseEntry response) {
string[] words = content.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
Dictionary<string, int> uniqueWords = new Dictionary<string, int>();
foreach (string word in words) {
if (!uniqueWords.ContainsKey(word)) {
uniqueWords.Add(word, 1);
} else {
uniqueWords[word]++;
}
}
    bool hasDuplicate = false;
    for (int i = 0; i < words.Length; i++) {
        for (int j = i + 1; j < words.Length; j++) {
            if (words[i].Contains(words[j]) || words[j].Contains(words[i])) {
                hasDuplicate = true;
                break;
            }
        }
        if (hasDuplicate) {
            break;
        }
    }

    if (hasDuplicate) {
        string result = "";
        foreach (KeyValuePair<string, int> pair in uniqueWords) {
            if (pair.Value == 1) {
                result = pair.Key;
            } else if (result.Length < pair.Key.Length) {
                result = pair.Key;
            }
        }
        return result;
    } else {
        return content;
    }
}
}

比如标题:适合五一出游的地方 适合五一出游的地方推荐,处理后就得到:适合五一出游的地方推荐。

图片[1]-火车头采集器通过脚本轻松实现双标题内容去重-SEO模板

火车头C#脚本:双标题去重 详细说明

一、 脚本功能概述

该脚本的核心功能是处理由空格分隔的“双标题”或“多标题”字符串。它的目标是:

  1. 检测重复:判断标题中的各个部分是否存在包含关系(例如,“中国”和“中国历史”被视为重复)。
  2. 智能去重:如果检测到重复,脚本会从所有标题部分中,挑选出最合适的一个作为最终结果。
  3. 保留原样:如果标题各部分之间没有任何包含关系,则直接返回原始标题。

二、 脚本工作原理详解

让我们分步解析代码的执行逻辑: 输入示例content = "中国历史 世界历史"

第一步:分词

string[] words = content.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
  • 作用:使用空格 作为分隔符,将输入的字符串 content 切割成一个字符串数组 words
  • StringSplitOptions.RemoveEmptyEntries 确保即使有多个连续空格,也不会产生空字符串。
  • 示例结果words 数组变为 ["中国历史", "世界历史"]

第二步:统计词频

Dictionary<string, int> uniqueWords = new Dictionary<string, int>();
foreach (string word in words) {
    if (!uniqueWords.ContainsKey(word)) {
        uniqueWords.Add(word, 1);
    } else {
        uniqueWords[word]++;
    }
}
  • 作用:遍历 words 数组,使用一个字典 uniqueWords 来记录每个独立标题部分出现的次数。
  • 注意:对于“双标题”场景,通常每个部分都是唯一的,所以这个字典里的 Value 大部分时候是 1。但如果输入是 "中国 中国历史",那么 uniqueWords 会是 {"中国": 2, "中国历史": 1}
  • 示例结果uniqueWords 字典为 {"中国历史": 1, "世界历史": 1}

第三步:检测包含关系(核心逻辑)

bool hasDuplicate = false;
for (int i = 0; i < words.Length; i++) {
    for (int j = i + 1; j < words.Length; j++) {
        if (words[i].Contains(words[j]) || words[j].Contains(words[i])) {
            hasDuplicate = true;
            break;
        }
    }
    if (hasDuplicate) {
        break;
    }
}
  • 作用:这是判断是否需要去重的关键。它使用双重循环,比较数组中任意两个不同的词。
  • words[i].Contains(words[j]) 检查 words[i] 是否包含 words[j]
  • words[j].Contains(words[i]) 检查 words[j] 是否包含 words[i]
  • 只要找到任何一对词满足包含关系,hasDuplicate 标志就会被设为 true,并立即跳出循环。
  • 示例分析:
    • words[0] 是 “中国历史”, words[1] 是 “世界历史”。
    • "中国历史".Contains("世界历史") 为 false
    • "世界历史".Contains("中国历史") 为 false
    • 循环结束,hasDuplicate 保持为 false

第四步:根据检测结果返回结果

if (hasDuplicate) {
    // ... 逻辑A ...
} else {
    return content;
}
  • 情况一:没有包含关系 (hasDuplicate 为 false)
    • 直接执行 else 分支,返回原始内容 content
    • 示例: 返回 "中国历史 世界历史"
  • 情况二:存在包含关系 (hasDuplicate 为 true)
    • 执行 if 分支内的逻辑A。

第五步:当存在重复时,选择最佳标题(逻辑A)

string result = "";
foreach (KeyValuePair<string, int> pair in uniqueWords) {
    if (pair.Value == 1) {
        result = pair.Key;
    } else if (result.Length < pair.Key.Length) {
        result = pair.Key;
    }
}
return result;
  • 作用:遍历之前统计的词频字典 uniqueWords,目的是找出一个“最佳”标题。
  • 选择规则:
    1. 优先选择出现次数为1的词if (pair.Value == 1)。这个逻辑在处理 "中国 中国历史" 时,会先选择 "中国历史"(因为它的Value是1),然后遇到 "中国"(Value是2)时,不会更新 result。最终保留 "中国历史"
    2. 在出现次数都为1的情况下,选择最长的词else if (result.Length < pair.Key.Length)。如果所有词都只出现了一次,这个规则会确保最终保留的是信息量最全、最长的那个标题。
  • 示例: 假设输入是 "中国 中国历史"
    1. hasDuplicate 会变为 true
    2. uniqueWords 是 {"中国": 2, "中国历史": 1}
    3. foreach 循环开始:
      • 遇到 {"中国": 2},不满足 pair.Value == 1,跳过。
      • 遇到 {"中国历史": 1},满足 pair.Value == 1result 被赋值为 "中国历史"
    4. 循环结束,返回 result,即 "中国历史"

三、 使用说明

  1. 适用场景
    • 采集的标题字段中,包含由单个或多个空格分隔的两个或多个标题片段。
    • 这些片段中,通常一个是另一个的子集,例如 "北京 北京天气预报" 或 "手机评测 苹果14Pro深度评测"
  2. 在火车头中的配置
    • 位置:将此脚本放在“内容处理规则”或“标签处理”中的 C#处理 插件里。
    • 变量:脚本中的 content 变量会自动接收传入的标签内容(例如,[标签:标题] 的值)。你不需要修改这部分。
    • 返回值:脚本的 return 值将作为处理后的新内容,替换掉原始标签内容。
  3. 使用示例
    输入内容 (content) 脚本执行过程 输出结果
    "中国历史 世界历史" 无包含关系,hasDuplicatefalse "中国历史 世界历史"
    "中国 中国历史" "中国""中国历史"包含,hasDuplicatetrue。选择更长的"中国历史" "中国历史"
    "手机评测 苹果14Pro深度评测" "手机评测""苹果14Pro深度评测"包含(不,这个例子不包含,脚本会保留原样)。一个更好的例子是:"评测 苹果14Pro深度评测" "苹果14Pro深度评测"
    "天气预报 天气" "天气""天气预报"包含,hasDuplicatetrue。选择更长的"天气预报" "天气预报"
    "A B C" 任意两词之间均无包含关系 "A B C"

四、 优缺点分析

优点
  1. 目标明确:脚本非常专注于解决“包含关系”的标题去重问题,对于这类场景效果直接。
  2. 逻辑清晰:代码结构简单,易于理解和修改。即使不熟悉C#的开发者也能看懂其核心逻辑。
  3. 智能选择:当存在重复时,它不是简单地删除一个,而是通过“选择最长”的策略,保留了信息最完整、最具体的标题,这是一个很实用的优点。
  4. 兼容性好:基于.NET Framework的C#代码,在火车头采集器中运行稳定,无需额外依赖。
缺点
  1. 性能问题(双重循环)
    • 检测包含关系的部分使用了双重 for 循环。其时间复杂度为 O(n²)。
    • 对于标题片段非常多的情况(例如,由空格隔开的10个词),性能会显著下降。但对于“双标题”这种片段数量很少(通常2-3个)的场景,这个缺点几乎可以忽略不计。
  2. 匹配规则过于严格
    • 脚本使用 string.Contains(),这是大小写敏感严格匹配的。
    • 无法处理同义词:例如,"中国历史" 和 "中华上下五千年",虽然意思相近,但不存在字符串包含关系,脚本无法识别。
    • 无法处理细微差异:例如,"苹果14 Pro评测" 和 "苹果14Pro评测",由于缺少空格,Contains 也会失败。
  3. 对分隔符的硬编码
    • 脚本硬编码使用空格 作为分隔符。如果原始数据使用其他分隔符(如 -|/),脚本将无法正确分词,导致失效。
  4. 词频统计逻辑略显冗余
    • 在典型的“双标题”场景下,每个片段通常只出现一次,词频统计部分 uniqueWords 的主要作用其实是为了遍历所有唯一的词。虽然逻辑上可行,但可以写得更简洁(例如直接遍历 words 数组并去重)。

五、 总结与建议

这个脚本是一个小巧、实用的“小工具”,非常适合解决火车头采集中常见的、格式固定的“双标题包含”问题。它的优点在于简单直接,选择策略智能。 使用建议

  • 放心使用:如果你的数据格式稳定(总是空格分隔,且存在明确的包含关系),这个脚本非常可靠。
  • 注意数据清洗:在使用此脚本前,最好确保标题字段中没有多余的、不必要的空格。
  • 考虑扩展:如果你的数据格式更复杂(例如,使用多种分隔符),可以考虑修改 Split 部分,使其能处理多种情况。例如:
    // 修改后的分词代码,支持多种分隔符
    char[] separators = new char[] { ' ', '-', '|', '/' };
    string[] words = content.Split(separators, StringSplitOptions.RemoveEmptyEntries);
    

总的来说,这是一个在特定场景下表现出色的脚本,理解其工作原理和局限性后,可以更有效地发挥它的价值。

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享