python数据挖掘案例系列教程——python实现搜索引擎

article/2025/10/30 21:19:04

全栈工程师开发手册 (作者:栾鹏)

python数据挖掘系列教程

今天我们使用python实现一个网站搜索引擎。主要包含两个部分。网站数据库的生成、搜索引擎。其中搜索引擎部分我们使用单词频度算法、单词距离算法、外部回值算法、链接文本算法、pagerank算法和神经网络学习等6种算法来实现搜索排名。

我们这里将http://blog.csdn.net/luanpeng825485697站点下的所有网站当做一个小型服务器。对该网站域名下的网页进行搜索引擎设计。

由于需要获取博客文章的单词作为每篇博客的特征属性,并且我们的博客都是中文,所以在学习获取博客数据前,需要学习结巴中文分词库的使用。参考:
http://blog.csdn.net/luanpeng825485697/article/details/78757563

在我们的代码中,我们将模型固话在sqlite数据库中。所以在学习获取博客数据前,还需要学习sqlite3库的使用,参考:
http://blog.csdn.net/luanpeng825485697/article/details/78361168

由于我们会将所有的数据都存储在数据库中以便今后常用,所以代码中会有部分sqlite数据库的相关代码操作。

调试环境python3.6

gitup网址:https://github.com/626626cdllp/data-mining/tree/master/Search-Engines

第一部分、爬取网站生成网站数据库

这一部分,我们的目标是将域名下的所有网页的信息生成几个数据集。

这里写图片描述

sqlite数据库默认的主键索引名为rowid,mysql数据库默认的主键索引名为id。

第一张表urllist、保存的是已经url列表,字段hascrawl表示该网页是否已经爬取过。
第二张表wordlist、保存的是整个网站的单词列表,不包含重复单词。
第三张表wordlocation、保存的是单词在文档中所处位置的列表。单词的位置为单词在该文档内容所形成的单词列表中的索引。
第四张表link、保存了两个urlid,指明从一张表到另一张表的跳转关系。
第五张表linkwords、则利用wordid和linkid记录链接的描述。

下面我们就来爬取整个网站,生成这五张表,这里我们使用urllib获取响应,所以不能爬取js动态加载的网站。如果想爬取动态加载的网站,可以参考http://blog.csdn.net/luanpeng825485697/article/details/78436963

将下面的代码存储成spyder.py

# 根据连接爬取中文网站,获取标题、子连接、子连接数目、连接描述、中文分词列表,
import urllib
from bs4 import BeautifulSoup
import bs4
import sqlite3
import os
import jieba   #对中文进行分词
import traceback# 分词时忽略下列词,即不为这些单词建立数据库
biaodian = '[!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~]+ ,。?‘’“”!;:\r\n、()…'     #所有的标点符号
ignorewords = list(set(biaodian))   #去重
ignorewords.append('\r\n')   #另外添加一个忽略的项# 定义爬虫类。获取链接的标题、描述、分词、深度
class crawler:def __init__(self,dbname):self.urls={}              #创建爬取列表self.con = sqlite3.connect(dbname)self.curs = self.con.cursor()try:self.createindextables()except:passdef __del__(self):  # 关闭数据库self.curs.close()self.con.close()def dbcommit(self):  # 保存数据库self.con.commit()# 创建数据库表def createindextables(self):self.curs.execute('create table urllist(url,hascrawl)')  # 创建url列表数据表self.curs.execute('create table wordlist(word)')  # 创建word列表数据表self.curs.execute('create table wordlocation(urlid,wordid,location)')  # 创建url-word-location数据表(每个链接下出现的所有单词和单词出现的位置)self.curs.execute('create table link(fromid integer,toid integer)')  # 创建url-url数据表(当前链接和目标链接对)self.curs.execute('create table linkwords(wordid,linkid)')  # 创建word-url数据表(链接描述)self.curs.execute('create index wordidx on wordlist(word)')  # 创建索引self.curs.execute('create index urlidx on urllist(url)')  # 创建索引self.curs.execute('create index wordurlidx on wordlocation(wordid)')  # 创建索引self.curs.execute('create index urltoidx on link(toid)')  # 创建索引self.curs.execute('create index urlfromidx on link(fromid)')  # 创建索引self.dbcommit()#获取一个dom内的所有单词。soup为dom元素def getword(self,soup):# 获取每个单词text=self.gettextonly(soup)   #提取所有显示出来的文本allword=self.separatewords(text)  #使用分词器进行分词return allword# 根据一个dom元素提取文字(不带标签的)。由外至内获取文本元素。style和script内的文字忽略def gettextonly(self,soup):v=soup.stringif v==None:c=soup.contents   # 直接子节点的列表,将<tag>所有儿子节点存入列表resulttext=''for t in c:if t.name=='style' or t.name=='script':   #当元素为style和script和None时不获取内容continuesubtext=self.gettextonly(t)resulttext+=subtext+'\n'return resulttext.strip()else:if isinstance(v,bs4.element.Comment):   #代码中的注释不获取return ''return v.strip()# 使用结巴分词,同时去除标点符号def separatewords(self,text):seg_list = jieba.cut(text, cut_all=False)  #使用结巴进行中文分词allword = []for word in seg_list:if word not in ignorewords:allword.append(word)# print(allword)return allword#爬虫主函数def crawl(self,url,host):if url in self.urls and self.urls[url]['hascrawl']:return  # 如果网址已经存在于爬取列表中,并且已经爬取过,则直接返回try:if url in self.urls:    if self.urls[url]['hascrawl']:returnelse: self.urls[url]['hascrawl']=True  # 如果网址已存在,但是并没有爬取过,就设置hascrawl为Trueelse:self.urls[url]={}self.urls[url]['hascrawl']=Trueresponse=urllib.request.urlopen(url)   #获取响应流text = str(response.read(), encoding='utf-8')   #转化为utf-8编码的字符串soup = BeautifulSoup(text, 'html.parser')   #解析成dom树links = soup('a')  #获取所有链接for link in links:if ('href' in dict(link.attrs)):   #如果链接元素存在href属性newurl = urllib.parse.urljoin(url, link['href'])  #获取链接的绝对网址if not host in newurl: continue  # 非服务范围网址不爬取,不记录if newurl == url: continue  # 如果网址是当前网址,不爬取,不记录if newurl.find("'") != -1: continue  # 包含'的链接不爬取,不记录newurl = newurl.split('#')[0]  # 去掉#后的数据部分if newurl[0:4] == 'http':  # 只处理http协议if newurl not in self.urls:  # 将链接加入爬取列表self.urls[newurl] = {}self.urls[newurl]['hascrawl'] = False # 添加链接描述linkText = self.gettextonly(link).strip()  # 获取链接的描述self.addlinkref(url, newurl, linkText)  # 添加链接跳转对和链接描述self.addtoindex(url, soup.body)  # 创建链接列表库和链接-分词库self.dbcommit()    #保存return Trueexcept:traceback.print_exc()return False# print("Could not parse page %s" % url)# 添加链接列表库和链接-分词索引def addtoindex(self, url, soup):# 查询-增加网址获得urlidurlid = self.get_add_id('urllist', 'url', url)# 提取所有单词allword = self.getword(soup)  # 提取所有显示出来的文本print(allword)# 将每个单词与该url关联,写入到数据库index = 0for word in allword:index += 1# 查询-增加单词获得wordid wordid = self.get_add_id('wordlist', 'word', word)self.curs.execute("insert into wordlocation(urlid,wordid,location) values (%d,%d,%d)" % (urlid, wordid, index))# 添加链接跳转对,和链接-描述文本。def addlinkref(self, urlFrom, urlTo, linkText):words = self.separatewords(linkText)fromid = self.get_add_id('urllist', 'url', urlFrom)   #参数:表名、列名、值toid = self.get_add_id('urllist', 'url', urlTo)   #参数:表名、列名、值if fromid == toid: returncur = self.curs.execute("insert into link(fromid,toid) values (%d,%d)" % (fromid, toid))linkid = cur.lastrowidfor word in words:wordid = self.get_add_id('wordlist', 'word', word)   #参数:表名、列名、值self.curs.execute("insert into linkwords(linkid,wordid) values (%d,%d)" % (linkid, wordid))# 辅助函数,用于获取数据库中记录的id,并且如果记录不存在,就将其加入数据库中,再返回iddef get_add_id(self, table, field, value):command = "select rowid from %s where %s='%s'" % (table, field, value)cur = self.curs.execute(command)res = cur.fetchall()if res == None or len(res) == 0:cur = self.curs.execute("insert into %s (%s) values ('%s')" % (table, field, value))return cur.lastrowidelse:return res[0][0]   #返回第一行第一列

