前言

文章抄袭在互联网中普遍存在,很多博主都收受其烦。近几年随着互联网的发展,抄袭等不道德行为在互联网上愈演愈烈,甚至复制、黏贴后发布标原创屡见不鲜,部分抄袭后的文章甚至标记了一些联系方式从而使读者获取源码等资料。这种恶劣的行为使人愤慨。

本文使用搜索引擎结果作为文章库,再与本地或互联网上数据做相似度对比,实现文章查重;由于查重的实现过程与一般情况下的微博情感分析实现流程相似,从而轻易的扩展出情感分析功能(下一篇将在此篇代码的基础上完成数据采集、清洗到情感分析的整个过程)。

由于近期时间上并不充裕,暂时实现了主要功能,细节上并没有进行优化,但是在代码结构上进行了一些简要的设计,使得之后的功能扩展、升级更为简便。我本人也将会持续更新该工具的功能,争取让这个工具在技术上更加的成熟、实用。

技术

本文实现的查重功能为了考虑适配大多数站点,从而使用selenium用作数据获取,配置不同搜索引擎的信息,实现较为通用的搜索引擎查询,并且不需要考虑过多的动态数据抓取;分词主要使用jieba库,完成对中文语句的分词;使用余弦相似度完成文本相似度的对比并导出对比数据至Excel文章留作举报信息。

微博情感分析基于sklearn,使用朴素贝叶斯完成对数据的情感分析;在数据抓取上,实现流程与文本查重的功能类似。

测试代码获取

CSDN codechina 代码仓库:https://codechina.csdn.net/A757291228/s-analysetooldemo

环境

作者的环境说明如下:

  • 操作系统:Windows7 SP1 64
  • python 版本:3.7.7
  • 浏览器:谷歌浏览器
  • 浏览器版本: 80.0.3987 (64 位)

如有错误欢迎指出,欢迎留言交流。

一、实现文本查重

1.1 selenium安装配置

由于使用的selenium,在使用前需要确保读者是否已安装selenium,使用pip命令,安装如下:

pip install selenium

安装完成 Selenium 还需要下载一个驱动。

  • 谷歌浏览器驱动:驱动版本需要对应浏览器版本,不同的浏览器使用对应不同版本的驱动,点击下载
  • 如果是使用火狐浏览器,查看火狐浏览器版本,点击

    GitHub火狐驱动下载地址

    下载(英文不好的同学右键一键翻译即可,每个版本都有对应浏览器版本的使用说明,看清楚下载即可)

安装了selenium后新建一python文件名为selenium_search,先在代码中引入

from selenium import webdriver

可能有些读者没有把驱动配置到环境中,接下来我们可以指定驱动的位置(博主已配置到环境中):

driver = webdriver.Chrome(executable_path=r'F:\python\dr\chromedriver_win32\chromedriver.exe')

新建一个变量url赋值为百度首页链接,使用get方法传入url地址,尝试打开百度首页,完整代码如下:

from selenium import webdriver

url='https://www.baidu.com'
driver=webdriver.Chrome()
driver.get(url)

在小黑框中使用命令行运行python文件(windows下):



运行脚本后将会打开谷歌浏览器并跳转至百度首页:



这样就成功使用selenium打开了指定网址,接下来将指定搜索关键词查询得到结果,再从结果中遍历到相似数据。

1.2 selenium百度搜索引擎关键词搜索

在自动操控浏览器进行关键字键入到搜索框前,需要获取搜索框元素对象。使用谷歌浏览器打开百度首页,右键搜索框选择查看,将会弹出网页元素(代码)查看视窗,找到搜索框元素(使用鼠标在元素节点中移动,鼠标当前位置的元素节点将会对应的在网页中标蓝):



在html代码中,id的值大多数情况下唯一(除非是打错了),在此选择id作为获取搜索框元素对象的标记。selenium提供了find_element_by_id方法,可以通过传入id获取到网页元素对象。

input=driver.find_element_by_id('kw')

获取元素对象后,使用send_keys方法可传入需要键入的值:

input.send_keys('php基础教程 第十一步 面向对象')

在此我传入了 “php基础教程 第十一步 面向对象”作为关键字作为搜索。运行脚本查看是否在搜索框中键入了关键字。代码如下:

input.send_keys('php基础教程 第十一步 面向对象')

成功打开浏览器并键入了搜索关键字:



现在还差点击“百度一下”按钮完成最终的搜索。使用与查看搜索框相同的元素查看方法查找“百度一下”按钮的id值:



使用find_element_by_id方法获取到该元素对象,随后使用click方法使该按钮完成点击操作:

search_btn=driver.find_element_by_id('su')
search_btn.click()

完整代码如下:

from selenium import webdriver

url='https://www.baidu.com'
driver=webdriver.Chrome()
driver.get(url)
input=driver.find_element_by_id('kw')
input.send_keys('php基础教程 第十一步 面向对象')
search_btn=driver.find_element_by_id('su')
search_btn.click()

