用Python写了个检测抄袭/文章去重算法

Python技术杂谈 2019-06-03 17:16:37 阅读(11678) 评论(8)

中国人有句话叫“天下文章一大抄”,但是在正规场合下“抄”是要付出代价的,比如考试、写论文是不能抄的,一旦被发现后果相当严重。在互联网出现之前,“抄”很不方便,一是“源”少,而是发布渠道少;而在互联网出现之后,“抄”变得很简单,铺天盖地的“源”源源不断,发布渠道也数不胜数,博客论坛甚至是自建网站,而爬虫还可以让“抄”完全自动化不费劲。这就导致了互联网上的“文章”重复性很高。这里的“文章”只新闻、博客等文字占据绝大部分内容的网页。

用Python写了个检测抄袭/文章去重算法

我在猿人学网站上写了一个《大规模异步新闻爬虫》的Python爬虫教程,里面涉及了如何抓取网页、如何提取正文内容,却没有将如何去重。中文新闻网站的“转载”(其实就是抄)现象非常严重,这种“转载”几乎是全文照抄,或改下标题,或是改下编辑姓名,或是文字个别字修改。所以,对新闻网页的去重很有必要。

 

一、去重算法原理

文章去重(或叫网页去重)是根据文章(或网页)的文字内容来判断多个文章之间是否重复。这是爬虫爬取大量的文本行网页(新闻网页、博客网页等)后要进行的非常重要的一项操作,也是搜索引擎非常关心的一个问题。搜索引擎中抓取的网页是海量的,海量文本的去重算法也出现了很多,比如minihash, simhash等等。

在工程实践中,对simhash使用了很长一段时间,有些缺点,一是算法比较复杂、效率较差;二是准确率一般。

网上也流传着百度采用的一种方法,用文章最长句子的hash值作为文章的标识,hash相同的文章(网页)就认为其内容一样,是重复的文章(网页)。

这个所谓的“百度算法”对工程很友好,但是实际中还是会有很多问题。中文网页的一大特点就是“天下文章一大抄”,各种博文、新闻几乎一字不改或稍作修改就被网站发表了。这个特点,很适合这个“百度算法”。但是,实际中个别字的修改,会导致被转载的最长的那句话不一样,从而其hash值也不一样了,最终结果是,准确率很高,召回率较低。

为了解决这个问题,我提出了nshash(top-n longest sentences hash)算法,即:取文章的最长n句话(实践下来,n=5效果不错)分别做hash值,这n个hash值作为文章的指纹,就像是人的5个手指的指纹,每个指纹都可以唯一确认文章的唯一性。这是对“百度算法”的延伸,准确率还是很高,但是召回率大大提高,原先一个指纹来确定,现在有n个指纹来招回了。

 

二、算法实现

该算法的原理简单,实现起来也不难。比较复杂一点的是对于一篇文章(网页)返回一个similar_id,只要该ID相同则文章相似,通过groupby similar_id即可达到去重目的。

为了记录文章指纹和similar_id的关系,我们需要一个key-value数据库,本算法实现了内存和硬盘两种key-value数据库类来记录这种关系:

HashDBLeveldb 类:基于leveldb实现, 可用于海量文本的去重;

HashDBMemory 类:基于Python的dict实现,可用于中等数量(只要Python的dict不报内存错误)的文本去重。

这两个类都具有get()和put()两个方法,如果你想用Redis或MySQL等其它数据库来实现HashDB,可以参照这两个类的实现进行实现。

HashDBLeveldb类的实现

HashDBMemory类的实现

从效率上看,肯定是HashDBMemory速度更快。利用nshash对17400篇新闻网页内容的测试结果如下:

HashDBLeveldb: 耗时2.47秒;

HashDBMemory: 耗时1.6秒;

具体测试代码请看 example/test.py。

有了这两个类,就可以实现nshash的核心算法了。

首先,对文本进行分句,以句号、感叹号、问号、换行符作为句子的结尾标识,一个正在表达式就可以分好句了。

其次,挑选最长的n句话,分别进行hash计算。hash函数可以用Python自带模块hashlib中的md5, sha等等,也可以用我在爬虫教程中多次提到的farmhash。

最后,我们需要根据这n个hash值给文本内容一个similar_id,通过上面两种HashDB的类的任意一种都可以比较容易实现。其原理就是,similar_id从0开始,从HashDB中查找这n个hash值是否有对应的similar_id,如果有就返回这个对应的similar_id;如果没有,就让当前similar_id加1作为这n个hash值对应的similar_id,将这种对应关系存入HashDB,并返回该similar_id即可。

这个算法实现为NSHash类:

NSHash类的实现

 

三、使用方法