上面就完成了一个基本的爬虫类,当然今天我们还会在这个类中添加一些功能,以适应更加丰富的搜索。

下面我们就可以借用这个类来爬取一个网站了。

# 爬取指定域名范围内的所有网页,beginurl为开始网址,host为根网址
def crawlerhost(beginurl,host,dbname):mycrawler = crawler(dbname)      #定义爬虫对象mycrawler.crawl(beginurl,host)  #爬取主页for url in list(mycrawler.urls.keys()):print(url)mycrawler.crawl(url, host)  # 爬取子网页# 获取pageRank数据# mycrawler.calculatepagerank()

爬取部分算是完成了。下面就可以来测试一下了。

# 读取数据库信息,检验是否成功建立了搜索数据库
def readdb(dbname):con = sqlite3.connect(dbname)curs = con.cursor()command = "select fromid,toid from link"   #'select * from link'cur = curs.execute(command)res = cur.fetchall()allurl=[]if res != None and len(res) > 0:for row in res:print(row)command = "select url from urllist where rowid=%d" % row[0]fromurl = curs.execute(command).fetchall()[0][0]command = "select url from urllist where rowid=%d" % row[1]tourl = curs.execute(command).fetchall()[0][0]# print(fromurl,tourl)if fromurl not in allurl:allurl.append(fromurl)if tourl not in allurl:allurl.append(tourl)if __name__ == '__main__':# 爬取服务器建立数据库url = 'http://blog.csdn.net/luanpeng825485697'# if os._exists('csdn.db'):#     os.remove('csdn.db')    #删除旧的数据库crawlerhost(url, url,'csdn.db')#读取数据库readdb('csdn.db')

第二部分、搜索

这一部分我们根据用户的输入,从数据库中获取相关的链接集。在下一部分链接集进行排序使用。

我们为搜索引擎新建一个模块,命名为searchengine.py

先引入一些必要的模块和变量

# 搜索和排名
import urllib
from bs4 import BeautifulSoup
import re
import sqlite3
import nn
import os
import spyder   #获取爬虫数据集
import jieba# 分词时忽略下列词
biaodian = '[!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~]+ ,。?‘’“”!;:\r\n、()…'     #所有的标点符号
ignorewords = list(set(biaodian))   #去重
ignorewords.append('\r\n')   #添加一个不忽略的项

在模块中我们先建立一个搜索引擎类。


# 定义搜索引擎类
class searcher:def __init__(self,dbname):self.con=sqlite3.connect(dbname)  #链接数据库self.curs = self.con.cursor()def __del__(self):self.curs.close()self.con.close()

搜索引擎类的一个任务就是能根据用户搜索的字符串分割成多个关键词,再查询到相关的网址。而只要文章中出现了每个关键词,就认为网页相关。这一部分代码比较难懂,建议读者边运行边学习。

查询过程:

1、对输入字符串进行分词获得多个关键词。例如我们查询“腾讯股票”,会分词成“腾讯”、“股票”
2、查询每个关键词在单词库wordlist数据库中的索引。例如我们会查询到“腾讯”在单词库中的索引为126,“股票在单词库中的索引为127”
3、利用这两个关键词索引把所有包含这两个关键词的链接以及这两个关键词在链接中出现的位置查询出来。形成以下的数据格式

[(urlid1,wordlocation1_1,wordlocation2_1),  # 链接1中,单词1的位置1,单词2的位置1(urlid1,wordlocation1_1,wordlocation2_2),  # 链接1中,单词1的位置1,单词2的位置2(urlid1,wordlocation1_1,wordlocation2_3),  # 链接1中,单词1的位置1,单词2的位置3(urlid1,wordlocation1_2,wordlocation2_1),  # 链接1中,单词1的位置2,单词2的位置1(urlid1,wordlocation1_2,wordlocation2_2),  # 链接1中,单词1的位置2,单词2的位置2(urlid1,wordlocation1_2,wordlocation2_3),  # 链接1中,单词1的位置2,单词2的位置3...(urlid2,wordlocation1_1,wordlocation2_1),  # 链接2中,单词1的位置1,单词2的位置1(urlid2,wordlocation1_1,wordlocation2_2),  # 链接2中,单词1的位置1,单词2的位置2...
]