浏览器自动完成了键入搜索关键字及搜索功能:

1.3 搜索结果遍历

当前已在浏览器中得到了搜索结果,接下来需要获取整个web页面内容,得到搜索结果。使用selenium并不能很方便的获取到,在这里使用BeautifulSoup对整个web页面进行解析并获取搜索结果。

BeautifulSoup是一个HTML/XML解析器,使用BeautifulSoup会极大的方便我们对整个html的信息获取。

使用BeautifulSoup前需确保已安装。安装命令如下:

pip install BeautifulSoup

安装后,在当前python文件头部引入:

from bs4 import BeautifulSoup

获取html文本可以调用page_source即可:

html=driver.page_source

得到了html代码后,新建BeautifulSoup对象,传入html内容并且指定解析器,这里指定使用 html.parser 解析器:

soup = BeautifulSoup(html, "html.parser")

接下来查看搜索内容,发现所有的结果都由一个h标签包含,并且classt



BeautifulSoup提供了select方法对标签进行获取,支持通过类名、标签名、id、属性、组合查找等。我们发现百度搜索结果中,结果皆有一个class ="t",此时可以通过类名进行遍历获取最为简便:

search_res_list=soup.select('.t')

select方法中传入类名t,在类名前加上一个点(.)表示是通过类名获取元素。

完成这一步后可以添加print尝试打印出结果:

print(search_res_list)

一般情况下,可能输出search_res_list为空列表,这是因为我们在浏览器解析数据渲染到浏览器前已经获取了浏览器当前页的内容,这时有一个简单的方法可以解决这个问题,但是此方法效率却不高,在此只是暂时使用,之后将会用其它效率高于此方法的代码替换(使用time需要在头部引入):

time.sleep(2)

完整代码如下:

from selenium import webdriver
from bs4 import BeautifulSoup
import time url='https://www.baidu.com'
driver=webdriver.Chrome()
driver.get(url)
input=driver.find_element_by_id('kw')
input.send_keys('php基础教程 第十一步 面向对象')
search_btn=driver.find_element_by_id('su')
search_btn.click() time.sleep(2)#在此等待 使浏览器解析并渲染到浏览器 html=driver.page_source #获取网页内容
soup = BeautifulSoup(html, "html.parser")
search_res_list=soup.select('.t')
print(search_res_list)

运行程序将会输出内容:



获取到的结果为所有class为t的标签,包括该标签的子节点,并且使用点(.)运算发可以获取子节点元素。通过浏览器得到的搜索内容皆为链接,点击可跳转,那么只需要获取每一个元素下的a标签即可:

for el in search_res_list:
print(el.a)



从结果中很明显的看出搜索结果的a标签已经获取,那么接下来我们需要的是提取每个a标签内的href超链接。获取href超链接直接使用列表获取元素的方式获取即可:

for el in search_res_list:
print(el.a['href'])

运行脚本成功得到结果:



细心的读者可能会发现,这些获取到的结果中,都是baidu的网址。其实这些网址可以说是“索引”,通过这些索引再次跳转到真实网址。由于这些“索引”不一定会变动,并不利于长期存储,在此还是需要获取到真实的链接。

我们调用js脚本对这些网址进行访问,这些网址将会跳转到真实网址,跳转后再获取当前的网址信息即可。调用execute_script方法可执行js代码,代码如下:

for el in search_res_list:
js = 'window.open("'+el.a['href']+'")'
driver.execute_script(js)

打开新的网页后,需要获取新网页的句柄,否则无法操控新网页。获取句柄的方法如下:

handle_this=driver.current_window_handle#获取当前句柄
handle_all=driver.window_handles#获取所有句柄

获取句柄后需要把当前操作的对象切换成新的页面。由于打开一个页面后所有页面只有2个,简单的使用遍历做一个替换:

handle_exchange=None#要切换的句柄
for handle in handle_all:#不匹配为新句柄
if handle != handle_this:#不等于当前句柄就交换
handle_exchange = handle
driver.switch_to.window(handle_exchange)#切换

切换后,操作对象为当前刚打开的页面。通过current_url属性拿到新页面的url:

real_url=driver.current_url
print(real_url)

随后关闭当前页面,把操作对象置为初始页面:

driver.close()
driver.switch_to.window(handle_this)#换回最初始界面

运行脚本成功获取到真实url:



最后在获取到真实url后使用一个列表将结果存储:

real_url_list.append(real_url)

这一部分完整代码如下:

from selenium import webdriver
from bs4 import BeautifulSoup
import time url='https://www.baidu.com'
driver=webdriver.Chrome()
driver.get(url)
input=driver.find_element_by_id('kw')
input.send_keys('php基础教程 第十一步 面向对象')
search_btn=driver.find_element_by_id('su')
search_btn.click() time.sleep(2)#在此等待 使浏览器解析并渲染到浏览器 html=driver.page_source
soup = BeautifulSoup(html, "html.parser")
search_res_list=soup.select('.t') real_url_list=[]
# print(search_res_list)
for el in search_res_list:
js = 'window.open("'+el.a['href']+'")'
driver.execute_script(js)
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
print(real_url)
real_url_list.append(real_url)#存储结果
driver.close()
driver.switch_to.window(handle_this)

