摘要
由于互联网的开发性,知识内容获取极其简便。某些平台、博主、机构为了个人虚荣、营销使用不光彩的手段对博主的文章进行剽窃、洗稿、抄袭,对于许多在互联网上发表技术文章、教程的博主来说是十分不公平的;我曾经听到我的一位朋友 A 说过,他在面试一位技术时,该技术展示了他的博客文章,这些文章竟然大多数是抄袭 A 的。在如今,博主对于自身劳动成果的保护意识在逐渐加深,但往往由于自身时间不够等其它因素,在对抄袭现象感到愤怒之后便不了了之。
本篇文将会编写一个小工具,随后将会逐步迭代,自动从搜索引擎中获取文章的查询结果,并且与之完成相似度对比,自动编写举报文档等,完成这一部分繁琐的过程,为保护原创增加一份力量。
工具及环境
本篇文章使用 python 作为开发语言、 selenium 作为数据抓取库、余弦相似度作为文本相似度对比方法。
使用 python 作为开发语言的原因主要有以下几点:
- 支持库众多方便在之后的功能扩展中能够增加开发效率 
- 语法简单开发效率高 
使用 selenium 作为 Web 数据抓取库的原因:
第三方依赖库:
- jieba 
- collections 
- selenium 
- BeautifulSoup 
注:
本篇文将使用颜色对不同属性的英文进行标注:
一、使用 selenium 对目标内容进行搜索
1.1 谷歌与火狐浏览器驱动安装提示
使用 selenium 还需要使用驱动对浏览器进行操作,谷歌与火狐浏览器的操作驱动并不一样,并且该驱动需要与你当前所使用的浏览器版本进行匹配,否则将出现错误,以下是两个浏览器驱动的下载地址:
以上驱动下载完毕后还需配置到系统 path 环境中,当然你可以在代码中进行路径匹配从而得到该驱动。在本文中将会默认该驱动已配置到 path 之中;配置方法在网上已经很多文章进行了解释说明,在此不再赘述。
1.2 使用火狐浏览器打开百度搜索页面
selenium 库本质上是操作驱动从而间接的对浏览器进行操作,在这里我们先新建一个 dataspider.py 文件,使用 selenium 打开一个百度搜索界面熟悉 selenium 的使用方法。
 from selenium import webdriver
driver = webdriver.Firefox()url='https://www.baidu.com'driver.get(url)
   复制代码
 
以上代码的第一行从 selenium 引入了 webdriver,之后使用 webdriver 调用 Firefox 生成一个浏览器的操作对象 driver。在此得到了 driver 后,我们可以通过 driver 调用相应的方法对浏览器进行操作。在第五行代码 driver.get(url) 调用了 get 方法,get  接收一个 url 参数,使浏览器打开一个网址,网址为 https://www.baidu.com。此时运行这一段代码,将会看到自动打开火狐浏览器并且访问百度。
1.3 完成目标内容的搜索
在 1.2 节中使用 selenium 进行了目标网址的打开,那么接下来我们将在百度搜索引擎中搜索我们原创文字的关键字,得到对应的搜索结果;在得到结果后,我们可以从搜索出来的结果中进行数据分析:对搜索结果中的文章与我们自己所写的原创文章进行相似度分析,最后再从结果中进行分类。
知道了我们的流程步骤后,我们可以分析一下正常用户在进行搜索时的操作流程。第一步打开搜索页面、第二步输入搜索关键字、第三步点击搜索键,需要模拟这个过程我们应拿到这 3 个操作所对应的 Web 页元素。在这里获取元素可以通过 driver 调用 find_element_by_id 方法,find_element_by_id 通过传入一个元素 id 作为标识,在 Web 页中获取到该元素,从而拿到该元素对象。从该搜索页中,可以得知文本框 input 的 id 为 kw,确认搜索按钮的 id 为 su:
此时我们已经知道了该元素的 id,那我们获取到该元素后该输入什么内容进行测试搜索呢?在此我选择 infoQ 上的 6 个月的文章热榜的榜首 《Java 失宠,谷歌宣布 Kotlin 现在是 Android 开发的首选语言》作为搜索内容,查看百度搜索出来的结果中到底有多少类似的文章:
知道了搜索内容后,我们可以通过获取到的文本框对象调用 send_keys 方法,传入搜索值即可完成在输入框中填入该值,随后获取到了确认按钮对象后,使用该对象调用 click 即可完成搜索任务,完整代码如下:
 from selenium import webdriver