在下面的语句中我们会用到一个数据库查询语句。
例如我们查询了字符串中包含“腾讯”、“股票”两个关键词,在单词列表wordlist中的索引分别为126、和127。

则我们使用下面的语法查询出数据集。

select w0.urlid,w0.location,w1.location from wordlocation w0,wordlocation w1 where w0.wordid=126 and w0.urlid=w1.urlid and w1.wordid=127

上面的 代码先看where后面的语句。w0表示一个数据表,w1表示另一个数据表。
下面的语句表示满足条件:
w0表的wordid字段为126的所有w0中的记录
w1表的wordid字段为127的所有w1中的记录
w0表的urlid字段与w1表中urlid字段存在相同值的所有w0中的记录和w1中的所有记录。
where后的语句查询上述数据集的交集。

where w0.wordid=126 and w0.urlid=w1.urlid and w1.wordid=127

再来看from后面的语句。表示wordlocation表可以用w0这个名称代替,wordlocation这个表也可以用w1这个名称代替。

from wordlocation w0,wordlocation w1 

最后来看select后的语句。上面查询到的数据集既有w0中的又有w1中的,把w0中记录的urlid字段、w0中记录的location字段、w1中记录的location字段提取出来。

select w0.urlid,w0.location,w1.location 

最终形成了我们想要的数据集格式。

[(urlid1,wordlocation1_1,wordlocation2_1),  # 链接1中,单词1的位置1,单词2的位置1(urlid1,wordlocation1_1,wordlocation2_2),  # 链接1中,单词1的位置1,单词2的位置2(urlid1,wordlocation1_1,wordlocation2_3),  # 链接1中,单词1的位置1,单词2的位置3(urlid1,wordlocation1_2,wordlocation2_1),  # 链接1中,单词1的位置2,单词2的位置1(urlid1,wordlocation1_2,wordlocation2_2),  # 链接1中,单词1的位置2,单词2的位置2(urlid1,wordlocation1_2,wordlocation2_3),  # 链接1中,单词1的位置2,单词2的位置3...(urlid2,wordlocation1_1,wordlocation2_1),  # 链接2中,单词1的位置1,单词2的位置1(urlid2,wordlocation1_1,wordlocation2_2),  # 链接2中,单词1的位置1,单词2的位置2...
]

查询实现代码如下:

# 使用结巴分词,去除标点符号def separatewords(self, text):seg_list = jieba.cut(text, cut_all=False)  # 使用结巴进行中文分词allword = []for word in seg_list:if word not in ignorewords:allword.append(word)# print(allword)return allword# 根据搜索字符串分词后获取查询到的链接def getmatchrows(self,querystr):# 构造数据库的查询字符串(搜索字符串根据空格分割成查询字符串列表)fieldlist='w0.urlid'tablelist=''clauselist=''wordids=[]# words=querystr.strip().split(' ')   # 根据空格分割单词words = self.separatewords(querystr)  #使用结巴进行中文分词tablenumber=0for word in words:# 获取单词的idwordrow=self.curs.execute("select rowid from wordlist where word='%s'" % word).fetchall()if wordrow!=None and len(wordrow)> 0:wordid=wordrow[0][0]  #获取单词idwordids.append(wordid)if tablenumber>0:tablelist+=','clauselist+=' and 'clauselist+='w%d.urlid=w%d.urlid and ' % (tablenumber-1,tablenumber)fieldlist+=',w%d.location' % tablenumbertablelist+='wordlocation w%d' % tablenumberclauselist+='w%d.wordid=%d' % (tablenumber,wordid)tablenumber+=1# 根据各个组分,建立查询。为列表中的每个单词,建立指向wordlocation表的引用,并根据对应的urlid将它们连接起来进行联合查询fullquery='select %s from %s where %s' % (fieldlist,tablelist,clauselist)# print(fullquery)cur=self.curs.execute(fullquery)rows=[row for row in cur.fetchall()]return rows,wordids

经过上面的查询语句,返回的rows,wordids。其中
wordids样式如[126, 127],表示每个查询关键词在单词库wordlist数据库中的索引。这个比较简单。

而rows的样式比较复杂,但他是理解后续代码的关键。

下面假设搜索字符串解析出了两个关键词。并且这两个关键词在某一个网页中可能都出现了很多次。
rows的样式为

[(urlid1,wordlocation1_1,wordlocation2_1),  # 链接1中,单词1的位置1,单词2的位置1(urlid1,wordlocation1_1,wordlocation2_2),  # 链接1中,单词1的位置1,单词2的位置2(urlid1,wordlocation1_1,wordlocation2_3),  # 链接1中,单词1的位置1,单词2的位置3(urlid1,wordlocation1_2,wordlocation2_1),  # 链接1中,单词1的位置2,单词2的位置1(urlid1,wordlocation1_2,wordlocation2_2),  # 链接1中,单词1的位置2,单词2的位置2(urlid1,wordlocation1_2,wordlocation2_3),  # 链接1中,单词1的位置2,单词2的位置3...(urlid2,wordlocation1_1,wordlocation2_1),  # 链接2中,单词1的位置1,单词2的位置1(urlid2,wordlocation1_1,wordlocation2_2),  # 链接2中,单词1的位置1,单词2的位置2...
]

可以看出列表中的每个元组中,第一列代表是网址id,后面的列是每个关键词出现的位置的一种组合。

第三部分、排名算法

上面已经根据用户的输入获取到了相关的网址数据。
获取到的数据中rows的形式如下
[(urlid1,wordlocation1_1,wordlocation1_2,wordlocation1_3…),(urlid2,wordlocation2_1,wordlocation2_2,wordlocation2_3…)]
列表的每个元素是一个元组,每个元素的内容是urlid和每个关键词在该文档中的位置。

wordids形式为[wordid1, wordid2, wordid3…],即每个关键词所对应的单词id

