文章目录

  • 前言
  • 1. 爬取百度文章
    • 1.1 网页分析
    • 1.2 代码实现
    • 1.3 代码测试
  • 2. 爬取微信公众号文章
    • 2.1 网页分析
    • 2.2 反爬分析
    • 2.3 代码实现
    • 2.4 代码测试
  • 3. 模型训练
  • 4. 相似度分析
  • 5. 剩下的就交给客服吧
  • 结束语

前言

  免责声明:
    本篇博文的初衷是分享自己学习逆向分析时的个人感悟,所涉及的内容仅供学习、交流,请勿将其用于非法用途!!!任何由此引发的法律纠纷均与作者本人无关,请自行负责!!!
  版权声明:
    未经作者本人授权,禁止转载!!!


  没想到,我的博客竟然被抄袭了(•́へ•́╬),文章一丢丢都不改,原封不动的照搬,更过分的是,将作者的名字直接改成他自己的名字,然后美名其曰:“原创”;还有个网站在未经授权的情况下,直接连作者名字都抄,也不标注来源。。。这种行为本人极其鄙视。虽然客服及时做了处理,将侵权的文章删除了,但这样的惩罚措施未免也太轻了些,抄袭者没有付出什么代价,而且抄袭对他们来说百利而无一害,没有直接封号来的实在,希望这方面的制度继续完善。

  本篇博文就是在这样的背景下,分享一个博客查重脚本,通过对全网进行搜索(包括微信公众号在内),然后用doc2vec将网页向量化,通过余弦相似度对相关网页进行相似度分析,对相似度极高的网页进一步核实,一旦发现抄袭,就联系客服进行版权申诉。
  目标:通过输入关键词信息,在百度和微信公众号分别进行搜索,将相关文章保存到本地,然后再进行相似度分析,输出相似度高的文章链接,以便后面的版权申诉。
  工具:Google Chrome、Charles、PyCharm

1. 爬取百度文章

  这里以我的这篇文章《Python爬虫:逆向分析网易云音乐加密参数》为例,进行查重。

1.1 网页分析

  首先我们在百度上输入关键词网易云爬虫进行搜索,搜索结果有九百多万条,为了方便我们这里选择近一个月发表过的文章,操作就是点击右侧的搜索工具,时间选择一个月内,如下图:


  然后我们打开Chrome的调试面板,相关信息是直接放在网页上的:


  一页有十篇文章,它们的信息都放在一个id="content_left"div标签里,每篇文章也对应一个div,它们的区别是id不同,id是文章的序号,这里我们打开第一个,即id=1div标签:


  我们点击箭头标记的连接就会跳转到这篇文章,它的真实urlhttps://blog.csdn.net/weixin_39190897/article/details/108327884


  这篇文章是CSDN网站上的,文章的内容在一个id="article_content"div标签里。但是我们的搜索是进行全网搜索,搜索结果里除了有CSDN里的文章外,还有博客园、知乎、简书、哔哩哔哩、GitHub等其他网站的内容,如果我们这样对内容进行精确提取的话,需要为每个网站进行标签分析,这样未免太繁琐了。我的方法是通过正则表达式去除网页源代码,然后对剩余的内容进行保存,其实只要正则表达式写的好些,基本上就可以将文本提取出来了。


  大致流程就是上面说的,这里说一下具体如何实现:
  (1) 需要对搜索结果进行一次筛选,筛选的条件是一个月内,即先找到搜索工具的按钮,然后左移一定的距离到时间选项,然后再向下移动一定距离选择一个月内。这里使用selenium库中的动作链来模拟此动作,代码如下:

    def check(self, browser):"""只检索最近一个月的文章:param browser::return:"""# 找到搜索工具按钮action1 = browser.find_element_by_css_selector('.search_tool')actions = ActionChains(browser)actions.click(action1)# 左移actions.move_by_offset(-495, 0).click()# 下移actions.move_by_offset(0, 110).click()actions.perform()time.sleep(2)return browser

  (2) 需要实现翻页的功能,即要找到下一页按钮所对应的idclass,因为上一页下一页的按钮所对应的class是相同的,所以还需要一个判断,代码如下:

 next_page = browser.find_elements_by_css_selector('.n')[-1]if next_page.text == '下一页 >':next_page.click()

  (3) 需要对网页内容进行一次粗提取,使用正则表达式,然后将提取的结果进行保存。

  使用selenium时要注意加一个延时,否则浏览器可能反应不过来,会导致下一步操作出现异常错误。

1.2 代码实现