1.4 获取源文本

在当前文件的目录下新建一个文件夹,命名为textsrc,在该目录下创建一个txt文件,把需要对比的文本存放至该文本中。在此我存放的内容为文章“php基础教程 第十一步 面向对象”的内容。



在代码中编写一个函数为获取文本内容:

def read_txt(path=''):
f = open(path,'r')
return f.read()
src=read_txt(r'F:\tool\textsrc\src.txt')

为了方便测试,这里使用是绝对路径。

获取到文本内容后,编写余弦相似度的对比方法。

1.5 余弦相似度

相似度计算参考文章《python实现余弦相似度文本比较》,本人修改一部分从而实现。

本文相似度对比使用余弦相似度算法,一般步骤分为分词->向量计算->计算相似度。

新建一个python文件,名为Analyse。新建一个类名为Analyse,在类中添加分词方法,并在头部引入jieba分词库,以及collections统计次数:

from jieba import lcut
import jieba.analyse
import collections

Count方法:

#分词
def Count(self,text):
tag = jieba.analyse.textrank(text,topK=20)
word_counts = collections.Counter(tag) #计数统计
return word_counts

Count方法接收一个text变量,text变量为文本,使用textrank方法分词并且使用Counter计数。

随后添加MergeWord方法,使词合并方便之后的向量计算:

#词合并
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

最后添加相似度计算方法:

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):#测试对比本地数据对比搜索引擎方法
# 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))

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)

1.6 搜索结果内容与文本做相似度对比

在selenium_search文件中引入Analyse,并且新建对象:

from Analyse import Analyse
Analyse=Analyse()

在遍历搜索结果中添加获取新打开后的页面的网页内容:

time.sleep(5)
html_2=driver.page_source

使用 time.sleep(5)是为了等待浏览器能够有时间渲染当前web内容。获取到新打开的页面内容后,进行相似度对比:

Analyse.get_Tfidf(src,html_2)

由于返回的是一个值,使用print输出:

print('相似度:',Analyse.get_Tfidf(src,html_2))

完整代码如下:

from selenium import webdriver
from bs4 import BeautifulSoup
import time
from Analyse import Analyse def read_txt(path=''):
f = open(path,'r')
return f.read() #获取对比文件
src=read_txt(r'F:\tool\textsrc\src.txt')
Analyse=Analyse() url='https://www.baidu.com'
driver=webdriver.Chrome()
driver.get(url)
input=driver.find_element_by_id('kw')
input.send_keys('php基础教程 第十一步 面向对象')
search_btn=driver.find_element_by_id('su')
search_btn.click() time.sleep(2)#在此等待 使浏览器解析并渲染到浏览器 html=driver.page_source
soup = BeautifulSoup(html, "html.parser")
search_res_list=soup.select('.t') real_url_list=[]
# print(search_res_list)
for el in search_res_list:
js = 'window.open("'+el.a['href']+'")'
driver.execute_script(js)
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 time.sleep(5)
html_2=driver.page_source
print('相似度:',Analyse.get_Tfidf(src,html_2)) print(real_url)
real_url_list.append(real_url)
driver.close()
driver.switch_to.window(handle_this)

运行脚本:



结果显示有几个高度相似的链接,那么这几个就是疑似抄袭的文章了。

以上是完成基本查重的代码,但是相对于说代码比较冗余、杂乱,接下来我们优化一下代码。

二、代码优化

通过以上的程序编程,简要步骤可以分为:获取搜索内容->获取结果->计算相似度。我们可以新建三个类,分别为:Browser、Analyse(已新建)、SearchEngine。

Browser用于搜索、数据获取等;Analyse用于相似度分析、向量计算等;SearchEngine用于不同搜索引擎的基本配置,因为大部分搜多引擎的搜索方式较为一致。

2.1Browser 类

初始化

新建一个python文件,名为Browser,添加初始化方法:

def __init__(self,conf):
self.browser=webdriver.Chrome()
self.conf=conf
self.engine_conf=EngineConfManage().get_Engine_conf(conf['engine']).get_conf()

self.browser=webdriver.Chrome()为新建一个浏览器对象;conf为传入的搜索配置,之后进行搜索内容由编写配置字典实现;self.engine_conf=EngineConfManage().get_Engine_conf(conf['engine']).get_conf()为获取搜索引擎的配置,不同搜索引擎的输入框、搜索按键不一致,通过不同的配置信息实现多搜索引擎搜索。

添加搜索方法

	#搜索内容写入到搜素引擎中