我们将会介绍几种排名算法,所谓排名也就是根据各自的规则为每个链接评分,评分越好。并且最终我们会将几种排名算法综合利用起来,给出最终的排名。既然要综合利用,那么我们就要先实现每种算法。在综合利用时会遇到几个问题。

1、每种排名算法评分机制不同,给出的评分尺度和含义也不尽相同
2、如何综合利用,要考虑每种算法的效果。为效果好的给与较大的权重。

我们先来考虑第一个问题,如何消除每种评分算法所给出的评分尺度和含义不相同的问题。
第2个问题,等研究完所有的算法以后再来考虑。

简单,使用归一化,将每个评分值缩放到0-1上,1代表最高,0代表最低。

# 评价值归一化:因为不同的评价方法的返回值和含义不同。这里所有的评价值归一化到0-1,默认越大越好def normalizescores(self,scores,smallIsBetter=0):vsmall=0.00001 #避免被0整除if smallIsBetter:minscore=min(scores.values())return dict([(u,float(minscore)/max(vsmall,l)) for (u,l) in scores.items()])else:maxscore=max(scores.values())if maxscore==0: maxscore=vsmallreturn dict([(u,float(c)/maxscore) for (u,c) in scores.items()])

下面我们来研究每一种算法。

第1个排名算法:根据单词位置进行评分的函数

我们可以认为对用户输入的多个关键词,在文档中,这些关键词出现的位置越靠前越好。比如我们往往习惯在文章的前面添加一些摘要性、概括性的描述。

    # 根据单词位置进行评分的函数.# rows是[(urlid1,wordlocation1_1,wordlocation1_2,wordlocation1_3...),(urlid2,wordlocation2_1,wordlocation2_2,wordlocation2_3...)]def locationscore(self,rows):locations=dict([(row[0],1000000) for row in rows])for row in rows:loc=sum(row[1:]) #计算每个链接的单词位置总和,越小说明越靠前if loc<locations[row[0]]:  #记录每个链接最小的一种位置组合locations[row[0]]=locreturn self.normalizescores(locations,smallIsBetter=1)

第2个排名算法:根据单词频度进行评价的函数

我们可以认为对用户输入的多个关键词,在文档中,这些关键词出现的次数越多越好。比如我们在指定主题的文章中会反复提到这个主题。

  # 根据单词频度进行评价的函数# rows是[(urlid1,wordlocation1_1,wordlocation1_2,wordlocation1_3...),(urlid2,wordlocation2_1,wordlocation2_2,wordlocation2_3...)]def frequencyscore(self,rows):counts=dict([(row[0],0) for row in rows])for row in rows: counts[row[0]]+=1   #统计每个链接出现的组合数目。 每个链接只要有一种位置组合就会保存一个元组。所以链接所拥有的组合数,能一定程度上表示单词出现的多少。return self.normalizescores(counts)

第3个排名算法:根据单词距离进行评价的函数

我们可以认为对用户输入的多个关键词,在文档中,这些关键词出现的越紧凑越好。这是因为我们更希望所有单词出现在一句话中,而不是不同的关键词出现在不同段落或语句中。

  # 根据单词距离进行评价的函数。# rows是[(urlid1,wordlocation1_1,wordlocation1_2,wordlocation1_3...),(urlid2,wordlocation2_1,wordlocation2_2,wordlocation2_3...)]def distancescore(self,rows):# 如果仅查询了一个单词,则得分都一样if len(rows[0])<=2: return dict([(row[0],1.0) for row in rows])# 初始化字典,并填入一个很大的值mindistance=dict([(row[0],1000000) for row in rows])for row in rows:dist=sum([abs(row[i]-row[i-1]) for i in range(2,len(row))]) # 计算每种组合中每个单词之间的距离if dist<mindistance[row[0]]:  # 计算每个链接所有组合的距离。并为每个链接记录最小的距离mindistance[row[0]]=distreturn self.normalizescores(mindistance,smallIsBetter=1)

第4个排名算法:利用指向该链接的链接数目进行评价

我们可以认为查询到的相关链接中,如果有较多的其他链接在文档中指向了当前链接,这该链接内容质量比较好。

  # 利用指向该链接的链接数目进行评价(仅计算回指数目)。# rows是[(urlid1,wordlocation1_1,wordlocation1_2,wordlocation1_3...),(urlid2,wordlocation2_1,wordlocation2_2,wordlocation2_3...)]def inboundlinkscore(self,rows):uniqueurls=dict([(row[0],1) for row in rows])inboundcount=dict([(u,self.curs.execute('select count(*) from link where toid=%d' % u).fetchall()[0]) for u in uniqueurls])return self.normalizescores(inboundcount)

第5个排名算法:pagerank算法

互联网中的网页可以看出是一个有向图,其中网页是结点,如果网页A有链接到网页B,则存在一条有向边A->B,下面是一个简单的示例:

这里写图片描述

这个例子中只有四个网页,如果当前在A网页,那么悠闲的上网者将会各以1/3的概率跳转到B、C、D,这里的3表示A有3条出链,如果一个网页有k条出链,那么跳转任意一个出链上的概率是1/k,同理D到B、C的概率各为1/2,而B到C的概率为0。

在跳转有向图中不考虑指向自己的链接

由于上网者并不一定会点击网络中的链接跳转到新的网址,所以我们需要设置阻尼系数,代表用户点击链接进入新网址的概率。

另外每一个网页本身的重要性也不同,被不同重要性的网址所引用自然也就代表了不同的重要性。比如网址D是一个非常重要的网址,在D中指向了网址B和C,则我们可以认为网址B和网址C也很重要。而网址的重要性又是通过pagerank所表示的,所以这是一个迭代收敛获取每个网址pagerank的过程。

由于每个网址的链接情况是相对固定的,每个网址的pagerank我们可以在用户请求前就计算好,将每个网址的pagerank值保存到数据库中,待用户请求时,直接查询相关网址的pagerank值就可以了。

第一步、提前迭代计算每个网址的pagerank的值。

每个链接的pagerank=每个指向此链接的网页的pagerank/该网页中的链接总数*0.85+0.15,其中0.85表示阻尼因子,表示网页是否点击该网页中的链接。

先初始化每个网页的pagerank的值为1,然后按照上面的方法迭代n次计算每个网页的pagerank。

我们在spyder.py模块的crawler类中添加下面的函数

