摘要
由于互联网的开发性,知识内容获取极其简便。某些平台、博主、机构为了个人虚荣、营销使用不光彩的手段对博主的文章进行剽窃、洗稿、抄袭,对于许多在互联网上发表技术文章、教程的博主来说是十分不公平的;我曾经听到我的一位朋友 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.parser
soup = 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 lcut
import jieba.analyse
import 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 webdriver
from bs4 import BeautifulSoup
from selenium.webdriver.support.ui import WebDriverWait
import glob,os,time,re
from 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 lcut
import jieba.analyse
import 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 条评论)