# -*- coding: utf-8 -*-
# @Time    : 2020/9/15 15:20
# @Author  : XiaYouRan
# @Email   : youran.xia@foxmail.com
# @File    : CSDN.py
# @Software: PyCharmfrom bs4 import BeautifulSoup
import requests
from selenium import webdriver
from selenium.webdriver import ActionChains
import time
import re
import osclass BauDu(object):def __init__(self):self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ''(KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}self.input_kw = input('请输入要搜索的关键字: ')def search(self):"""搜索:return:"""url = 'http://www.baidu.com'browser = webdriver.Chrome()browser.get(url)time.sleep(2)# from selenium.webdriver.common.by import By# input = browser.find_element(By.ID, 'kw')# input = browser.find_elements_by_id('kw')input = browser.find_element_by_css_selector('#kw')input.send_keys(self.input_kw)button = browser.find_element_by_css_selector('#su')button.click()time.sleep(3)return browserdef check(self, browser):"""只检索最近一个月的文章:param browser::return:"""# 找到搜索工具按钮action1 = browser.find_element_by_css_selector('.search_tool')actions = ActionChains(browser)actions.click(action1)# 左移actions.move_by_offset(-495, 0).click()# 下移actions.move_by_offset(0, 110).click()actions.perform()time.sleep(2)return browserdef parser(self, browser):"""解析出文章的url:param browser::return:"""html = browser.page_sourcesoup = BeautifulSoup(html, 'lxml')text_list = soup.find_all(class_='result c-container new-pmd')urls = []for text in text_list:tag_a = text.h3.atitle = tag_a.get_text()url = tag_a['href']urls.append(url)# print(title, url)return urlsdef getAllurl(self, browser):"""提取所有结果的url:param browser: :return: """url_list = []count = 1while True:print('{:*^30}'.format('正在抓取第%d页' % count))url = baidu.parser(browser)url_list.extend(url)try:# 下一页next_page = browser.find_elements_by_css_selector('.n')[-1]if next_page.text == '下一页 >':next_page.click()time.sleep(3)count += 1else:breakexcept Exception as err:print(err)breakbrowser.close()return url_listdef getText(self, url):"""提取文章:param url: :return: """try:response = requests.get(url, headers=self.headers)response.raise_for_status()response.encoding = 'utf-8'return response.url, response.textexcept Exception as err:# print(err)# print('请求异常')return '', ''def saveArticle(self, url_list):"""保存文章:param url_list: :return: """for index in range(len(url_list)):# 开始整处理网页blog_url, blog_text = baidu.getText(url_list[index])if blog_text == '找不到页面' or blog_text == '':# 内容为空, 该url请求失败, 内容有可能是被删了404continue# 正则提取pattern = re.compile('[a-zA-Z0-9</>":.;!!=_#&@\\\?\[\]()(),:+,\'%《》$、。\s\|\{\}\*\-?【】“”‘’~\^]')blog_text = re.sub(pattern, '', blog_text)if not os.path.exists('./articleBaidu'):os.mkdir('./articleBaidu')with open('./articleBaidu/{}.txt'.format(str(index).zfill(5)), 'w', encoding='gb18030') as f:f.write(blog_url + '\n' + blog_text)if (index + 1) % 10 == 0:print('已保存: %d页' % (index + 1))

1.3 代码测试

  测试代码如下:

if __name__ == '__main__':baidu = BauDu()browser = baidu.search()browser = baidu.check(browser)url_list = baidu.getAllurl(browser)baidu.saveArticle(url_list)

  运行结果如下,首先是模拟浏览器进行点击跳转操作:

  然后提取网页中各个文章的url


  然后将文章处理后进行保存:



  可以看到,CSDN、简书、搜狐,还有GitHub等各网站的文章都保存到了本地,每个文件对应一篇文章,每个文章分为两个部分:文章的url和文章内容。当然也就极个别网站因为编码等各种问题,提取到的内容都是乱码,这个问题可以不用管它,毕竟是很少一部分。

2. 爬取微信公众号文章

  因为微信公众号的文章禁止了百度爬虫,所以百度搜索引擎没有权限收录微信公众号里的文章。我们可以输入https://mp.weixin.qq.com/robots.txt查看到微信公众号的Robots协议:


  或许你已经发现了,百度没有收录,但是搜狗收录了,我们可以通过搜狗的搜索引擎来搜索微信公众号的文章,这里依旧用关键词网易云爬虫进行搜索:

2.1 网页分析

  分析流程是可百度一样的,我们还是打开Chrome的调试面板,看一下文章的信息:


  可以看到,每个网页也是十篇文章,都放在了一个class="news-list"ul标签里,每个文章的url在一个class="txt-box"div标签里,点击图中箭头所示的链接也会发生跳转,转到所在的公众号:

  然后提取方法就和百度的一样,直接用正则提取即可。
  相比百度文章的提取来说,这里不需要使用动作链模拟时间选择,只需要找到下一页的按钮,然后提取相关信息并保存即可。这样看来微信公众号的提取比百度还简单,其实并不是,这些都是假象。

2.2 反爬分析

  这里来到第一步,我们找到了文章在搜狗的搜索引擎中url,这里以第一篇为例,它在搜狗的搜索引擎中urlhttp://weixin.sogou.com/link?url=dn9a_-gY295K0Rci_xozVXfdMkSQTLW6cwJThYulHEtVjXrGTiVgS_ebTjeDJbzRobk7sV0-esChYTg9WI5RvlqXa8Fplpd9p9wyDv1i5nLnl71X1UfI_CJKUC0n9tWj6xepdWbSVrenOGPPw_VvyqlJvHqDryH-3JVoytoHLXnTdEvnQ15mFVP42dxeIClXiHTBUrOZ2RdaiHLZUWi_E5Yn3k7xqldQhRrZENR-zjShq5GeNOivVW7i63t4nGTYnF0v03wc3RlhWLv6O878UA..&type=2&query=%E7%BD%91%E6%98%93%E4%BA%91%E7%88%AC%E8%99%AB&token=E6921574590DD76C1712B9B460D8616317D1AC4D5F661F52,其中%后面是什么意思应该已经知道了吧,它的意思在上一篇文章里已经介绍了,这里的%E7%BD%91%E6%98%93%E4%BA%91%E7%88%AC%E8%99%AB意思就是我们输入的关键词网易云爬虫
  我通过代码去访问该链接,理论上应该跳转到该文章的公众号上,然而却出现了错误,根据PyCharm控制台输出的信息,我看到了一个链接:https://weixin.sogou.com/antispider/,很明显,被网站的反爬虫机制给禁止了,打开这个链接看一下:


  这里有两种方法来解决,第一个就是尝试进一步伪装来欺骗网站的反爬虫机制,第二个就是验证码识别。这里我尝试了去识别验证码,使用的是Tesseract-OCR以及它的Python API pytesseract,但是效果并不好,它的这个验证码不仅有很多小的字母,而且图片上加了一条类似余弦波的曲线来干扰识别。刚开始我也想尝试针对这个验证码来训练一个神经网络模型,但是随之而来的就是数据集问题,而且还得自己标注,太麻烦了。所以还是采用第一种方法,伪装自己。
  因为发生了跳转,为了方便看一下这个跳转的过程中发生了什么,所以这里我用Charles进行了抓包:


  这里的第一个不同就是这个发生跳转的url发生了稍微的变化,相比原来的,这里多了两个参数:&k=21&h=t,经过几次刷新发现,并没有什么影响,即这两个参数不是必须的。


  我们手动点击的那个链接会跳转到这里,就是上图框框里面的,它会执行这个这段JavaScript代码,然后将url进行重定向,框框中的url就是微信公众号里的文章链接,所以干脆直接在这里面提取微信公众号里文章的url,代码如下:

 reault = response.text.split(';')real_url = ''for i in reault[2:-3]:j = i.strip()real_url += j[8:-1]return real_url

  这段代码的意思就是先将文本以进行分割,然后去除冗余字符后再进行拼接,拼接出来的url就是我们要找的微信公众号里文章的url,最后将结果返回。

  需要注意的是,这个url后面的字符也是随机的,每次刷新都不一样,但是无妨,因为我们直接提取的是变化后的结果。

  经过多次失败地尝试,Request请求所需要的User-AgentRefererCookieHost要和上面抓包抓取的内容一样,否则就会触发反爬虫机制,这里的反爬虫机制对这些参数还是很敏感的。

 Host: weixin.sogou.comUser-Agent: netdisk;2.2.2;pc;pc-mac;10.14.5;macbaiduyunguanjiaAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Referer: http://weixin.sogou.com/weixin?type=2&s_from=input&query=%E7%BD%91%E6%98%93%E4%BA%91%E7%88%AC%E8%99%AB&ie=utf8&_sug_=n&_sug_type_=Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: SUID=4E1BC07A2C18960A000000005F562407; SUV=1599480842999554; pgv_pvi=4547401728; ABTEST=0|1600214894|v1; weixinIndexVisited=1; IPLOC=CN3203; SNUID=590DD76C1712B9B460D8616317D1AC4D; ld=Dkllllllll2KqAy4lllllVduVhDlllllW7lbFkllllylllll9Zlll5@@@@@@@@@@; LSTMV=182%2C70; LCLKINT=1731; JSESSIONID=aaaTMPxeCI4CUf2aC0ZoxConnection:keep-alive

  再说一下这几个参数都是干什么的:
    Host 用于指定请求资源的主机
    User-Agent 浏览器的UA标识,服务器根据此内容可以识别到用户使用的浏览器信息及操作系统地信息
    Referer 标识请求是从哪个页面发过来的
    Cookie 网站为了辨别用户进行会话跟踪而存储在本地的数据


  同理,也要对抓取文章的Request请求参数进行相应的改变:

 Host: mp.weixin.qq.comReferer: 这个就是搜狗搜索引擎中文章的urlCookie: pgv_pvi=3376051200; RK=zRKkVVUClt; ptcz=bea38e8122d337415e409f1122e019246b9cd2d50352aba7155d95384bfc9cc0; tvfe_boss_uuid=57ca5a85851f958c; pgv_pvid=3704594732; euin=owozNK-zNKosNn**; psrf_access_token_expiresAt=1607758183; tmeLoginType=2; psrf_qqrefresh_token=A8028B993A1A7526C49F5F7CEA2F8D8F; psrf_qqunionid=; psrf_qqaccess_token=48A2231BD3DAFAB5395160B26CCA59C1; psrf_qqopenid=975A7CB39D73DB46B588429325C16AE5; o_cookie=2309209368; pgv_info=ssid=s4047750795; rewardsn=; wxtokenkey=777

  这里还要注意一下,用requests.get()发起请求时最好关闭一下证书认证,即verify=False,否则会引发错误或者得不到信息。对我们输入的关键词也要重新做一下编码,处理后的结果为以%开头后面是两个十六进制的字符,否则会出现编码错误:UnicodeEncodeError: 'latin-1' codec can't encode characters in position