def send_keyword(self):
input = self.browser.find_element_by_id(self.engine_conf['searchTextID'])
input.send_keys(self.conf['kw'])

以上方法中self.engine_conf['searchTextID']self.conf['kw']通过初始化方法得到对应的搜索引擎配置信息,直接获取信息得到元素。

点击搜索

	#搜索框点击
def click_search_btn(self):
search_btn = self.browser.find_element_by_id(self.engine_conf['searchBtnID'])
search_btn.click()

通过使用self.engine_conf['searchBtnID']获取搜索按钮的id。

获取搜索结果与文本

#获取搜索结果与文本
def get_search_res_url(self):
res_link={}
WebDriverWait(self.browser,timeout=30,poll_frequency=1).until(EC.presence_of_element_located((By.ID, "page")))
#内容通过 BeautifulSoup 解析
content=self.browser.page_source
soup = BeautifulSoup(content, "html.parser")
search_res_list=soup.select('.'+self.engine_conf['searchContentHref_class'])
for el in search_res_list:
js = 'window.open("'+el.a['href']+'")'
self.browser.execute_script(js)
handle_this=self.browser.current_window_handle #获取当前句柄
handle_all=self.browser.window_handles #获取所有句柄
handle_exchange=None #要切换的句柄
for handle in handle_all: #不匹配为新句柄
if handle != handle_this: #不等于当前句柄就交换
handle_exchange = handle
self.browser.switch_to.window(handle_exchange) #切换
real_url=self.browser.current_url time.sleep(1)
res_link[real_url]=self.browser.page_source #结果获取 self.browser.close()
self.browser.switch_to.window(handle_this)
return res_link

以上方法跟之前编写的遍历搜索结果内容相似,从中添加了WebDriverWait(self.browser,timeout=30,poll_frequency=1).until(EC.presence_of_element_located((By.ID, "page")))替代了sleep,用于判断EC.presence_of_element_located((By.ID, "page"))是否找到id值为page的网页元素,idpage的网页元素为分页按钮的标签id,如果未获取表示当前web页并未加载完全,等待时间为timeout=3030秒,如果已过去则跳过等待。

以上代码中并不做相似度对比,而是通过 res_link[real_url]=self.browser.page_source 将内容与url存入字典,随后返回,之后再做相似度对比,这样编写利于之后的功能扩展。

打开目标搜索引擎进行搜索

	#打开目标搜索引擎进行搜索
def search(self):
self.browser.get(self.engine_conf['website']) #打开搜索引擎站点
self.send_keyword() #输入搜索kw
self.click_search_btn() #点击搜索
return self.get_search_res_url() #获取web页搜索数据

最后添加一个search方法,直接调用search方法即可实现之前的所有操作,不用暴露过多简化使用。

完整代码如下:

from selenium import webdriver
from bs4 import BeautifulSoup
from SearchEngine import EngineConfManage
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import time class Browser:
def __init__(self,conf):
self.browser=webdriver.Chrome()
self.conf=conf
self.engine_conf=EngineConfManage().get_Engine_conf(conf['engine']).get_conf()
#搜索内容写入到搜素引擎中
def send_keyword(self):
input = self.browser.find_element_by_id(self.engine_conf['searchTextID'])
input.send_keys(self.conf['kw'])
#搜索框点击
def click_search_btn(self):
search_btn = self.browser.find_element_by_id(self.engine_conf['searchBtnID'])
search_btn.click()
#获取搜索结果与文本
def get_search_res_url(self):
res_link={}
WebDriverWait(self.browser,timeout=30,poll_frequency=1).until(EC.presence_of_element_located((By.ID, "page")))
#内容通过 BeautifulSoup 解析
content=self.browser.page_source
soup = BeautifulSoup(content, "html.parser")
search_res_list=soup.select('.'+self.engine_conf['searchContentHref_class'])
for el in search_res_list:
js = 'window.open("'+el.a['href']+'")'
self.browser.execute_script(js)
handle_this=self.browser.current_window_handle #获取当前句柄
handle_all=self.browser.window_handles #获取所有句柄
handle_exchange=None #要切换的句柄
for handle in handle_all: #不匹配为新句柄
if handle != handle_this: #不等于当前句柄就交换
handle_exchange = handle
self.browser.switch_to.window(handle_exchange) #切换
real_url=self.browser.current_url time.sleep(1)
res_link[real_url]=self.browser.page_source #结果获取 self.browser.close()
self.browser.switch_to.window(handle_this)
return res_link #打开目标搜索引擎进行搜索
def search(self):
self.browser.get(self.engine_conf['website']) #打开搜索引擎站点
self.send_keyword() #输入搜索kw
self.click_search_btn() #点击搜索
return self.get_search_res_url() #获取web页搜索数据

2.2SearchEngine 类

SearchEngine类主要用于不同搜索引擎的配置编写。更加简便的实现搜索引擎或相似业务的扩展。