driver = webdriver.Firefox()url='https://www.baidu.com'driver.get(url)
textinput=driver.find_element_by_id('kw')enter=driver.find_element_by_id('su')
textinput.send_keys('Java 失宠,谷歌宣布 Kotlin 现在是 Android 开发的首选语言')enter.click()
   复制代码
 
搜索结果如下:
从搜索结果中我们可以看见,标题有超过  1000000 个类似内容,那抄袭该文的有多少呢?由于本文所编写的工具无法进行过大的内容查询,使用 selenium 在得到了提升开发效率的同时也降低了脚本的运行效率;该工具所面向的用户是个人博主,在某些文章被抄袭时可简单的完成抄袭内容的记录。
此时我们得到了该数据后,接下来就应该对搜索出来的数据进行收集、记录到本地,方便接下来的相似度分析。
1.4 对搜索结果进行记录
我们需要对搜索结果进行记录需要使用 driver 调用 page_source 属性获取整个 Web 页的 HTML 内容,随后对整个 HTML 内容进行解析。对 HTML 进行解析在这里使用一个 HTML 解析工具 BeautifulSoup,BeautifulSoup 提供了多种方法对解析后的 HTML 文本进行指定内容的提取。
首先我们在前几节所新建的 python 文件 dataspider.py 头部追加引入 BeautifulSoup:
 from bs4 import BeautifulSoup
   复制代码
 
在 dataspider.py 文件的代码尾部追加使用 BeautifulSoup 解析 HTML 内容的代码:
 #获取页面源码html=driver.page_source 
#使用 BeautifulSoup 新建解析 html 内容,指定解析器为 html.parsersoup = BeautifulSoup(html, "html.parser") 
   复制代码
 
随后我们查看搜索结果的 Web 页,发现搜索出来的结果都在 class 为 t 的 h3 标签之中,并且对应的 a 标签也存在于 h3 元素之内。
这时我们可以通过使用  的 select 方法对一般指定标记了对应 css 样式的元素对象进行获取。
 search_res=soup.select('.t')
   复制代码
 
此时 search_res 将会得到一个内容为 h3 元素的 html 内容列表。显然,我们想要获取的内容并不是 html,而是对应的 title 文本以及对应的超链接地址。此时我们可以使用遍历取出对应的 a 元素,随后再取 a 元素的 href 属性内容:
 #遍历结果取值for res in search_res:    print(res.a['href'])
   复制代码
 
这个时候运行代码可能会取不到值,原因是我们并未等待浏览器解析就直接获取了页面数据,此时可以使用循环对获取的搜索结果进行判断,若为空则继续获取:
 for i in range(1000):    #获取页面源码    html=driver.page_source    #使用 BeautifulSoup 新建解析 html 内容,指定解析器为 html.parser    soup=BeautifulSoup(html,"html.parser")    search_res=soup.select('.t')    if len(search_res)>0:        break
#遍历结果取值for res in search_res:    print(res.a['href'])
   复制代码
 
我使用的是 vscode,运行脚本后在终端中将会出现对应的超链接内容:
1.5 获取结果的真实链接
此时我们得到的结果并不是搜索出的资源的真实地址,需要进行过滤后提取。
此时只需要访问其地址,得到正确的 url 后关掉其页面即可。
第一步使用 execute_script 执行 js 代码新开一个 Web 页:
 for res in search_res:    jscode = 'window.open("'+res.a['href']+'")'    driver.execute_script(jscode)
   复制代码
 
此时将会打开一个 Web 页,我们需要对 driver 获取操作的浏览器页面对象,须使用 driver 调用 current_window_handle 获取当前句柄作为保留,以便于之后的句柄切换:
 handle_this=driver.current_window_handle # 保留当前句柄
   复制代码
 