# (每个链接的pagerank=指向此链接的网页的pagerank/网页中的链接总数*0.85+0.15,其中0.85表示阻尼因子,表示网页是否点击该网页中的链接)# pagerank算法,离线迭代计算,形成每个链接的稳定pagerank值。iterations为迭代计算的次数def calculatepagerank(self, iterations=20):# 清除您当前的pagerank表self.curs.execute('drop table if exists pagerank')self.curs.execute('create table pagerank(urlid primary key,score)')# 初始化每个url,令其pagerank的值为1for (urlid,) in self.curs.execute('select rowid from urllist').fetchall():self.curs.execute('insert into pagerank(urlid,score) values (%d,1.0)' % urlid)self.dbcommit()for i in range(iterations):print("Iteration %d" % (i))for (urlid,) in self.curs.execute('select rowid from urllist').fetchall():pr = 0.15# 循环遍历指向当前网页的所有其他网页for (linker,) in self.curs.execute('select distinct fromid from link where toid=%d' % urlid).fetchall():# 得到链接源对应网页的pagerank值linkingpr = self.curs.execute('select score from pagerank where urlid=%d' % linker).fetchall()[0][0]# 根据链接源求总的链接数linkingcount = self.curs.execute('select count(*) from link where fromid=%d' % linker).fetchall()[0][0]pr += 0.85 * (linkingpr / linkingcount)self.curs.execute('update pagerank set score=%f where urlid=%d' % (pr, urlid))self.dbcommit()

第二步、查询网址的pagerank的值为相关网址进行排名

调用函数生成网址pagerank数据库以后,就可以通过pagerank对查询到的网址进行排名了。

# 根据pagerank值进行评价的函数。(利用外部回值链接进行评价)。
# rows是[(urlid1,wordlocation1_1,wordlocation1_2,wordlocation1_3...),(urlid2,wordlocation2_1,wordlocation2_2,wordlocation2_3...)]def pagerankscore(self,rows):pageranks=dict([(row[0],self.curs.execute('select score from pagerank where urlid=%d' % row[0]).fetchall()[0][0]) for row in rows])maxrank=max(pageranks.values())   #求最大的pagerank值for urlid in pageranks:pageranks[urlid] /= maxrank   #归一化return pageranks   #返回归一化的url的pagerank

第6个排名算法:利用链接文本进行评价的函数

有时,相比于被链接的网址自身所提供的信息而言,我们从指向该网页的链接中所得到的信息会更有价值。因为针对其所指向的网页,网站的开发者会倾向于提供一些解释其内容的简短描述。

# 利用链接文本进行评价的函数。# rows是[(urlid1,wordlocation1_1,wordlocation1_2,wordlocation1_3...),(urlid2,wordlocation2_1,wordlocation2_2,wordlocation2_3...)]def linktextscore(self,rows,wordids):linkscores=dict([(row[0],0) for row in rows])for wordid in wordids:cur=self.curs.execute('select link.fromid,link.toid from linkwords,link where wordid=%d and linkwords.linkid=link.rowid' % wordid)for (fromid,toid) in cur.fetchall():if toid in linkscores:pr=self.curs.execute('select score from pagerank where urlid=%d' % fromid).fetchall()[0][0]linkscores[toid]+=prmaxscore=max(linkscores.values())   #求最大的pagerank值for urlid in linkscores:linkscores[urlid] /= maxscore   #归一化return linkscores

第五部分、综合利用各种评分算法,将查询到的网址进行评分排名

# 对查询到的链接进行排名。参数:rows,wordids查询字符串id列表def getscoredlist(self,rows,wordids):totalscores=dict([(row[0],0) for row in rows])# 对链接进行评价的函数。(权重和评价值),使用了多种评价函数weights=[(1.0,self.locationscore(rows)),   #根据关键词出现的位置获取权重(1.0,self.frequencyscore(rows)),  #根据关键词出现的频率获取权重(1.0,self.pagerankscore(rows)),   #根据pagerank获取权重(1.0,self.linktextscore(rows,wordids)), #根据链接描述获取权重#(5.0,self.nnscore(rows,wordids))   #根据神经网络获取权重] for (weight,scores) in weights:for urlid in totalscores:totalscores[urlid]+=weight*scores[urlid]return totalscores  #返回每个链接的评价值

现在我们已经能根据用户输入字符串,分词成多个关键词,查询相关网址,对网址进行评分。那只要按照分数进行排序,给出排名靠前的搜索引擎就完成了。

#搜索函数:将上面的搜索、评价、排名合并在一起。querystr为用户输入字符串def query(self,querystr):rows,wordids=self.getmatchrows(querystr)  #rows是[(urlid1,wordlocation1_1,wordlocation1_2,wordlocation1_3...),(urlid2,wordlocation2_1,wordlocation2_2,wordlocation2_3...)]# print(rows)rows = list(set(rows))   #去重,位置组合重复计算没有意义,虽然说,所有的都重复了不影响效果。但是计算量增大了。if rows==None or len(rows)==0:print('无法查询到,请使用空格分隔查询关键词')returnscores=self.getscoredlist(rows,wordids)rankedscores=[(score,url) for (url,score) in scores.items()]rankedscores.sort()rankedscores.reverse()for (score,urlid) in rankedscores[0:10]:print('%f\t%d\t%s' % (score,urlid,self.geturlname(urlid)))return wordids,[r[1] for r in rankedscores[0:10]]#根据urlid查询urldef geturlname(self,id):return self.curs.execute("select url from urllist where rowid=%d" % id).fetchall()[0][0]

测试

mynet=nn.searchnet('csdn.db')
if __name__ == '__main__':mysearcher= searcher('csdn.db')searchkey = input("搜索关键词>")wordids,urlids=mysearcher.query(searchkey)print(wordids,urlids)

我们输入“腾讯股票爬虫”

返回的结果为

3.421882	56	http://blog.csdn.net/luanpeng825485697/article/details/78451057
3.026871	58	http://blog.csdn.net/luanpeng825485697/article/details/78442062
1.472765	1	http://blog.csdn.net/luanpeng825485697
1.386459	2	http://blog.csdn.net/luanpeng825485697?viewmode=contents
1.220016	51	http://blog.csdn.net/luanpeng825485697/article/category/7225092
1.220016	48	http://blog.csdn.net/luanpeng825485697/article/category/7083783
1.167393	43	http://blog.csdn.net/luanpeng825485697/article/category/7060769
1.167161	50	http://blog.csdn.net/luanpeng825485697/article/category/7190343
1.152465	47	http://blog.csdn.net/luanpeng825485697/article/category/7145481
1.105118	7	http://blog.csdn.net/luanpeng825485697/article/details/78154417