#搜索引擎配置
class EngineConfManage:
def get_Engine_conf(self,engine_name):
if engine_name=='baidu':
return BaiduEngineConf()
elif engine_name=='qihu360':
return Qihu360EngineConf()
elif engine_name=='sougou':
return SougouEngineConf() class EngineConf:
def __init__(self):
self.engineConf={}
def get_conf(self):
return self.engineConf class BaiduEngineConf(EngineConf):
engineConf={}
def __init__(self):
self.engineConf['searchTextID']='kw'
self.engineConf['searchBtnID']='su'
self.engineConf['nextPageBtnID_xpath_f']='//*[@id="page"]/div/a[10]'
self.engineConf['nextPageBtnID_xpath_s']='//*[@id="page"]/div/a[11]'
self.engineConf['searchContentHref_class']='t'
self.engineConf['website']='http://www.baidu.com' class Qihu360EngineConf(EngineConf):
def __init__(self):
pass class SougouEngineConf(EngineConf):
def __init__(self):
pass

在此只实现了百度搜索引擎的配置编写。所有不同种类的搜索引擎继承EngineConf基类,使子类都有了get_conf方法。EngineConfManage类用于不同搜索引擎的调用,传入引擎名即可。

2.3如何使用

首先引入两个类:

from Browser import Browser
from Analyse import Analyse

新建一个方法读取本地文件:

def read_txt(path=''):
f = open(path,'r')
return f.read()

获取文件并新建数据分析类:

src=read_txt(r'F:\tool\textsrc\src.txt')#获取本地文本
Analyse=Analyse()

配置信息字典编写:

#配置信息
conf={
'kw':'php基础教程 第十一步 面向对象',
'engine':'baidu',
}

新建Browser类,并传入配置信息:

drvier=Browser(conf)

获取搜索结果及内容

url_content=drvier.search()#获取搜索结果及内容

遍历结果及计算相似度:

for k in url_content:
print(k,'相似度:',Analyse.get_Tfidf(src,url_content[k]))

完整代码如下:

from Browser import Browser
from Analyse import Analyse def read_txt(path=''):
f = open(path,'r')
return f.read() src=read_txt(r'F:\tool\textsrc\src.txt')#获取本地文本
Analyse=Analyse() #配置信息
conf={
'kw':'php基础教程 第十一步 面向对象',
'engine':'baidu',
} drvier=Browser(conf)
url_content=drvier.search()#获取搜索结果及内容
for k in url_content:
print(k,'相似度:',Analyse.get_Tfidf(src,url_content[k]))

是不是感觉舒服多了?简直不要太清爽。你以为这就完了吗?还没完,接下来扩展一下功能。

三、功能扩展

暂时这个小工具的功能只有查重这个基础功能,并且这个存在很多问题。如没有白名单过滤、只能查一篇文章的相似度、如果比较懒也没有直接获取文章列表自动查重的功能以及结果导出等。接下来慢慢完善部分功能,由于篇幅关系并不完全把的功能实现在此列出,之后将会持续更新。

3.1自动获取文本

新建一个python文件,名为FileHandle。该类用于自动获取指定目录下txt文件,txt文件文件名为关键字,内容为该名称的文章内容。类代码如下:

import os

class FileHandle:
#获取文件内容
def get_content(self,path):
f = open(path,"r") #设置文件对象
content = f.read() #将txt文件的所有内容读入到字符串str中
f.close() #将文件关闭
return content
#获取文件内容
def get_text(self):
file_path=os.path.dirname(__file__) #当前文件所在目录
txt_path=file_path+r'\textsrc' #txt目录
rootdir=os.path.join(txt_path) #目标目录内容
local_text={}
# 读txt 文件
for (dirpath,dirnames,filenames) in os.walk(rootdir):
for filename in filenames:
if os.path.splitext(filename)[1]=='.txt':
flag_file_path=dirpath+'\\'+filename #文件路径
flag_file_content=self.get_content(flag_file_path) #读文件路径
if flag_file_content!='':
local_text[filename.replace('.txt', '')]=flag_file_content #键值对内容
return local_text

其中有两个方法get_contentget_textget_text为获取目录下所有txt文件路径,通过get_content获取到详细文本内容,返回local_textlocal_text键为文件名,值为文本内容。

3.2BrowserManage类

Browser类文件中添加一个BrowserManage类继承于Browser,添加方法:

#打开目标搜索引擎进行搜索
def search(self):
self.browser.get(self.engine_conf['website']) #打开搜索引擎站点
self.send_keyword() #输入搜索kw
self.click_search_btn() #点击搜索
return self.get_search_res_url() #获取web页搜索数据

添加该类使Browser类的逻辑与其它方法分开,便于扩展。

3.3Browser类的扩展

Browser类中添加下一页方法,使搜索内容时能够获取更多内容,并且可指定获取结果条数:

#下一页
def click_next_page(self,md5):
WebDriverWait(self.browser,timeout=30,poll_frequency=1).until(EC.presence_of_element_located((By.ID, "page")))
#百度搜索引擎翻页后下一页按钮 xpath 不一致 默认非第一页xpath
try:
next_page_btn = self.browser.find_element_by_xpath(self.engine_conf['nextPageBtnID_xpath_s'])
except:
next_page_btn = self.browser.find_element_by_xpath(self.engine_conf['nextPageBtnID_xpath_f'])
next_page_btn.click()
#md5 进行 webpag text 对比,判断是否已翻页 (暂时使用,存在bug)
i=0
while md5==hashlib.md5(self.browser.page_source.encode(encoding='UTF-8')).hexdigest():#md5 对比
time.sleep(0.3)#防止一些错误,暂时使用强制停止保持一些稳定
i+=1
if i>100:
return False
return True

百度搜索引擎翻页后下一页按钮 xpath 不一致 默认非第一页xpath,出现异常使用另外一个xpath。随后对页面进行md5,对比md5值,如果当前页面没有刷新,md5值将不会改变,等待小短时间之后点击下一页。

3.4get_search_res_url方法的修改

get_search_res_url方法的修改了部分内容,添加了增加结果条数指定、下一页内容获取以及白名单设置更改过后的代码如下:

#获取搜索结果与文本
def get_search_res_url(self):
res_link={}
WebDriverWait(self.browser,timeout=30,poll_frequency=1).until(EC.presence_of_element_located((By.ID, "page")))
#内容通过 BeautifulSoup 解析
content=self.browser.page_source
soup = BeautifulSoup(content, "html.parser")
search_res_list=soup.select('.'+self.engine_conf['searchContentHref_class'])
while len(res_link)<self.conf['target_page']:
for el in search_res_list:
js = 'window.open("'+el.a['href']+'")'
self.browser.execute_script(js)
handle_this=self.browser.current_window_handle #获取当前句柄
handle_all=self.browser.window_handles #获取所有句柄
handle_exchange=None #要切换的句柄
for handle in handle_all: #不匹配为新句柄
if handle != handle_this: #不等于当前句柄就交换
handle_exchange = handle
self.browser.switch_to.window(handle_exchange) #切换
real_url=self.browser.current_url
if real_url in self.conf['white_list']: #白名单
continue
time.sleep(1)
res_link[real_url]=self.browser.page_source #结果获取 self.browser.close()
self.browser.switch_to.window(handle_this)
content_md5=hashlib.md5(self.browser.page_source.encode(encoding='UTF-8')).hexdigest() #md5对比
self.click_next_page(content_md5)
return res_link

while len(res_link)<self.conf['target_page']:为增加了对结果条数的判断。

content_md5=hashlib.md5(self.browser.page_source.encode(encoding='UTF-8')).hexdigest() #md5对比
self.click_next_page(content_md5)

以上代码增加了当前页面刷新后的md5值判断,不一致则进行跳转。

if real_url in self.conf['white_list']:         #白名单
continue

以上代码对白名单进行了判断,自己设置的白名单不加入到条数。

3.5新建Manage类

新建一python文件名为Manage,再次封装。代码如下:

from Browser import BrowserManage
from Analyse import Analyse
from FileHandle import FileHandle class Manage:
def __init__(self,conf):
self.drvier=BrowserManage(conf)
self.textdic=FileHandle().get_text()
self.analyse=Analyse()
def get_local_analyse(self):
resdic={} for k in self.textdic:
res={}
self.drvier.set_kw(k)
url_content=self.drvier.search()#获取搜索结果及内容
for k1 in url_content:
res[k1]=self.analyse.get_Tfidf(self.textdic[k],url_content[k1])
resdic[k]=res
return resdic

以上代码初始化方法接收一个参数,且初始化方法中新建了BrowserManage对象、Analyse对象以及获取了文本内容。

get_local_analyse方法遍历文本,使用文件名当作关键字进行搜索,并且将搜索内容与当前文本做相似度对比,最后返回结果。

结果如下:



博主目录下文件如下:



相似度分析部分以上为主要内容,工具之后将会丢GitHubcsdn的代码仓库中,使用的无头模式,本篇所讲的内容为一般实现。

所有完整的代码如下

Analyse类:

from jieba import lcut
import jieba.analyse
import collections
from FileHandle import FileHandle 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)

Browser类:

from selenium import webdriver
from bs4 import BeautifulSoup
from SearchEngine import EngineConfManage
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import hashlib
import time
import xlwt class Browser:
def __init__(self,conf):
self.browser=webdriver.Chrome()
self.conf=conf
self.conf['kw']=''
self.engine_conf=EngineConfManage().get_Engine_conf(conf['engine']).get_conf()
#搜索内容设置
def set_kw(self,kw):
self.conf['kw']=kw
#搜索内容写入到搜素引擎中
def send_keyword(self):
input = self.browser.find_element_by_id(self.engine_conf['searchTextID'])
input.send_keys(self.conf['kw'])
#搜索框点击
def click_search_btn(self):
search_btn = self.browser.find_element_by_id(self.engine_conf['searchBtnID'])
search_btn.click()
#获取搜索结果与文本
def get_search_res_url(self):
res_link={}
WebDriverWait(self.browser,timeout=30,poll_frequency=1).until(EC.presence_of_element_located((By.ID, "page")))
#内容通过 BeautifulSoup 解析
content=self.browser.page_source
soup = BeautifulSoup(content, "html.parser")
search_res_list=soup.select('.'+self.engine_conf['searchContentHref_class'])
while len(res_link)<self.conf['target_page']:
for el in search_res_list:
js = 'window.open("'+el.a['href']+'")'
self.browser.execute_script(js)
handle_this=self.browser.current_window_handle #获取当前句柄
handle_all=self.browser.window_handles #获取所有句柄
handle_exchange=None #要切换的句柄
for handle in handle_all: #不匹配为新句柄
if handle != handle_this: #不等于当前句柄就交换
handle_exchange = handle
self.browser.switch_to.window(handle_exchange) #切换
real_url=self.browser.current_url
if real_url in self.conf['white_list']: #白名单
continue
time.sleep(1)
res_link[real_url]=self.browser.page_source #结果获取 self.browser.close()
self.browser.switch_to.window(handle_this)
content_md5=hashlib.md5(self.browser.page_source.encode(encoding='UTF-8')).hexdigest() #md5对比
self.click_next_page(content_md5)
return res_link
#下一页
def click_next_page(self,md5):
WebDriverWait(self.browser,timeout=30,poll_frequency=1).until(EC.presence_of_element_located((By.ID, "page")))
#百度搜索引擎翻页后下一页按钮 xpath 不一致 默认非第一页xpath
try:
next_page_btn = self.browser.find_element_by_xpath(self.engine_conf['nextPageBtnID_xpath_s'])
except:
next_page_btn = self.browser.find_element_by_xpath(self.engine_conf['nextPageBtnID_xpath_f'])
next_page_btn.click()
#md5 进行 webpag text 对比,判断是否已翻页 (暂时使用,存在bug)
i=0
while md5==hashlib.md5(self.browser.page_source.encode(encoding='UTF-8')).hexdigest():#md5 对比
time.sleep(0.3)#防止一些错误,暂时使用强制停止保持一些稳定
i+=1
if i>100:
return False
return True
class BrowserManage(Browser):
#打开目标搜索引擎进行搜索
def search(self):
self.browser.get(self.engine_conf['website']) #打开搜索引擎站点
self.send_keyword() #输入搜索kw
self.click_search_btn() #点击搜索
return self.get_search_res_url() #获取web页搜索数据

Manage类:

from Browser import BrowserManage
from Analyse import Analyse
from FileHandle import FileHandle class Manage:
def __init__(self,conf):
self.drvier=BrowserManage(conf)
self.textdic=FileHandle().get_text()
self.analyse=Analyse()
def get_local_analyse(self):
resdic={} for k in self.textdic:
res={}
self.drvier.set_kw(k)
url_content=self.drvier.search()#获取搜索结果及内容
for k1 in url_content:
res[k1]=self.analyse.get_Tfidf(self.textdic[k],url_content[k1])
resdic[k]=res
return resdic

FileHandle类:

import os

class FileHandle:
#获取文件内容
def get_content(self,path):
f = open(path,"r") #设置文件对象
content = f.read() #将txt文件的所有内容读入到字符串str中
f.close() #将文件关闭
return content
#获取文件内容
def get_text(self):
file_path=os.path.dirname(__file__) #当前文件所在目录
txt_path=file_path+r'\textsrc' #txt目录
rootdir=os.path.join(txt_path) #目标目录内容
local_text={}
# 读txt 文件
for (dirpath,dirnames,filenames) in os.walk(rootdir):
for filename in filenames:
if os.path.splitext(filename)[1]=='.txt':
flag_file_path=dirpath+'\\'+filename #文件路径
flag_file_content=self.get_content(flag_file_path) #读文件路径
if flag_file_content!='':
local_text[filename.replace('.txt', '')]=flag_file_content #键值对内容
return local_text

本文最终使用方法如下:

from Manage import Manage

white_list=['blog.csdn.net/A757291228','www.cnblogs.com/1-bit','blog.csdn.net/csdnnews']#白名单
#配置信息
conf={
'engine':'baidu',
'target_page':5
'white_list':white_list,
} print(Manage(conf).get_local_analyse())