随后获取所有的可操作句柄:
 handle_all=driver.window_handles # 获取所有句柄
   复制代码
 
之后就简单的使用 for 对所有的句柄进行遍历,如果不是等于当前的操作页句柄就将其赋予给操作句柄变量即可,此时就可以完成切换 Web 页操作对象:
 handle_exchange=None #要操作的句柄for handle in handle_all:   if handle != handle_this: #非当前句柄就交换        handle_exchange = handle
   复制代码
 
得到句柄之后使用 driver 调用 switch_to 方法传入所需切换操作的 Web 页句柄:
 driver.switch_to.window(handle_exchange)#切换
   复制代码
 
此时已经完成了句柄的切换操作,那么直接使用 driver 调用 current_url 属性即可得到一个真实的目标 url 地址,并且将其存储到一个列表中进行保存即可:
 real_url=driver.current_url
   复制代码
 
接着使用 close 方法关闭当前网页,并且再次使用 switch 方法将句柄切换至原有操作页句柄即可:
 driver.close()driver.switch_to.window(handle_this)
   复制代码
 
其实此时获取真实链接地址的代码并不严谨,因为在进行请求时,得到的真实链接将会有一定的重定向时间,否则无法获取到相应的真实地址,值为 about:blank。解决这个问题可以对当前的浏览器页面 url 值进行判断,若为 about:blank 将继续循环获取该页面值,并且为了防止意外 bug,在超过 100 次循环时则跳出当前循环,并且赋值真实地址为 null,最后将真实地址存入一列表中,修改代码后该部分的完整代码如下:
 link_res=[]#遍历结果取值for res in search_res:    jscode = 'window.open("'+res.a['href']+'")'    driver.execute_script(jscode)    handle_this=driver.current_window_handle    handle_all=driver.window_handles    handle_exchange=None #要操作的句柄
    for handle in handle_all:        if handle != handle_this: #非当前句柄就交换                handle_exchange = handle    driver.switch_to.window(handle_exchange)    real_url=driver.current_url    if real_url=='about:blank':        i=0        while True:            real_url=driver.current_url            if real_url!='about:blank':                break            if i>10000:                real_url='null'                break            i+=1    driver.close()    driver.switch_to.window(handle_this)    link_res.append(real_url)    print(real_url)
   复制代码
 
终端输出结果如下:
1.6 修改 dataspider.py 代码结构
为了方便之后进行扩展,我们对已有的代码结构进行一下优化。
新建一个类名叫 Spider,创建一个 open_engine 方法用于打开浏览器以及搜索引擎页;为这一步编写方法的原因是查重不一定是一个搜索引擎,可能会使用不同的搜索引擎,并且每个搜索引擎的策略可能不一:
 #打开引擎class Spider():  def open_engine(self,driver,engine):    if engine=='baidu':      url='https://www.baidu.com'      driver.get(url)      self.input_id='kw'      self.click_id='su'
   复制代码
 
该方法接收两个参数,分别是 driver 以及 engine;driver 为浏览器对象,engine 为搜索引擎;在方法中指定搜索页链接,随后设定当前搜索框 id 以及搜索按钮的 id。
接下来创建一个 enter_kw 方法用于键入关键字并且进行搜索:
 #键入关键字def enter_kw(self,driver):  textinput=driver.find_element_by_id(self.input_id)  enter=driver.find_element_by_id(self.click_id)  textinput.send_keys('Java 失宠,谷歌宣布 Kotlin 现在是 Android 开发的首选语言')  enter.click()
   复制代码
 
该方法传入了浏览器操作对象 driver,具体代码已有说明不再赘述。
之后新建一个 get_search_link 方法用于分析搜索引擎的结果获取:
 #搜索结果链接def get_search_link(self,driver,engine):  self.open_engine(driver,engine)  self.enter_kw(driver)
  engine_res=[]  for i in range(10000):    engine_res=[]    #获取页面源码    html=driver.page_source    #使用 BeautifulSoup 新建解析 html 内容,指定解析器为 html.parser    soup=BeautifulSoup(html,"html.parser")    engine_res=soup.select('.t')    if len(engine_res)>0:      break      return engine_res 
   复制代码
 