搜索引擎设计成功。

通过神经网络学习用户点击行为

在上面的搜索引擎设计中我们实现了6种排名算法。下面我们要再介绍一种排名算法。由于这种算法的设计本身并不对文档内容进行解析,而只是对用户行为进行解析,内容较多,知识较难,所以我们单独来讲。

下面要讲的是通过神经网络来实现搜索引擎。没有学习过深度学习的同学可以先不用看这一部分。

神经网络设计搜索引擎,简单的说就是会记忆用户的选择行为。比如搜索引擎根据已有的算法为用户的搜索返回了一些网址。但是这些网址究竟好不好呢。网站并没有这样一个反馈机制。而搜索引擎就是为这样一个反馈机制存在的。当用户选择了一个推荐的网址,神经网络就会为这个网址添加一定的权重,下次搜索相同的内容,更加情愿优先推荐权重大的网址。

将下面的代码存储成nn.py

# 神经网络学习用户点击行为,实现搜索排名
from math import tanh
import sqlite3# 指定任何输出值的斜率
def dtanh(y):return 1.0-y*yclass searchnet:def __init__(self,dbname):self.con=sqlite3.connect(dbname)  #链接数据库self.curs = self.con.cursor()try:self.maketables()except:passdef __del__(self):self.curs.close()self.con.close()def dbcommit(self):  # 保存数据库self.con.commit()def maketables(self):self.curs.execute('create table hiddennode(create_key)')              #创建神经元节点self.curs.execute('create table wordhidden(fromid,toid,strength)')   #输入节点(单词)到神经元的权重,默认-0.2self.curs.execute('create table hiddenurl(fromid,toid,strength)')    #神经元到输出节点(链接)的权重,默认为0self.dbcommit()# 获取节点间权重。layer为0表示输入到神经元之间,为1表示神经元到输出之间def getstrength(self,fromid,toid,layer):if layer==0: table='wordhidden'else: table='hiddenurl'res=self.curs.execute('select strength from %s where fromid=%d and toid=%d' % (table,fromid,toid)).fetchall()if res==None or len(res)==0:if layer==0: return -0.2  # 输入到神经元之间默认为-0.2if layer==1: return 0   # 神经元到输出节点间默认为0return res[0][0]# 设置数据库中节点间权重。layer为0表示输入到神经元之间,为1表示神经元到输出之间def setstrength(self,fromid,toid,layer,strength):if layer==0: table='wordhidden'else: table='hiddenurl'res=self.curs.execute('select rowid from %s where fromid=%d and toid=%d' % (table,fromid,toid)).fetchall()if res==None or len(res)==0:self.curs.execute('insert into %s (fromid,toid,strength) values (%d,%d,%f)' % (table,fromid,toid,strength))else:rowid=res[0][0]self.curs.execute('update %s set strength=%f where rowid=%d' % (table,strength,rowid))# 根据已知正确的输入输出结果,创建神经元,建立输入与神经元间里连接,神经元与输出间连接。每一种输入组合都创建一个神经节点def generatehiddennode(self,wordids,urlids):# if len(wordids)>3: return None   #对于有3个输出单词的我们不做处理,太复杂# 检测我们是否已经为这组输入建好了一个节点sorted_words=[str(id) for id in wordids]sorted_words.sort()createkey='_'.join(sorted_words)res=self.curs.execute("select rowid from hiddennode where create_key='%s'" % createkey).fetchall()# 如果没有,就创建if res==None or len(res)==0:cur=self.curs.execute("insert into hiddennode (create_key) values ('%s')" % createkey)hiddenid=cur.lastrowid# 设置默认权重for wordid in wordids:self.setstrength(wordid,hiddenid,0,1.0/len(wordids))   #设置输入节点到神经元间的权重为1/输入数量for urlid in urlids:self.setstrength(hiddenid,urlid,1,0.1)   #设置神经元到输出节点间权重为0.1self.dbcommit()# 根据输入关键词id和相关的链接的id,获取数据库中神经元节点的id。(相关的链接也就是初步查询到的链接,只要链接的网页中出现过关键词就会被认为相关)def getallhiddenids(self,wordids,urlids):hiddenids=[]for wordid in wordids:cur=self.curs.execute('select toid from wordhidden where fromid=%d' % wordid).fetchall()for row in cur:hiddenids.append(row[0])for urlid in urlids:cur=self.curs.execute('select fromid from hiddenurl where toid=%d' % urlid).fetchall()for row in cur:hiddenids.append(row[0])return hiddenids  # 返回神经元节点# 构建一个神经网络def setupnetwork(self,wordids,urlids):# 值列表:输入:神经元、输出self.wordids=wordidsself.hiddenids=self.getallhiddenids(wordids,urlids)self.urlids=urlids# 构建输入节点、神经元、输出节点。就是前面的输入、神经元、输出。这里用了一个更加普遍的名称self.ai = [1.0]*len(self.wordids)self.ah = [1.0]*len(self.hiddenids)self.ao = [1.0]*len(self.urlids)# 建立权重矩阵(线性组合系数矩阵):输入-神经元,  神经元-输出self.wi = [[self.getstrength(wordid,hiddenid,0)for hiddenid in self.hiddenids]for wordid in self.wordids]self.wo = [[self.getstrength(hiddenid,urlid,1)for urlid in self.urlids]for hiddenid in self.hiddenids]# 前馈算法:一列输入,进入神经网络,返回所有输出结果的活跃程度。越活跃也好。因为神经网络每向下传播一层就会衰弱一层。衰弱函数使用tanh这种0时陡峭,无限大或无限小时平稳的函数def feedforward(self):# 查询的单词是仅有的输入for i in range(len(self.wordids)):self.ai[i] = 1.0   #输入节点的活跃程度就设为1# 根据输入节点活跃程度,获取神经元节点的活跃程度for j in range(len(self.hiddenids)):sum = 0.0for i in range(len(self.wordids)):sum = sum + self.ai[i] * self.wi[i][j]  # 线性组合self.ah[j] = tanh(sum)   #使用tanh表示神经元对输入的反应强度。(因为tanh是一个在0附近震荡强烈,在远离0时趋于稳定的函数)# 根据神经元节点活跃程度,获取输出节点的活跃程度for k in range(len(self.urlids)):sum = 0.0for j in range(len(self.hiddenids)):sum = sum + self.ah[j] * self.wo[j][k]  # 线性组合self.ao[k] = tanh(sum)return self.ao[:]# =============================以上是公共实例函数=======================================#=======================getresult是应用神经网络进行搜索的函数==============================#  针对一组单词和url给出输出def getresult(self,wordids,urlids):self.setupnetwork(wordids,urlids)return self.feedforward()# ============================下面是使用反向传播法进行神经网络训练============================#前馈训练法:依据当前权重预测输出,计算误差,更正权重。用户每选择一次,进行一次训练#用户每选择一次链接,就调整一次权重。targets表示正确的输出结果。即用户选择的链接def backPropagate(self, targets, N=0.5):# 计算输出层误差output_deltas = [0.0] * len(self.urlids)for k in range(len(self.urlids)):error = targets[k]-self.ao[k]    #计算正确输出和预测输出之间的误差output_deltas[k] = dtanh(self.ao[k]) * error   #确定总输入需要如何改变# 计算神经元误差:hidden_deltas = [0.0] * len(self.hiddenids)for j in range(len(self.hiddenids)):error = 0.0for k in range(len(self.urlids)):error = error + output_deltas[k]*self.wo[j][k]  #将每个神经元-输出间的权重值乘以输出节点的改变量,再累加求和,从而改变节点的输出结果hidden_deltas[j] = dtanh(self.ah[j]) * error  #确定节点的总输入所需的该变量# 更新神经元-输出间权重for j in range(len(self.hiddenids)):for k in range(len(self.urlids)):change = output_deltas[k]*self.ah[j]self.wo[j][k] = self.wo[j][k] + N*change# 更新输入-神经元间权重for i in range(len(self.wordids)):for j in range(len(self.hiddenids)):change = hidden_deltas[j]*self.ai[i]self.wi[i][j] = self.wi[i][j] + N*change# 更新数据库def updatedatabase(self):# 将值写入数据库for i in range(len(self.wordids)):for j in range(len(self.hiddenids)):self.setstrength(self.wordids[i],self. hiddenids[j],0,self.wi[i][j])for j in range(len(self.hiddenids)):for k in range(len(self.urlids)):self.setstrength(self.hiddenids[j],self.urlids[k],1,self.wo[j][k])self.dbcommit()# 对神经网络进行一次训练。wordids为查询关键词id,urlids为查找到相关的url的id,selectedurl为选中的url的iddef trainquery(self,wordids,urlids,selectedurlid):# 第一次运行时,在数据库中创建表。以默认权重赋值self.generatehiddennode(wordids,urlids)# 创建神经网络类的属性。(输入、神经元、输出、两个权重)self.setupnetwork(wordids,urlids)self.feedforward()   #执行前馈算法,根据输入获取输出targets=[0.0]*len(urlids)targets[urlids.index(selectedurlid)]=1.0  #获取用户选择的正确链接error = self.backPropagate(targets)  #执行反向传播法修正网络self.updatedatabase()   #更新数据库if __name__ == '__main__':con = sqlite3.connect('csdn.db')curs = con.cursor()command = 'select * from hiddennode'  # "select fromid,toid from link"cur = curs.execute(command)res = cur.fetchall()if res != None and len(res) > 0:for row in res:print(row)

