[爬虫学习笔记]基于 SimHash 的去重复处理模块ContentSeen的构建

      Internet上的一些站点常常存在着镜像网站(mirror),即两个网站的内容一样但网页对应的域名不同。这样会导致对同一份网页爬虫重复抓取多次。为了避免这种情况,对于每一份抓取到的网页,它首先需要进入ContentSeen模块。该模块会判断网页的内容是否和已下载过的某个网页的内容一致,如果一致,则该网页不会再被送去进行下一步的处理。这样的做法能够显著的降低爬虫需要下载的网页数。至于如果判断两个网页的内容是否一致,一般的思路是这样的:并不会去直接比较两个网页的内容,而是将网页的内容经过计算生成FingerPrint(指纹),通常FingerPrint是一个固定长度的字符串,要比网页的正文短很多。如果两个网页的FingerPrint一样,则认为它们内容完全相同。

      为了完成这一模块,首先我们需要一个强大的指纹算法,将我们的网页内容计算成指纹存入数据库,下次直接判断指纹在保存前通过指纹的对比即可成功完成去重复操作。

      首先来看一下大名鼎鼎的Google公司使用的网页去重复算法SimHash吧:

      GoogleMoses Charikar发表的一篇论文“detecting near-duplicates for web crawling”中提出了simhash算法,专门用来解决亿万级别的网页的去重任务。

      SimHash作为locality sensitive hash(局部敏感哈希)的一种:

      其主要思想是降维,将高维的特征向量映射成低维的特征向量,通过两个向量的Hamming Distance来确定文章是否重复或者高度近似。

      其中,Hamming Distance,又称汉明距离,在信息论中,两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数。也就是说,它就是将一个字符串变换成 另外一个字符串所需要替换的字符个数。例如:1011101 与 1001001 之间的汉明距离是 2。至于我们常说的字符串编辑距离则是一般形式的汉明距离。

      如此,通过比较多个文档的SimHash值的海明距离,可以获取它们的相似度。

      详情可以看这里SimHash算法

_______________________________________________________________________________________________

下面我们来进行代码实现:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Crawler.Common
{
    public class SimHashAnalyser
    {

        private const int HashSize = 32;

        public static float GetLikenessValue(string needle, string haystack, TokeniserType type = TokeniserType.Overlapping)
        {
            var needleSimHash = GetSimHash(needle, type);
            var hayStackSimHash = GetSimHash(haystack, type);
            return GetLikenessValue(needleSimHash, hayStackSimHash);
        }

        public static float GetLikenessValue(int needleSimHash, int hayStackSimHash)
        {
            return (HashSize - GetHammingDistance(needleSimHash, hayStackSimHash)) / (float)HashSize;
        }

        private static IEnumerable<int> DoHashTokens(IEnumerable<string> tokens)
        {
            return tokens.Select(token => token.GetHashCode()).ToList();
        }

        private static int GetHammingDistance(int firstValue, int secondValue)
        {
            var hammingBits = firstValue ^ secondValue;
            var hammingValue = 0;
            for (var i = 0; i < 32; i++)
                if (IsBitSet(hammingBits, i))
                    hammingValue += 1;
            return hammingValue;
        }

        private static bool IsBitSet(int b, int pos)
        {
            return (b & (1 << pos)) != 0;
        }

        public static int GetSimHash(string input)
        {
            return GetSimHash(input, TokeniserType.Overlapping);
        }

        public static int GetSimHash(string input, TokeniserType tokeniserType)
        {
            ITokeniser tokeniser;
            if (tokeniserType == TokeniserType.Overlapping)
                tokeniser = new OverlappingStringTokeniser();
            else
                tokeniser = new FixedSizeStringTokeniser();

            var hashedtokens = DoHashTokens(tokeniser.Tokenise(input));
            var vector = new int[HashSize];
            for (var i = 0; i < HashSize; i++)
                vector[i] = 0;

            foreach (var value in hashedtokens)
                for (var j = 0; j < HashSize; j++)
                    if (IsBitSet(value, j))
                        vector[j] += 1;
                    else
                        vector[j] -= 1;
            var fingerprint = 0;
            for (var i = 0; i < HashSize; i++)
                if (vector[i] > 0)
                    fingerprint += 1 << i;
            return fingerprint;
        }

    }

    public interface ITokeniser
    {
        IEnumerable<string> Tokenise(string input);
    }

    public class FixedSizeStringTokeniser : ITokeniser
    {
        private readonly ushort _tokensize;
        public FixedSizeStringTokeniser(ushort tokenSize = 5)
        {
            if (tokenSize < 2)
                throw new ArgumentException("Token 不能超出范围");
            if (tokenSize > 127)
                throw new ArgumentException("Token 不能超出范围");
            _tokensize = tokenSize;
        }

        public IEnumerable<string> Tokenise(string input)
        {
            var chunks = new List<string>();
            var offset = 0;
            while (offset < input.Length)
            {
                chunks.Add(new string(input.Skip(offset).Take(_tokensize).ToArray()));
                offset += _tokensize;
            }
            return chunks;
        }

    }

    public class OverlappingStringTokeniser : ITokeniser
    {

        private readonly ushort _chunkSize;
        private readonly ushort _overlapSize;

        public OverlappingStringTokeniser(ushort chunkSize = 4, ushort overlapSize = 3)
        {
            if (chunkSize <= overlapSize)
                throw new ArgumentException("Chunck 必须大于 overlap");
            _overlapSize = overlapSize;
            _chunkSize = chunkSize;
        }

        public IEnumerable<string> Tokenise(string input)
        {
            var result = new List<string>();
            var position = 0;
            while (position < input.Length - _chunkSize)
            {
                result.Add(input.Substring(position, _chunkSize));
                position += _chunkSize - _overlapSize;
            }
            return result;
        }

    }

    public enum TokeniserType
    {
        Overlapping,
        FixedSize
    }
}

 