在这个方法中,使用了 open_engine 方法以及 enter_kw 方法。在该方法中使用 for 循环等待页面内容获取,若浏览器加载了该页面,那么就可以获取到 搜索内容,engine_res 列表长度则大于 0,即跳出循环。
最后创建 get_real_link 方法用于获取真实连接:
     #获取 real link    def get_real_link(self,engine_res,driver):        #遍历结果取值        real_res=[]        for res in engine_res:            jscode='window.open("'+res.a['href']+'")'            driver.execute_script(jscode)            handle_this=driver.current_window_handle            handle_all=driver.window_handles            handle_exchange=None #要操作的句柄
            for handle in handle_all:                if handle != handle_this: #非当前句柄就交换                        handle_exchange=handle            driver.switch_to.window(handle_exchange)            real_url=driver.current_url            if real_url=='about:blank':                i=0                while True:                    real_url=driver.current_url                    if real_url!='about:blank':                        break                    if i>10000:                        real_url='null'                        break                    i+=1            driver.close()            driver.switch_to.window(handle_this)            real_res.append(real_url)            #print(real_url)        return real_res
   复制代码
 
最后编写一个 get_search_res 方法用于调用,完整的 Spider 类如下:
 class Spider():        def get_search_res(self,driver,engine):        engine_res=self.get_search_link(driver,engine)        return self.get_real_link(engine_res,driver)        #打开引擎    def open_engine(self,driver,engine):        if engine=='baidu':            url='https://www.baidu.com'            driver.get(url)            self.input_id='kw'            self.click_id='su'        #键入关键字    def enter_kw(self,driver):        textinput=driver.find_element_by_id(self.input_id)        enter=driver.find_element_by_id(self.click_id)        textinput.send_keys('Java 失宠,谷歌宣布 Kotlin 现在是 Android 开发的首选语言')        enter.click()
    #搜索结果链接    def get_search_link(self,driver,engine):        self.open_engine(driver,engine)        self.enter_kw(driver)
        engine_res=[]        for i in range(10000):            engine_res=[]            #获取页面源码            html=driver.page_source            #使用 BeautifulSoup 新建解析 html 内容,指定解析器为 html.parser            soup=BeautifulSoup(html,"html.parser")            engine_res=soup.select('.t')            if len(engine_res)>0:                break        return engine_res        
    #获取 real link    def get_real_link(self,engine_res,driver):        #遍历结果取值        real_res=[]        for res in engine_res:            jscode='window.open("'+res.a['href']+'")'            driver.execute_script(jscode)            handle_this=driver.current_window_handle            handle_all=driver.window_handles            handle_exchange=None #要操作的句柄
            for handle in handle_all:                if handle != handle_this: #非当前句柄就交换                        handle_exchange=handle            driver.switch_to.window(handle_exchange)            real_url=driver.current_url            if real_url=='about:blank':                i=0                while True:                    real_url=driver.current_url                    if real_url!='about:blank':                        break                    if i>10000:                        real_url='null'                        break                    i+=1            driver.close()            driver.switch_to.window(handle_this)            real_res.append(real_url)            #print(real_url)        return real_res      
   复制代码
 
接下来直接新建 Spider 类即可完成页面数据抓取:
 s=Spider()driver=webdriver.Firefox()res=s.get_search_res(driver,'baidu')print(res)
   复制代码
 