根据长时间的用户行为,为新搜索相关的网址进行排名

# 根据神经网络(用户点击行为学习)进行评价的函数。神经网络在nn.py中实现。# rows是[(urlid1,wordlocation1_1,wordlocation1_2,wordlocation1_3...),(urlid2,wordlocation2_1,wordlocation2_2,wordlocation2_3...)]def nnscore(self,rows,wordids):# 获得一个由唯一的url id构成的有序列表urlids=[urlid for urlid in dict([(row[0],1) for row in rows])]nnres=mynet.getresult(wordids,urlids)scores=dict([(urlids[i],nnres[i]) for i in range(len(urlids))])return self.normalizescores(scores)

有了上面的排名算法,就可以添加到之前的综合评分函数中去了。


http://chatgpt.dhexx.cn/article/G2jZ2yNO.shtml

相关文章

工业数据挖掘实例

智能的基础是智能决策&#xff0c;所有的决策都来自于分析。所以简单说所有的智能都是做好两件事&#xff1a;收集数据&#xff0c;使用数据。数据挖掘技术根据业务数据不同有不同的应用场景。在我以往工作中主要在以下领域有应用尝试&#xff1a; 市场营销&#xff1a;用数据…

[数据挖掘案例]逻辑回归LR模型实现电商商品个性化推荐

目录 一、问题描述 二、数据摸底 三、数据清洗和特征筛选 3.1 数据抽取和清洗 3.2 特征筛选&#xff1a;决策树 3.3 特征分布转换 3.4 特征共线性检查 四、模型搭建 4.1 数据集 4.2 模型训练 4.3 模型验证 五、模型上线效果跟踪 一、问题描述 在电商平台中&#xff…

数据挖掘案例(2):用户画像

内容分为两个部分&#xff1a;     第一部分&#xff1a;用户画像概述     第二部分&#xff1a;用户画像案例 数据和源码 移步到Github &#xff1a; https://github.com/Stormzudi/Data-Mining-Case 邮箱&#xff1a;442395572qq.com 目录 第一部分&#xff1a;1…

数据挖掘案例实战:利用LDA主题模型提取京东评论数据(一)

泰迪智能科技&#xff08;数据挖掘平台&#xff1a;TipDM数据挖掘平台&#xff09;最新推出的数据挖掘实战专栏 专栏将数据挖掘理论与项目案例实践相结合&#xff0c;可以让大家获得真实的数据挖掘学习与实践环境&#xff0c;更快、更好的学习数据挖掘知识与积累职业经验 专栏…

数据挖掘案例实战:利用LDA主题模型提取京东评论数据(二)