2.3 代码实现

# -*- coding: utf-8 -*-
# @Time    : 2020/9/16 8:13
# @Author  : XiaYouRan
# @Email   : youran.xia@foxmail.com
# @File    : WeChat.py
# @Software: PyCharmfrom bs4 import BeautifulSoup
import requests
from selenium import webdriver
import time
import re
import osclass SouGou(object):def __init__(self):# 对输入的内容做一下编码, 否则也会出错self.input_kw = input('请输入要搜索的关键字: ')self.headers = {'User-Agent': 'netdisk;2.2.2;pc;pc-mac;10.14.5;macbaiduyunguanjia','Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,''image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9','Cookie': 'SUID=4E1BC07A2C18960A000000005F562407; SUV=1599480842999554; ''pgv_pvi=4547401728; ABTEST=0|1600214894|v1; weixinIndexVisited=1; ''IPLOC=CN3203; ld=olllllllll2KqAy4lllllVd6hNGlllllW7lbFkllllGlllll''9klll5@@@@@@@@@@; LCLKINT=1528; LSTMV=180%2C76; SNUID=590DD76C1712B''9B460D8616317D1AC4D; JSESSIONID=aaagW2SHM9h2C4PRboZox','Connection': 'keep-alive','Referer': 'http://weixin.sogou.com/weixin?type=2&s_from=input&query={}&ie=utf8''&_sug_=n&_sug_type_='.format(str(self.input_kw.encode())[1:].replace('\\x', '%')),'Host': 'weixin.sogou.com','Accept-Encoding': 'gzip, deflate','Accept-Language': 'zh-CN,zh;q=0.9'}self.headers_ = {'User-Agent': 'netdisk;2.2.2;pc;pc-mac;10.14.5;macbaiduyunguanjia','Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,''image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9','Cookie': 'pgv_pvi=3376051200; RK=zRKkVVUClt; ptcz=bea38e8122d337415e409f1122e0192''46b9cd2d50352aba7155d95384bfc9cc0; tvfe_boss_uuid=57ca5a85851f958c; pgv_''pvid=3704594732; euin=owozNK-zNKosNn**; psrf_access_token_expiresAt=16077''58183; tmeLoginType=2; psrf_qqrefresh_token=A8028B993A1A7526C49F5F7CEA2F8''D8F; psrf_qqunionid=; psrf_qqaccess_token=48A2231BD3DAFAB5395160B26CCA59C1;'' psrf_qqopenid=975A7CB39D73DB46B588429325C16AE5; o_cookie=2309209368; rewar''dsn=; wxtokenkey=777','Connection': 'keep-alive','Referer': '','Host': 'mp.weixin.qq.com','Accept-Encoding': 'gzip, deflate','Accept-Language': 'zh-CN,zh;q=0.9'}def search(self):"""搜索:return:"""url = 'http://weixin.sogou.com'browser = webdriver.Chrome()browser.get(url)time.sleep(2)input = browser.find_element_by_css_selector('#query')input.send_keys(self.input_kw)button = browser.find_element_by_css_selector('.swz')button.click()time.sleep(3)return browserdef parser(self, browser):"""解析出文章的url:param browser: :return: """html = browser.page_sourcesoup = BeautifulSoup(html, 'lxml')text_list = soup.find_all(class_='txt-box')urls = []for text in text_list:tag_a = text.h3.atitle = tag_a.get_text()url = 'http://weixin.sogou.com' + tag_a['href']urls.append(url)# print(title, url)return urlsdef getAllurl(self, browser):"""提取所有结果的url:param browser: :return: """url_list = []count = 1while True:print('{:*^30}'.format('正在抓取第%d页' % count))url = sougou.parser(browser)url_list.extend(url)try:# 下一页next_page = browser.find_elements_by_css_selector('.np')[-1]if next_page.text == '下一页':next_page.click()time.sleep(3)count += 1else:breakexcept Exception as err:print(err)breakbrowser.close()return url_listdef getRealURL(self, url):"""提取真正的url:param url: :return: """try:# 忽略证书认证response = requests.get(url, headers=self.headers, verify=False)response.raise_for_status()response.encoding = 'utf-8'reault = response.text.split(';')real_url = ''for i in reault[2:-3]:j = i.strip()real_url += j[8:-1]return real_urlexcept Exception as err:print(err)print('请求异常')return ''def getText(self, url):"""提取文章:param url: :return: """real_url = self.getRealURL(url)try:self.headers_['Referer'] = urlresponse = requests.get(real_url, headers=self.headers_, verify=False)response.raise_for_status()response.encoding = 'utf-8'return response.url, response.textexcept Exception as err:# print(err)# print('请求异常')return '', ''def saveArticle(self, url_list):"""保存文章:param url_list: :return: """for index in range(len(url_list)):# 开始整处理网页blog_url, blog_text = sougou.getText(url_list[index])# 微信公众号的主题内容在一个id为js_content的div标签内# 所以这里再进行一下冗余处理soup = BeautifulSoup(blog_text, 'lxml')text_tag = soup.find(id='js_content')if text_tag is None:# 内容为空, 应该是内容被删了continueblog_text = text_tag.textpattern = re.compile('[a-zA-Z0-9</>":.;!!=_#&@\\\?\[\]()(),:+,\'%《》$、。\s\|\{\}\*\-?【】“”‘’~\^]')blog_text = re.sub(pattern, '', blog_text)if not os.path.exists('./articleWechat'):os.mkdir('./articleWechat')with open('./articleWechat/{}.txt'.format(str(index).zfill(5)), 'w', encoding='gb18030') as f:f.write(blog_url + '\n' + blog_text)if (index + 1) % 10 == 0:print('已保存: %d页' % (index + 1))