二、使用余弦相似度对数据进行对比
2.1 余弦相似度(参考阮一峰)
余弦相似度是一种较为简单的文本相似度对比方法,首先需要进行分词,其次进行词向量计算,最后进行相似度分析。分词我们需要使用 jieba 中文分词库,并且使用 collections 进行次数统计。
余弦相似度使用在文本相似度分析,主要是将句子中的词进行切词,随后去除部分停词后计算剩余词语的词频,随后计算向量;该词频向量是指单一个词语所出现的次数,这个时候文本相似度就变成计算这两个向量之间的相似度。
将两个向量当作这两条线段,我们默认指向不同的方向,若两条线段越接近,或者说形成的夹角越小,那么则可以说明这两个向量之间约接近,或者说是这两个文本之间相似度越近。那么可以得出夹角θ:
例如有两个句子:
我们将句子 1 可以分词为:我、infoQ 、写、很棒、开心、认识、很多、朋友。
我们将句子 2 可以分词为:我、infoQ 、写、文章、认识、朋友、小明、小红、开心、很棒。
之后将词频进行统计:
1:[我:3,infoQ :1,写:1,很棒:1,开心:1,认识:1,很多:1,朋友:1]
2:[我:2,infoQ :1,写:1,文章:1,认识:2,朋友:1,开心:1,很棒:1]
转换为:
1: [2,1,1,1,1,1,1,1]
2: [2,1,1,1,2,1,1,1]
最后套入公式之中为:
(2*2+1*1+1*1+1*1+1*2+1*1+1*1+1*1)/sqrt(sqr(2)+7)*sqrt(sqr(2)+sqr(2)+6)
最终结果如下:
如果给予一个百分比则为是 96% 相似度。
2.2 实现相似度计算
在这一部分中,我们需要新建一个 python 文件名为 Analyse.py,并且引入 jieba 库与 collections。jieba 用于中文分词,collections 用于统计次数。此时在 Analyse 类中添加一个方法用于提取文本中权重的词语:
 #分词
def Count(self,text):
  tag = analyse.textrank(text,topK=20)
  word_counts = collections.Counter(tag) #计数统计
  print(word_counts)
  return word_counts
   复制代码
 
这一步其实就是分词,并且返回权重值 Top 20 的词语,随后使用 Counter 进行计数,方便之后使用。接着添加一个词袋合并,因为我们是两个文本作为对比,我们将其权重 Top 20 的词语进行合并,弱两者极为接近,那么最后在通过合并后的词袋进行向量计算时结果将会相近:
 #词合并def MergeWord(self,T1,T2):  MergeWord = []  for i in T1:    MergeWord.append(i)    for i in T2:      if i not in MergeWord:        MergeWord.append(i)        return MergeWord
   复制代码
 
T1 与 T2 是两个文本中权重 Top 20 的词语,随后合并到 MergeWord 列表中。
接着开始计算向量:
 #得出文档向量def CalVector(self,T1,MergeWord):    TF1 = [0] * len(MergeWord)    for ch in T1:        TermFrequence = T1[ch]        word = ch        if word in MergeWord:            TF1[MergeWord.index(word)] = TermFrequence    return TF1
   复制代码
 
接着我们添加一个方法用于计算相似度(以上以说过如何进行计算,在此就不再赘述):
 def cosine_similarity(self,vector1, vector2):        dot_product = 0.0        normA = 0.0        normB = 0.0
        for a, b in zip(vector1, vector2):#两个向量组合成 [(1, 4), (2, 5), (3, 6)] 最短形式表现            dot_product += a * b                normA += a ** 2            normB += b ** 2        if normA == 0.0 or normB == 0.0:            return 0        else:            return round(dot_product / ((normA**0.5)*(normB**0.5))*100, 2)
   复制代码
 
最后,我们添加一个方法用于调用这些计算步骤方便使用:
 def get_Tfidf(self,text1,text2):        T1 = self.Count(text1)        T2 = self.Count(text2)        mergeword = self.MergeWord(T1,T2)        return self.cosine_similarity(self.CalVector(T1,mergeword),self.CalVector(T2,mergeword))
   复制代码
 
Analyse 类完整代码如下:
 from jieba import lcutimport jieba.analyseimport collections