泰迪智能科技&#xff08;数据挖掘平台&#xff1a;TipDM数据挖掘平台&#xff09;最新推出的数据挖掘实战专栏 专栏将数据挖掘理论与项目案例实践相结合&#xff0c;可以让大家获得真实的数据挖掘学习与实践环境&#xff0c;更快、更好的学习数据挖掘知识与积累职业经验 专栏…

数据挖掘学习(四)——常见案例总结

笔者是一个痴迷于挖掘数据中的价值的学习人&#xff0c;希望在平日的工作学习中&#xff0c;挖掘数据的价值&#xff0c;找寻数据的秘密&#xff0c;笔者认为&#xff0c;数据的价值不仅仅只体现在企业中&#xff0c;个人也可以体会到数据的魅力&#xff0c;用技术力量探索行为…

数据挖掘实例1:亲和性分析示例(代码、注释、运行结果)

前言 本实例采用python3环境&#xff0c;编辑器采用Jupyter Notebook&#xff0c;安装使用方法请参考&#xff0c;本实例中所用到的附件内容放在文末&#xff0c;如果想要自行运行一下代码&#xff0c;可以尝试一下。 Jupyter Notebook介绍、安装及使用教程 亲和性分析示例 …

Python数据挖掘 数据预处理案例(以航空公司数据为例)

Python数据预处理 一、内容&#xff1a; 1、数据清洗 2、数据集成 3、数据可视化 二、实验数据 根据航空公司系统内的客户基本信息、乘机信息以及积分信息等详细数据&#xff0c;依据末次飞行日期( LAST_FLIGHT_DATE)&#xff0c;以2014年3月31日为结束时间&#xff0c;选取…

Axure 9.0.0.3712 授权码

更新日志 2020年8月4日&#xff0c;Axure 更新了最新的版本&#xff0c;本次的版本号为 Axure RP 9.0.0.3712&#xff0c;具体更新内容如下&#xff1a; 自从Axure发布了9.0版本以后&#xff0c;很多小伙伴之前使用的注册码已经失效了&#xff0c;为了不影响想体验的小伙伴&am…

axure8.1 授权码

Licensee: University of Science and Technology of China (CLASSROOM) Key: DTXRAnPn1P65Rt0xB4eTQ4bF5IUF0gu0X9XBEUhM4QxY0DRFJxYEmgh4nyh7RtL 原文链接&#xff1a;http://blog.csdn.net/quanqinyang/article/details/78217464

关于Axure RP 的授权,我猜你还想知道......

Axure RP发展到今天&#xff0c;已经出到9的版本&#xff0c;当然破解授权码层出不穷。 有条件的朋友建议使用正版&#xff0c;可以避免以后可能出现的一些问题。 关于Axure授权码&#xff0c;有几点给大家说明一下。 一个Axure RP 的授权码是否可以多个人使用&#xff1f; 一…

2019年最新最全香港5大银行开户条件及攻略

跑去香港开户的同学,大概都了解,现在香港银行开户已经越来越严格了。银行工作人员要么就以资料要审核委婉拒绝,要么就要求客户存入几百万的存款才肯开户。2019年货币贬值加速,港币美元升值。户开开与各大行银行经理联合给大家总结了香港的5大最常见银行,汇丰、渣打、中银、…

2019年香港银行开户条件有哪些?个人账户申请被拒绝后该怎么处理比较好!!!

网上关于香港银行开户的攻略很多,但是由于政策跟银行系统不断升级的问题,银行已经全面出新政策了,为此,小编这次专门跑了一圈香港,整理了最新的一份开户大全,有评星,有体验。历史上关于香港各大银行开户的攻略都在这里了。各位有需要的小伙伴看这里,关于香港各大银行开…

一定要收藏!!!2019取消管理费最新最全香港开户攻略

自2019年8月1日起,很多香港银行相继取消多个账户管理费,香港开户难度也再度提升,还流传汇丰银行对于内地旅客来香港开户不能获批的消息,但多位在香港的中资和外资银行人士在接受媒体采访时表示暂无收到相关通知。不管怎么样!卡君还是提醒有需要开港卡的尽早开户!!!防范…

说说香港银行开户的一些细节问题

很多人对“香港银行开户”存在很大误区,认为还可以轻松地通过视频异地开户,或简单地带上公司注册文件和身份证到香港银行柜台办理手续,一两周就能拿到银行账户。 实际上2012年汇丰事件后,一石激起千层浪,银行业界人心惶惶,谁都不想成为下一个“幸运儿”,现各银行纷纷加…

测试用例的基本方法

什么是测试用例 测试用例的定义 测试用例是执行测试的依据&#xff0c;把测试系统的操作步骤用文档的形式描述出来 1&#xff1a;测试用例是为达到最佳的测试效果或高效的揭露隐藏的错误&#xff0c;而精心设计的少量测试数据&#xff0c;包括测试输入、执行条件和预期的结果…

几种测试用例方法

针对穷举场景设计测试点 针对限定边界规则设计测试点 对多条件依赖关系进行设计测试点 对于项目业务进行设计用例 1、等价类划分法&#xff1a;针对穷举场景设计测试点 1&#xff09;说明&#xff1a;在所有测试数据中&#xff0c;具有某种共同特征的数据集进行划分 2&#xff…

设计测试用例的方法

目录 一、根据需求去设计测试用例 二、具体的设计测试用例的方法 1.等价类 2.边界值 3.因果图法 4.正交法 5.场景法 6.错误猜测法 三、如何评价测试用例的好坏 一、根据需求去设计测试用例 验证需求的正确性。 分析需求&#xff0c;细化需求&#xff0c;从需求中提炼…

设计测测试用例的五大方法

目录 一.等价类 1.等价类的概念 2.等价类的分类 &#xff08;1&#xff09;有效等价类 &#xff08;2&#xff09;无效等价类 3.使用场景 4.例子 二.边界值 1.边界值的概念 2.例子 三.因果图法 1.因果图法的概念 2.因果图中的逻辑图 3.因果图设计测试用例步骤 4.…

bat简单的批处理命令

授人以鱼不如授人以渔 如何查看dos命令帮助 命令名 /? 路径使用 \ 不能使用/ 例如查看del命令帮助 1. echo 显示信息&#xff0c;关闭、启用命令回显 echo hello关闭回显 echo off echo hello开启回显 echo on2. 关闭当前语句回显 3. del 删除一个或多个文件 /p 删除…