2.4 代码测试

  测试代码如下:

if __name__ == '__main__':sougou = SouGou()browser = sougou.search()url_list = sougou.getAllurl(browser)sougou.saveArticle(url_list)

  运行结果如下,警告信息不需要管它,哈哈哈哈:


3. 模型训练

  我是用doc2vec来将文本向量化,它是在word2vec的基础上进行拓展的,由于考虑了文本的上下文信息,所以效果会比单纯地word2vec更好。这里使用的是doc2vec中的DM模型,语料库用的是维基百科上的(2020年7月),训练代码如下:

# -*- coding: utf-8 -*-
# @Time    : 2020/9/17 13:03
# @Author  : XiaYouRan
# @Email   : youran.xia@foxmail.com
# @File    : WiKi.py
# @Software: PyCharmfrom gensim.models import doc2vec
import logging# 打印日志的时间 打印日志级别名称 打印日志信息
logging.basicConfig(format='%(asctime)s: %(levelname)s: %(message)s', level=logging.INFO)if __name__ == '__main__':wikidata = 'E:/Program/Python/NLP/AutomanticSummarization/data/wiki_202007_s.txt'# 遍历包含文档的文件: 一行 = TaggedDocument对象# 单词应该已经过预处理, 并用空格分隔# 文档标签是根据文档行号自动构建的(每个文档都有一个唯一的整数标签)document = doc2vec.TaggedLineDocument(source=wikidata)# dm=1, 使用分布式记忆模型(PV-DM), 否则, 将使用分布式词袋模型(PV-DBOW)# dbow_words=0, 只训练doc2vec# vector_size: 特征向量的维数# window: 窗口大小, 即句子中当前单词和预测单词之间的最大距离# min_count: 忽略总频率低于此频率的所有单词# epochs: 语料库中的迭代次数# workers: 使用多线程来训练模型model = doc2vec.Doc2Vec(documents=document, dm=1, dbow_words=0, vector_size=128, window=10, min_count=5, epochs=10, seed=128, workers=8)model.save('./model/doc2vec_size128_202007.model')

  有关gensim的详细操作,请参考官方文档。

  我这里设置的向量维度为128,训练了10次,大概用了四十多分钟,如果想进一步提高准确率,可以将这两个值再改大一些。模型的训练信息如下:

4. 相似度分析

  我是用余弦相似度来做文本的相似度分析,余弦相似度计算的是两个向量间的夹角的余弦值,夹角越小,其余弦值就越大,说明这两个向量越相关。计算公式如下:
similarity=cos⁡θ=A⃗B⃗∣∣A⃗∣∣∣∣B⃗∣∣=∑i=1nA⃗iB⃗i∑i=1nA⃗i∑i=1nB⃗isimilarity=\cos \theta=\frac {\vec A \vec B} {\big|\big|\vec A\big|\big| \big|\big|\vec B\big|\big|}=\frac {\sum_{i=1}^n\vec A_i \vec B_i} {\sqrt {\sum_{i=1}^n\vec A_i}\sqrt {\sum_{i=1}^n\vec B_i}}similarity=cosθ=∣∣​∣∣​A∣∣​∣∣​∣∣​∣∣​B∣∣​∣∣​AB​=∑i=1n​Ai​​∑i=1n​Bi​​∑i=1n​Ai​Bi​​  如果是向量是二维的,这个公式就是我们常说的向量的夹角公式:
cos⁡θ=x1x2+y1y2x12+y12x22+y22\cos \theta=\frac {x_1x_2+y_1y_2} {\sqrt {x_1^2+y_1^2}\sqrt {x_2^2+y_2^2}}cosθ=x12​+y12​​x22​+y22​​x1​x2​+y1​y2​​  代码实现就是:

    def cosine(self, vec1, vec2):# 两个分母vec1mod = np.sqrt(vec1.dot(vec1))vec2mod = np.sqrt(vec2.dot(vec2))if vec1mod != 0 and vec2mod != 0:sim = vec1.dot(vec2) / (vec1mod * vec2mod)else:sim = 0return "{:.4}".format(sim)

  当然,也可以使用sklearn库中的cosine_similarity函数来计算:

 from sklearn.metrics.pairwise import cosine_similarity

  这里又对文本进行了处理,使用了jieba来进行分词,方便进行向量化。还用到了以前整理的一个停用词文件,当然也可以使用其他的,其目的就是将一些非必要字符在进行去除。
  代码实现如下:

# -*- coding: utf-8 -*-
# @Time    : 2020/9/19 19:26
# @Author  : XiaYouRan
# @Email   : youran.xia@foxmail.com
# @File    : Check.py
# @Software: PyCharmfrom gensim.models import doc2vec
import logging
import jieba
import os
import numpy as np
import requests
import re# 打印日志的时间 打印日志级别名称 打印日志信息
logging.basicConfig(format='%(asctime)s: %(levelname)s: %(message)s', level=logging.INFO)class Check(object):def __init__(self):self.sw_list = self.get_stopword(stopword_file='./stopword/stopword.txt')self.model = doc2vec.Doc2Vec.load('./model/doc2vec_size128_202007.model')self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ''(KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}def get_stopword(self, stopword_file):"""加载停用词:param stopword_file::return:"""stopword_list = []with open(stopword_file, 'r', encoding='gb18030', errors='ignore') as f:line_list = f.readlines()for line in line_list:stopword_list.append(line.replace('\n', ''))return stopword_listdef article2vec(self, article_file):"""文本向量化:param article_file::return:"""article = []with open(article_file, 'r', encoding='gb18030') as f:line_list = f.readlines()article_url = line_list[0]for line in line_list[1:]:short_sentence = line.strip('\r\n')word_list = list(jieba.cut(short_sentence))for word in word_list:if len(word) > 1 and word not in self.sw_list:article.append(word)article_vec = self.model.infer_vector(doc_words=article, alpha=0.01, epochs=1000)return article_url, article_vecdef cosine(self, vec1, vec2):"""余弦相似度:param vec1::param vec2::return:"""# 两个分母vec1mod = np.sqrt(vec1.dot(vec1))vec2mod = np.sqrt(vec2.dot(vec2))if vec1mod != 0 and vec2mod != 0:sim = vec1.dot(vec2) / (vec1mod * vec2mod)else:sim = 0return "{:.4}".format(abs(sim))def simlarity(self, article_path, article_file, article_list):"""计算文章的相似度:param article_path::param article_file::param article_list::return:"""_, real_vec = self.article2vec(article_file=article_file)count = 0result = []for article in article_list:article = article_path + articlearticle_url, article_vec = self.article2vec(article)sim = self.cosine(real_vec, article_vec)result.append([article_url, sim])count += 1if count % 25 == 0:print('[{0}]Processed {1} articles'.format(article_path[9:-1], count))print('[{}]Processed over!'.format(article_path[9:-1]))return resultdef saveResult(self, result_list):"""将分析结果保存:param result_list::return:"""result_list = sorted(result_list, key=lambda list1: float(list1[1]), reverse=True)with open('./result.txt', 'w', encoding='utf-8') as f:count = 0for result in result_list:line = result[1] + '\t' + result[0] + '\n'f.write(line)count += 1if count % 50 == 0:print('Writed %d articles' % count)print('Writed over!')def getText(self, url, my_article_file):"""提取我的文章:param url::param my_article_file::return:"""try:response = requests.get(url, headers=self.headers)response.raise_for_status()response.encoding = 'utf-8'pattern = re.compile('[a-zA-Z0-9</>":.;!!=_#&@\\\?\[\]()(),:+,\'%《》$、。\s\|\{\}\*\-?【】“”‘’~\^]')my_article = re.sub(pattern, '', response.text)with open(my_article_file, 'w', encoding='gb18030') as f:f.write(response.url + '\n' + my_article)except Exception as err:print(err)print('请求异常')

  测试代码如下:

if __name__ == '__main__':my_article_url = 'https://blog.csdn.net/qq_42730750/article/details/108415551'my_article_file = './my_article.txt'baidu_article = './articleBaidu/'wechat_article = './articleWechat/'check = Check()check.getText(my_article_url, my_article_file)print('Processing...')baidu_result_list = check.simlarity(article_path=baidu_article, article_file=my_article_file,article_list=os.listdir(baidu_article))wechat_result_list = check.simlarity(article_path=wechat_article, article_file=my_article_file,article_list=os.listdir(wechat_article))print('Writting...')check.saveResult(baidu_result_list + wechat_result_list)

  运行结果如下:

  我对结果按照相似度从高到低进行了一个排序,结果文件中最上面的就是疑似侵权的,就比如第一个,相似度超过了98%,十分可疑,我们复制后面的链接到浏览器进一步核实:


  enmmm,是我自己的,哈哈哈哈(〃‘▽’〃),想必你会有个疑问,既然是同一篇文章,为什么相似度不是100%???这是因为算法引入了随机性,即随机初始化和随机采样,而且训练是多线程进行的,输出的结果因为排序也会引起微小的差异,会导致各个向量的训练位置漂移到任意不同的位置,详细信息请看官方给出的答复。