class Analyse:    def get_Tfidf(self,text1,text2):#测试对比本地数据对比搜索引擎方法        # self.correlate.word.set_this_url(url)        T1 = self.Count(text1)        T2 = self.Count(text2)        mergeword = self.MergeWord(T1,T2)        return self.cosine_similarity(self.CalVector(T1,mergeword),self.CalVector(T2,mergeword))            #分词    def Count(self,text):        tag = jieba.analyse.textrank(text,topK=20)        word_counts = collections.Counter(tag) #计数统计        return word_counts    #词合并    def MergeWord(self,T1,T2):        MergeWord = []        for i in T1:            MergeWord.append(i)        for i in T2:            if i not in MergeWord:                MergeWord.append(i)        return MergeWord    # 得出文档向量    def CalVector(self,T1,MergeWord):        TF1 = [0] * len(MergeWord)        for ch in T1:            TermFrequence = T1[ch]            word = ch            if word in MergeWord:                TF1[MergeWord.index(word)] = TermFrequence        return TF1    #计算 TF-IDF    def cosine_similarity(self,vector1, vector2):        dot_product = 0.0        normA = 0.0        normB = 0.0
        for a, b in zip(vector1, vector2):#两个向量组合成 [(1, 4), (2, 5), (3, 6)] 最短形式表现            dot_product += a * b                normA += a ** 2            normB += b ** 2        if normA == 0.0 or normB == 0.0:            return 0        else:            return round(dot_product / ((normA**0.5)*(normB**0.5))*100, 2)     
   复制代码
 
2.3 本地文本数据获取
此时已编写相似度对比类、浏览器搜索结果获取,接下来我们可以用一种较为简单的方式实现搜索结果的文本对比。
在 InfoQ 中将原文文本复制到当前目录下的 txtsrc 目录(新建目录)保存为 txt 文件,在此为了方便查重使用标题命名为“ Java 失宠,谷歌宣布 Kotlin 现在是 Android 开发的首选语言”:
在 dataspider.py 中编写一个类为 Srcdata 用来获取需要查重的源数据,在该类中添加两个方法,一个用于批量获取文本名,一个用于获取需要查重文章的文本数据:
 class Srcdata():  srcroot=os.getcwd()+r'/txtsrc/'  def getlocaltxt(self):      name=self.gettxtfile()      txt={}      for p in name:          f = open(self.srcroot+p,'r')          txt[p]=f.read()          f.close()      return txt
  def gettxtfile(self):      os.chdir(self.srcroot) # 切换到指定目录      name=[]      for filename in glob.glob("*.txt"):          name.append(filename)      return name
   复制代码
 
以上代码中 srcroot 为指定的文本查重根目录,gettxtfile 方法用于获取 txt 文件名,getlocaltxt 方法用于获取文本数据(此处需要 import os,glob)。
在此获取文本数据的原因是可以准确的消除对比数据的“噪点”,因为在接下的文本相似度对比中,所取的文本数据为 Web 页面数据,会有一部分内容干扰。在此为了最大限度保证准确性,源数据保持了准确度。
不过此方面也可以保证质量,使用 selenium 通过 xpath 可以方便且准确的获取 InfoQ 线上 Web 的文本数据,也可以从搜索结果中挑选出准确的文本数据,但是由于考虑篇幅,将会在之后的迭代版本中实现。
2.4 进行文本相似度对比
此时对于搜索结果的文本,我们可以在获取真实连接后强制一段时间等待浏览器加载数据,随后获取整页数据,并且将原有 real_res 列表改为字典,存储对应链接与文本数据的键值对。在 Spider 类的 get_real_link 方法中修改,如下代码:
 def get_real_link(self,engine_res,driver):        #遍历结果取值        real_res={}#更改为字典(新增)        for res in engine_res:            jscode='window.open("'+res.a['href']+'")'            driver.execute_script(jscode)            handle_this=driver.current_window_handle            handle_all=driver.window_handles            handle_exchange=None #要操作的句柄
            for handle in handle_all:                if handle != handle_this: #非当前句柄就交换                        handle_exchange=handle            driver.switch_to.window(handle_exchange)            real_url=driver.current_url            if real_url=='about:blank':                i=0                while True:                    real_url=driver.current_url                    if real_url!='about:blank':                        break                    if i>10000:                        real_url='null'                        break                    i+=1            time.sleep(5)#强制等待加载(新增)            html_txt=driver.page_source#获取网页数据(新增)            driver.close()            driver.switch_to.window(handle_this)            real_res[real_url]=html_txt#存储键值对(新增)            #print(real_url)        return real_res
   复制代码
 