调用方法如下:

var s1 = "the cat sat on the mat.";
var s2 = "the cat sat on a mat.";

var similarity = SimHashAnalyser.GetLikenessValue(s1, s2);

Console.Clear();
Console.WriteLine("相似度: {0}%", similarity * 100);
Console.ReadKey();

 

输出为:

相似度: 78.125%
接下来就是对ContentSeen模块的简单封装:
using Crawler.Common;

namespace Crawler.Processing
{
    /// <summary>
    /// 对于每一份抓取到的网页,它首先需要进入Content Seen模块。该模块会判断网页的内容是否和已下载过的某个网页的内容一致,如果一致,则该网页不会再被送去进行下一步的处理。
    /// </summary>
    public class ContentSeen
    {
        public static int GetFingerPrint(string html)
        {
            return SimHashAnalyser.GetSimHash(html);
        }

        public static float Similarity(int print1, int print2)
        {
            return SimHashAnalyser.GetLikenessValue(print1, print2);
        }

    }
}

时间: 2024-12-30 20:42:47

[爬虫学习笔记]基于 SimHash 的去重复处理模块ContentSeen的构建的相关文章

python网络爬虫学习笔记

python网络爬虫学习笔记 By 钟桓 9月 4 2014 更新日期:9月 4 2014 文章目录 1. 介绍: 2. 从简单语句中开始: 3. 传送数据给服务器 4. HTTP头-描述数据的数据 5. 异常 5.0.1. URLError 5.0.2. HTTPError 5.0.3. 处理异常 5.0.4. info和geturl 6. Opener和Handler 7. Basic Authentication 8. 代理 9. Timeout 设置 10. Cookie 11. Deb

爬虫学习 17.基于scrapy-redis两种形式的分布式爬虫

爬虫学习 17.基于scrapy-redis两种形式的分布式爬虫 redis分布式部署 1.scrapy框架是否可以自己实现分布式? - 不可以.原因有二. 其一:因为多台机器上部署的scrapy会各自拥有各自的调度器,这样就使得多台机器无法分配start_urls列表中的url.(多台机器无法共享同一个调度器) 其二:多台机器爬取到的数据无法通过同一个管道对数据进行统一的数据持久出存储.(多台机器无法共享同一个管道) 2.基于scrapy-redis组件的分布式爬虫 ? - scrapy-re