5. 剩下的就交给客服吧

  如果发现了抄袭的文章,可以将咱们提取的侵权链接和自己文章的原链接一起发给CSDN的客服,具体操作流程可以参考这篇文章:

  剩下的法律交涉就交给后台的法务部吧,哈哈哈哈o(´^`)o


  当然了,除此之外你还可以自己申诉,像博客园、微信公众号这些直接投诉即可,不过有些网站根本不搭理你,建议通过官方进行版权申诉。

结束语

  整体的想法就是这些,需要优化的部分还有很多。在进行文本相似度分析那一模块,由于文本很多,处理起来较慢,所以后期想加入多线程来提高并发性,有时间还可以再做一个GUI,加入邮件发送的功能,哈哈哈哈,侵权必究(•́へ•́╬),加油(ง •̀_•́)ง。

Python爬虫:博客被抄袭了还不知道?快来查查相关推荐

  1. python 爬虫 博客园_Python爬虫爬取博客园作业

    分析一下他们的代码,我在浏览器中对应位置右键,然后点击检查元素,可以找到对应部分的代码.但是,直接查看当前网页的源码发现,里面并没有对应的代码.我猜测这里是根据服务器上的数据动态生成的这部分代码,所以 ...

  2. Python爬虫-博客园首页推荐博客排行(整合词云+邮件发送)

    1.前提: 总体思路,利用多线程(mutiSpider)爬取博客园首页推荐博客,根据用户名爬取该用户的阅读排行榜(TopViewPosts),评论排行榜(TopFeedbackPosts),推荐排行榜 ...

  3. 博客园的申请真是太快了!

    今天在网上搜索资料的时候,跑到了博客园,心想自己老是把学过的的东西忘了,为什么不把自己学习的东西记录下来呢?也可以记录一下自己的想法,心情什么的,就注册了.但是注册和一般的Blog和BBS注册不一样, ...

  4. 第一个Python程序——博客自动访问脚本

    第一个Python程序--博客自动访问脚本 动机 今天有朋友写信说他认为自己的wordpress博客内显示的访问统计信息不正常,希望我能为他制造一些访问信息,供他对比.朋友提出的请求是在短时间内快速打 ...

  5. Python个人博客项目-5.统计数据应用开发

    学习Python已经6年有余,从事的行业数据分析师,算法工程师.爬虫工程师.目前准备正式入行全栈,先做一个个人的博客系统,用来记录日常工作的每一天,也是为自己做一个展示自己的空间. 本身该项目与之前的 ...

  6. 基于Python个人博客系统设计与实现 开题报告

      本科生毕业论文 基于python个人博客系统Django框架 开题报告 学    院: 专    业: 计算机科学与技术 年    级: 学生姓名: 指导教师: 黄菊华   XXXX大学本科生毕业 ...

  7. Python个人博客项目-3.用户应用开发

    学习Python已经6年有余,从事的行业数据分析师,算法工程师.爬虫工程师.目前准备正式入行全栈,先做一个个人的博客系统,用来记录日常工作的每一天,也是为自己做一个展示自己的空间. 本身该项目与之前的 ...

  8. 这是我的第一个学Python的博客

    这是我的第一个学Python的博客 前言 至于什么学,怎么学好我始终觉得先在路上比较重要,先开始在坚持,如果一直停留在自我满足的状态,迟早要被社会淘汰. 1.计算机的基本概念 1.1概念: " ...

  9. 用Python编写博客导出工具

    用Python编写博客导出工具 罗朝辉 (http://kesalin.github.io/) CC 许可,转载请注明出处 写在前面的话 我在 github 上用 octopress 搭建了个人博客, ...

最新文章

  1. DAS、NAS、SAN、iSCSI 存储方案概述
  2. pom.xml设置mysql连接_maven工程配置pom.xml实现mybatis的访问数据库操作
  3. 在VMware环境Windows server 2003下测试磁盘阵列实验
  4. 用matlab实现感知机学习算法,训练分类器并解决井字游戏
  5. 在本机上安装zabbix,来监控服务器 六
  6. 阅读 LdrInitializeThunk
  7. 利用Kafka发送/消费消息-Java示例
  8. 基于内容的图像检索技术:从特征到检索
  9. 我的第一个Python程序:Luogu1001 A+B Problem
  10. 小甲鱼windows程序设计(50讲)
  11. 海康威视多路播放开发步骤及参考代码
  12. 《匠人手记》里的10种软件滤波
  13. 6th WEEK BLOG:CSS中的单位(PS调色板)(doing)
  14. 奇迹单机版服务器修改,大天使之剑奇迹网页游戏 一键服务端单机版/架设教程/修改方法...
  15. STM32+多片AD7705+双通道采集热电偶
  16. GitHub——注册github账号、安装git工具、仓库工作流程、创建本地仓库、写项目提交本地仓库、版本回退、创建远程仓库、使用远程仓库、Git忽略文件、协作冲突、分支管理
  17. 工业互联网,姓工,名互
  18. Microsoft Visio 专业版 2019,注意事项(bat文件乱码以及登不上Microsoft账号问题0x80190001)
  19. 小鸡腿U R III 杭州2013
  20. Figma#5:文字

热门文章

  1. 走近国内同人游戏开发者 一探游戏开发的喜与悲
  2. 批量转换经纬度的网页实现方法
  3. 焦点弦的垂直平分线和轴的交点到焦点的距离和焦点弦的距离之比为二分之e
  4. 如何向天翼云服务器上传文件,天翼云盘如何上传文件?
  5. Mysql 数据库(4)
  6. 用相关法辨识系统的脉冲响应 matlab,利用相关分析法辨识脉冲响应
  7. 数学教程之14本用于早期数学学习的精美插图儿童读物
  8. 互联网观察:2021年2月信息流资讯类App月活排名分析
  9. python下划线怎么输入_Python中下划线的使用方法有哪些
  10. Gossiping闲聊路由协议