接下来为了方便测试,直接在 dataspider.py 文件下重新编写调用代码,首先获取本地的文本数据、接着新建浏览器操作对象、创建 Spider 对象以及文本相似度分析对象(需要 from Analyse import Analyse):
 src=Srcdata().getlocaltxt()#获取本地文本driver=webdriver.Firefox()#火狐浏览器s=Spider()#抓取搜索结果及 web 数据als=Analyse()#相似度分析
   复制代码
 
接着创建一个  res_html 列表保存数据,遍历本地文本内容使用文件名作为 关键字 kw 搜索,传入 get_search_res 方法中;get_search_res 已做修改,之后将贴出所有代码以供复现。在传入关键字搜索后,遍历搜索结果,使用 Unicode 提取汉字,最终添加到 res_html 列表中进行保存(正则 import re ):
 res_html=[]for kw in src:    res=s.get_search_res(driver,'baidu',kw)        for k in res:        clean_str = ''.join(re.findall('[\u4e00-\u9fa5]',res[k]))#使用 Unicode 提取汉字        src_str = ''.join(re.findall('[\u4e00-\u9fa5]',src[kw]))#使用 Unicode 提出汉字        #print(k,'相似度:',als.get_Tfidf(src_str,clean_str))        res_html.append([k,kw,als.get_Tfidf(src_str,clean_str)])
   复制代码
 
此时编写一个类,生成 html 文件保存结果(基础代码不再赘述):
 class Analyseres():    def res(self,res_html):        html_str=''        for v in res_html:            html_str+='<h4><a href="'+str(v[0])+'">'+str(v[1])+'</a> 相似度:'+str(v[2])+'</h4>'        self.writefile(html_str)    def writefile(self,html):        filename = './res.html'        with open(filename, 'w') as file_object:            file_object.write(html)            print('save res done')
   复制代码
 
并且在结尾处保存结果:
 wres=Analyseres()wres.res(res_html)
   复制代码
 
运行代码后终端将出现如下结果:
将会生成 res 命名的 html 文件:
2.5 结果分析
我们此时可以通过结果查看低相似度的文章结果是否正确:
在此列出一篇文章作为例子,可以看出两篇文章内容相似度不高,相似度结果可信度比较高:
再此再使用一篇较高相似度的文章进行对比,结果发现文章重复度较高,由此可见该工具对查重有一定的准确度:
三、总结
3.1 改进及优化
以下功能将会在之后的文章中进行迭代:
- 此工具在本地数据获取并不方便,可以编写方法进行扩展,直接获取博主的文章列表,通过 xpath 进行文章数据获取。 
- 在进行 Web 搜索时翻页并未实现,也通过 id 或者 class 获取到元素,随后进行点击翻页。 
- 浏览器查重应考虑多个搜索引擎,通过不同策略完成搜索引擎的配置,从而实现不同搜索引擎的查重,包括微信公众号内容的查重搜索。 
- 在过滤数据时可考虑部分头部平台,在头部平台中的网站可设置策略,自动精确获取文本数据。 
- 白名单设置,部分网站不需要抓取,否则将影响查重数据。 
3.2 完整代码
dataspider.py:
 from selenium import webdriverfrom bs4 import BeautifulSoupfrom selenium.webdriver.support.ui import WebDriverWaitimport glob,os,time,refrom Analyse import Analyse