[爬虫学习笔记]C#基于ARSoft.Tools.Net的DNS解析模块(半成品)

      最近在做爬虫的作业,今天学习的内容是关于DNS解析模块的制作的.使用的库为ARSoft.Tools.Net,它是一个非常强大的开源DNS控件库,包含.Net SPF validation, SenderID validation以及DNS Client.DNS Server接口.使用该接口可轻松实现DNS客户请求端及服务器解析端.项目地址:http://arsofttoolsnet.codeplex.com/,Nuget包地址:https://www.nuget.org/packag

python 爬虫学习笔记1

经过一段时间的学习,终于入了门 先爬一个csdn 的blog练练手 整体思路是首先判断某个blog有多少页 然后根据页数 去获得相应的url 再爬出每一页的title和对应的url 这里使用了BeautifulSoup来解析页面 #coding=utf-8 import urllib2 from bs4 import BeautifulSoup import sys reload(sys) sys.setdefaultencoding('utf-8') def query_item(input,

python 网络爬虫学习笔记(一)

为了方便,在Windows下我用了PyCharm,个人感觉这是一款优秀的python学习软件.爬虫,即网络爬虫,大家可以理解为在网络上爬行的一直蜘蛛,互联网就比作一张大网,而爬虫便是在这张网上爬来爬去的蜘蛛咯,如果它遇到资源,那么它就会抓取下来. 学习python爬虫前,先学习下其他的一些知识: (一)url URL,即统一资源定位符,也就是我们说的网址,统一资源定位符是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址.互联网上的每个文件都有一个唯一的URL,

[爬虫学习笔记]MemoryCache缓存的用法学习

      在完成了DNS解析模块之后,我意识到了DNS缓存机制也很有必要.在Redis,Memcache,和.Net自带的Cache之间,考虑到部署问题,最终选择了后者,之前在学习Web及开发的过程中用过System.Web.Caching.Cache这个类库,但是这次的爬虫程序我打算部署为桌面软件,所以选用了System.Runtime.Caching.MemoryCache(后期如有必要也会加入System.Web.Caching.Cache来适配Web端程序).       Memory

WCF学习笔记(基于REST规则方式)

一.WCF的定义 WCF是.NET 3.0后开始引入的新技术,意为基于windows平台的通讯服务. 首先在学习WCF之前,我们也知道他其实是加强版的一个面向服务(SOA)的框架技术. 如果熟悉WebService就会知道WebService是基于XML+XSD,SOAP和WSDL三大技术,当然他也是采用HTTP协议的通信,严格来说WebService是一种面向服务开发的标准.而ASP.NET WebService则是微软平台下的服务. WCF其实一定程度上就是ASP.NET Web Servi

【Python爬虫学习笔记(3)】正则表达式(re模块)相关知识点总结

1. 正则表达式     正则表达式是可以匹配文本片段的模式. 1.1 通配符     正则表达式能够匹配对于一个的字符串,可以使用特殊字符创建这类模式.(图片来自cnblogs) 1.2 特殊字符的转义     由于在正则表达式中,有时需要将特殊字符作为普通字符处理,就需要用'\'进行转义,例如'python\\.org'就会匹配'python.org',那么为什么要用两个反斜杠呢,原因在于需要进行两层转义,首先是re模块表示正则表达式中需要转义一次,其次是python解释器即python的语

神箭手爬虫学习笔记(二)

一,可以使用神剑手已经做好的爬虫市场直接跑,不需要自己定义爬取规则 二,爬虫市场里没有的网站,需要自己去定义规则来爬数据. 三,爬取的数据可以先存放在神剑手,也可以放到七牛暂存.(提醒下,网站需要数据备份如果数量不大可以到七牛做备份,有免费版) 四,爬取的数据可以直接发布到数据库,也可以发布到具有支持神剑插件的网站.可以手动发布,也可以自动发布. 五,可以使用代理IP,防封 六:常用的几个辅助工具:http://docs.shenjianshou.cn/develop/tools/tools.h