import nshash

nsh = nshash.NSHash(name='test', hashfunc='farmhash', hashdb='memory')

similar_id = nsh.get_similar(doc_text)

NSHash类有三个参数:

  • name: 用于hashdb保存到硬盘的文件名,如果hashdb是HashDBMemory, 则用pickle序列化到硬盘;如果是HashDBLeveldb,则leveldb目录名为:name+’.hashdb’。name按需随便起即可。
  • hashfunc: 计算hash值的具体函数类别,目前实现两种类型:md5farmhash。默认是md5,方便Windows上安装farmhash不方便。
  • hashdb:默认是memory即选择HashDBMemory,否则是HashDBLeveldb。

至于如何利用similar_id进行海量文本的去重,这要结合你如何存储、索引这些海量文本。可参考example/test.py文件。这个test是对excel中保存的新闻网页进行去重的例子。

附:论文查重算法

这个nshash的思想可以运用到论文查重。万方数据、知网等论文网站都有查重功能,你上传你的论文,它们几分钟后就可以在它几千万的论文库中比较出跟你论文相似的论文,并给出一些重复的百分百。其背后的算法,我猜大致和nshash的思想类似。

首先,它要对论文库的所有论文进行索引,以方便后面的查重时快速查找。索引什么呢?就是对每篇论文的句子都做hash,索引这些hash值。具体实现上不一定是一句话一个hash值,可能是ngram分句,比如每10个字符就算一句话。

其次,把你的论文安装上述方法分句计算hash值,在上一步建好的索引中查找你论文句子的hash值,并找到对应的论文ID,然后再把你的论文和找到的论文进行更详细的对比,计算相似百分百等等。

看到查重算法背后的实现,你可能就有了“抄”论文的妙计:把原文的句子主动句变为被动句,添加或删减个别字,用同义词替换等等。这写可能都是逃过查重算法法眼的“窍门”。但是,算法在对“句子”计算hash前做些特别的处理,就可以让你的“窍门”变成“死门”。比如,

主动句变被动句:把“句子”分词后,按照字符串排序,不管你怎么打乱句子,最后经过排序都一样;

加减个别字: 为了让句子意思准确,你加减的字往往是“的,了,吗”等无意义的虚词,也就是停用词。这下有了,排序前先去掉停用词。你加不加都一样;

同义词替换:替换就替换,这事儿我也干,排序前我先做同义词替换,最终大家都换成一样的。

所以说,写论文、写文章还是用心自己写,别偷懒也别想歪门邪道,哈哈哈。

不过,这个nshash是开源哒,你可以随便抄哦。猿人学公众号回复nshash可获取源码。

 

猿人学banner宣传图

我的公众号:猿人学 Python 上会分享更多心得体会,敬请关注。

***版权申明:若没有特殊说明,文章皆是猿人学 yuanrenxue.con 原创,没有猿人学授权,请勿以任何形式转载。***

说点什么吧...

  1. 1楼
    JackyChen78811 5年前 (2019-06-05)

    Great!但是现在绝大部分的查重系统并不是采用你的方法,百度学术就搜罗了一些论文查重网站,这些查重系统实际上都是将句子拆分后扔进百度进行搜索,然后返回结果进行汇总而成

    • 回复
      王平 5年前 (2019-06-05)
      回复 @JackyChen78811 :嗯,借用现有工具能节省不少事
  2. 2楼
    JackyChen78811 5年前 (2019-06-05)

    不过,一篇文档这么多句子,一次性将他们拆分后放进百度进行搜索,他们的方法也许会被百度屏蔽掉,但好像又不会,不知他们是如何做到的

    • 回复
      王平 5年前 (2019-06-06)
      回复 @JackyChen78811 :有个缺陷,如果某些网页没有被百度索引,也是不能完全检测出来的。 另:不被屏蔽要多IP
      • JackyChen78811 5年前 (2019-06-07)
        回复 @王平 :是的,这个缺陷很明显。这两天我就在琢磨着如何做个利用百度或谷歌做个标书比对的,但问题就是一份标书这么多句子,如果拆分句子后放入百度中搜索,IP会被屏蔽,困难就卡在这儿,这个应用场景不常见,只是突然有兴趣做一个这种免费给人使用的小工具,不知你有何高见?
      • 王平 5年前 (2019-06-09)
        回复 @JackyChen78811 :我不了解这个工具的使用人群,所以这个工具是否有用我不知道。
  3. 3楼
    china 5年前 (2019-06-06)

    你图片上编辑器字体怎么配置的? 看起来比较紧凑

    • 回复
      王平 5年前 (2019-06-06)
      回复 @china :在vim里编辑的,自己配置了一下