class Spider():        def get_search_res(self,driver,engine,kw):        engine_res=self.get_search_link(driver,engine,kw)        return self.get_real_link(engine_res,driver)        #打开引擎    def open_engine(self,driver,engine):        if engine=='baidu':            url='https://www.baidu.com'            driver.get(url)            self.input_id='kw'            self.click_id='su'        #键入关键字    def enter_kw(self,driver,kw):        textinput=driver.find_element_by_id(self.input_id)        enter=driver.find_element_by_id(self.click_id)        textinput.send_keys(kw)        enter.click()
    #搜索结果链接    def get_search_link(self,driver,engine,kw):        self.open_engine(driver,engine)        self.enter_kw(driver,kw)
        engine_res=[]        for i in range(10000):            engine_res=[]            #获取页面源码            html=driver.page_source            #使用 BeautifulSoup 新建解析 html 内容,指定解析器为 html.parser            soup=BeautifulSoup(html,"html.parser")            engine_res=soup.select('.t')            if len(engine_res)>0:                break        return engine_res        
    #获取 real link    def get_real_link(self,engine_res,driver):        #遍历结果取值        real_res={}        for res in engine_res:            jscode='window.open("'+res.a['href']+'")'            driver.execute_script(jscode)            handle_this=driver.current_window_handle            handle_all=driver.window_handles            handle_exchange=None #要操作的句柄
            for handle in handle_all:                if handle != handle_this: #非当前句柄就交换                        handle_exchange=handle            driver.switch_to.window(handle_exchange)            real_url=driver.current_url            if real_url=='about:blank':                i=0                while True:                    real_url=driver.current_url                    if real_url!='about:blank':                        break                    if i>10000:                        real_url='null'                        break                    i+=1            time.sleep(5)            html_txt=driver.page_source            driver.close()            driver.switch_to.window(handle_this)            real_res[real_url]=html_txt            #print(real_url)        return real_res
class Srcdata():        srcroot=os.getcwd()+r'/txtsrc/'    def getlocaltxt(self):        name=self.gettxtfile()        txt={}        for p in name:            f = open(self.srcroot+p,'r')            txt[p]=f.read()            f.close()        return txt
    def gettxtfile(self):        os.chdir(self.srcroot) # 切换到指定目录        name=[]        for filename in glob.glob("*.txt"):            name.append(filename)        return name
class Analyseres():    def res(self,res_html):        html_str=''        for v in res_html:            html_str+='<h4><a href="'+str(v[0])+'">'+str(v[1])+'</a> 相似度:'+str(v[2])+'</h4>'        self.writefile(html_str)    def writefile(self,html):        filename = './res.html'        with open(filename, 'w') as file_object:            file_object.write(html)            print('save res done')
src=Srcdata().getlocaltxt()driver=webdriver.Firefox()s=Spider()als=Analyse()
res_html=[]for kw in src:    res=s.get_search_res(driver,'baidu',kw)        for k in res:        clean_str = ''.join(re.findall('[\u4e00-\u9fa5]',res[k]))        src_str = ''.join(re.findall('[\u4e00-\u9fa5]',src[kw]))        #print(k,'相似度:',als.get_Tfidf(src_str,clean_str))        res_html.append([k,kw,als.get_Tfidf(src_str,clean_str)])wres=Analyseres()wres.res(res_html)
   复制代码
 
Analyse.py:
 from jieba import lcutimport jieba.analyseimport collections
class Analyse:    def get_Tfidf(self,text1,text2):#测试对比本地数据对比搜索引擎方法        # self.correlate.word.set_this_url(url)        T1 = self.Count(text1)        T2 = self.Count(text2)        mergeword = self.MergeWord(T1,T2)        return self.cosine_similarity(self.CalVector(T1,mergeword),self.CalVector(T2,mergeword))            #分词    def Count(self,text):        tag = jieba.analyse.textrank(text,topK=20)        word_counts = collections.Counter(tag) #计数统计        return word_counts    #词合并    def MergeWord(self,T1,T2):        MergeWord = []        for i in T1:            MergeWord.append(i)        for i in T2:            if i not in MergeWord:                MergeWord.append(i)        return MergeWord    # 得出文档向量    def CalVector(self,T1,MergeWord):        TF1 = [0] * len(MergeWord)        for ch in T1:            TermFrequence = T1[ch]            word = ch            if word in MergeWord:                TF1[MergeWord.index(word)] = TermFrequence        return TF1    #计算 TF-IDF    def cosine_similarity(self,vector1, vector2):        dot_product = 0.0        normA = 0.0        normB = 0.0
        for a, b in zip(vector1, vector2):#两个向量组合成 [(1, 4), (2, 5), (3, 6)] 最短形式表现            dot_product += a * b                normA += a ** 2            normB += b ** 2        if normA == 0.0 or normB == 0.0:            return 0        else:            return round(dot_product / ((normA**0.5)*(normB**0.5))*100, 2)     
   复制代码
 
评论 (55 条评论)