python 手把手教你基于搜索引擎实现文章查重的更多相关文章

  1. Delphi - 手把手教你基于D7+Access常用管理系统架构的设计与实现 (更新中)

    前言 从事软件开发工作好多年了,学的越深入越觉得自己无知,所以还是要对知识保持敬畏之心,活到老,学到老! 健身和代码一样都不能少,身体是革命的本钱,特别是我们这种高危工种,所以小伙伴们运动起来!有没有 ...

  2. 用Python手把手教你搭一个Transformer!

    来源商业新知网,原标题:百闻不如一码!手把手教你用Python搭一个Transformer 与基于RNN的方法相比,Transformer 不需要循环,主要是由Attention 机制组成,因而可以充 ...

  3. 用Python手把手教你搭建一个web框架-flask微框架!

    在之前的文章当中,小编已经教过大家怎么搭建一个Django框架,今天我们来探索另外的一种框架的搭建,这个框架就是web框架-flask微框架啦!首先我们带着以下的几个问题来阅读本文: 1.flask是 ...

  4. 手把手教你基于Netty实现一个基础的RPC框架(通俗易懂)

    阅读这篇文章之前,建议先阅读和这篇文章关联的内容. [1]详细剖析分布式微服务架构下网络通信的底层实现原理(图解) [2][年薪60W的技巧]工作了5年,你真的理解Netty以及为什么要用吗?(深度干 ...

  5. 手把手教你基于SqlSugar4编写一个可视化代码生成器(生成实体,以SqlServer为例,文末附源码)

    在开发过程中免不了创建实体类,字段少的表可以手动编写,但是字段多还用手动创建的话不免有些浪费时间,假如一张表有100多个字段,手写有些不现实. 这时我们会借助一些工具,如:动软代码生成器.各种ORM框 ...

  6. 手把手教你基于C#开发WinCC语音报警插件「附源代码」

    写在前面 众所周知,WinCC本身是可以利用C脚本或者VBS脚本来做语音报警,但是这种方式的本质是调用已存在的音频文件,想要实现实时播报报警信息是不行的,灵活性还不够,本文主要介绍基于C#/.NET开 ...

  7. 手把手教你基于koa2,mongoose实现增删改查

    初始化项目 npm init -y 先安装一波乱七八糟的依赖插件(需要具备一定的koa2知识,至于mongoDB自行百度安装教程),模板引擎我使用的是art-template(据说是性能最好的,而且是 ...

  8. 手把手教你基于CentOS8搭建微信订阅号后台服务(一)

    一.准备域名并完成解析 关于域名,我买的是阿里的一个1元/年的廉价域名,同时国内域名都需要备案,当时在这里耽搁了挺久的. 域名解析的话,在阿里云官方帮助文档里有.传送门:https://help.al ...

  9. 利用python deque的extend特性实现列表元素查重

    from collections import deque mydquene = deque() mylist = [0,1,1,2,2,3,3,3,3,4,5,6,7,7,8,8,9,10,10,1 ...

随机推荐

  1. C#算法设计排序篇之06-堆排序(附带动画演示程序)

    堆排序(Heap Sort) 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/685 访问. 堆排序是指利用堆积树(堆)这 ...

  2. Vue 大量data及rules的data选项结构组织

    如果Vue文件需要很多的data成员及表单验证,建议使用类似结构 export default{ data(){ const model = { username: 'suzhen', passwor ...

  3. 极简 Node.js 入门 - 2.2 事件

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  4. 个性探测综述阅读笔记——Recent trends in deep learning based personality detection

    目录 abstract 1. introduction 1.1 个性衡量方法 1.2 应用前景 1.3 伦理道德 2. Related works 3. Baseline methods 3.1 文本 ...

  5. Jmeter 常用函数(6)- 详解 __P

    如果你想查看更多 Jmeter 常用函数可以在这篇文章找找哦 https://www.cnblogs.com/poloyy/p/13291704.html 作用 和 __property 作用一样,不 ...

  6. Dubbo系列之 (五)服务订阅(2)

    辅助链接 Dubbo系列之 (一)SPI扩展 Dubbo系列之 (二)Registry注册中心-注册(1) Dubbo系列之 (三)Registry注册中心-注册(2) Dubbo系列之 (四)服务订 ...

  7. 数据分析-RFM模型用户分析

    RFM模型 根据美国数据库营销研究所Arthur Hughes的研究,客户数据库中有3个神奇的要素,这3个要素构成了数据分析最好的指标: 最近一次消费 (Recency) 消费频率 (Frequenc ...

  8. MPI组操作

    进程组的创建 MPI_Comm_Group int MPI_Comm_group( MPI_Comm comm, MPI_Group *group ); 把相同的通信子进程放到一个组内. #inclu ...

  9. SpringCloud系列之Nacos应用篇

    前言 原先项目是以SpringConfig作为项目配置中心组件,Eureka作为服务注册发现组件,基本上就是SpringCloud全家桶,Eureka已经停更,所以前期调研可替换方案,主流替换方案有C ...

  10. Docker 私有镜像仓库的搭建及认证

    DockerHub 为我们提供了很多官方镜像和个人上传的镜像,我们可以下载机构或个人提供的镜像,也可以上传我们自己的本地镜像,但缺点是: 由于网络的原因,从 DockerHub 下载和上传镜像速度可能 ...