这里返回的是包含查询单词的URLID所构成的列表,但是现在只能实现单个单词的查询,这种意义不大。接下去就将程序改进,可以查询多个单词。
查询
现在我们已经有了可用的crawler类和经过索引的大堆文件,接下来可以准备搜索引擎的搜索部分。首先,建立一个用于搜索的类:
#第二部分:查询
#新建一个用于搜索的类
class searcher:
def __init__(self,dbname):
self.con = sqlite.connect(dbname)
def __del__(self):
self.con.close()
我们需要一个查询函数,这个函数接受查询的 字符串为参数:
def getmatchrows(self, q):
# 构造查询的字符串
fieldlist = 'w0.urlid'
tablelist = ''
clauselist = ''
wordids = []
# 根据空格拆分单词
words = q.split(' ')
tablenumber = 0
for word in words:
# Get the word ID
wordrow = self.con.execute(
"select rowid from wordlist where word='%s'" % word).fetchone()
if wordrow != None:
wordid = wordrow[0]
wordids.append(wordid)
if tablenumber > 0:
tablelist += ','
clauselist += ' and '
clauselist += 'w%d.urlid=w%d.urlid and ' % (tablenumber - 1, tablenumber)
fieldlist += ',w%d.location' % tablenumber
tablelist += 'wordlocation w%d' % tablenumber
clauselist += 'w%d.wordid=%d' % (tablenumber, wordid)
tablenumber += 1
#根据各个组分,建立查询
fullquery = 'select %s from %s where %s' % (fieldlist, tablelist, clauselist)
#print fullquery
cur = self.con.execute(fullquery)
rows = [row for row in cur]
return rows, wordids```
上面这个函数的功能就是为列表当中的每一个单词建立一个指向wordlocation表的引用,并根据对应的URLID将他们连接起来进行联合查询:
![多词查询](http://upload-images.jianshu.io/upload_images/2730963-795d8679954a85f8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)可以看到单词位置的不同组合,同一个URL会出现很多次。接下来我们将介绍__基于内容的排名法__,它是根据网页的内容,利用一些度量规则对我们现在的查询结果进行一些判断。还有一种是__外部回指链接排名法__是利用站点的链接结构来决定查询结果的重要性。
#####基于内容的排名
首先,我们需要一个新的方法,这个方法接受查询请求,将获取得到的行集置于字典当中,并用格式化列表的形式输出。在searcher类当中添加:
def getscoredlist(self, rows, wordids):
totalscores = dict([row[0], 0] for row in rows)
# 这里待会会补充评价函数的地方
#weights = []
for (weight, scores) in weights:
for url in totalscores:
totalscores[url] += weight * scores[url]
return totalscores
def geturlname(self, id):
return self.con.execute("select url from urllist where rowid="
"%d" % id).fetchone()[0]
def query(self, q):
rows, wordids = self.getmatchrows(q)
scores = self.getscoredlist(rows, wordids)
rankedscores = sorted([(score, url) for (url, score) in scores.items()], reverse=1)
for (score, urlid) in rankedscores[0:10]:
print'%f\t%s' % (score, self.geturlname(urlid))
query现在没有对结果进行任何的打分评价,不过我们可以看一下它输出了URL和代表评价值的占位符:![没有评价函数的排名](http://upload-images.jianshu.io/upload_images/2730963-6f2787cf9b8af357.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
######*归一化函数*
这里再介绍一个需要归一化的函数。因为我们返回的是URLID和数字评分,但是有的时候,衡量一个网页的质量,评分是越高越好,有的是越低越好。对于不同的方法,我们需要一种对结果进行归一化处理的方法,使得他们具有相同的值域以及变化方向:
定义一个归一化函数,这个函数是将我们的评分值变成0-1之间
def normalizescores(self, scores, smallIsBetter=0):
vsmall = 0.00001 # Avoid division by zero errors
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 = vsmall
return dict([(u, float(c) / maxscore) for (u, c) in scores.items()])
调用这个函数就会对结果进行 归一化处理并且返回一个介于0-1的数值。
######1、单词频度
顾名思义,就是根据我们查询的单词在文档中出现的次数来帮助我们判断文档的相关程度。
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)
实验结果:
![单词频率](http://upload-images.jianshu.io/upload_images/2730963-50cb0b3a5d22e91f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
######2、文档位置
这一方法基于的思想是如果一个网页与搜索的单词有关,那么这个单词就很有可能出现在靠近网页开始的位置。根据这一点,搜索引擎可以对待查询的单词在文档中的位置进行打分。
def locationscore(self,rows):
location=dict([(row[0],100000) for row in rows])
for row in rows:
loca=sum(row[1:])
if loca<location[row[0]]:location[row[0]] = loca
return self.normalizescores(location,1)
实验结果:
![文档位置](http://upload-images.jianshu.io/upload_images/2730963-5232457d1665f670.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
当然可以将上面两个评价指标结合起来,并赋予不同的权重:
weights=[(1.0,self.frequencyscore(rows)),(1.5,self.locationscore(rows))]
######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]] = dist
return self.normalizescores(mindistance, smallIsBetter=1)
实验结果:
![单词距离](http://upload-images.jianshu.io/upload_images/2730963-0d0c21feadd34be3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
#####利用外部回指链接
到目前为止,我们介绍的方法都是基于网页内容的。现在我们可以通过考察外界对该网页的链接来判断一个网页的质量。
在一开始我们就建立了一个表格link,来记录一个链接它的源和目的相对于的URLID。
添加一个关联两个网页的连接
def addlinkref(self,urlFrom,urlTo,linkText):
words = self.separatewords(linkText)
fromid = self.getentryid('urllist', 'url', urlFrom)
toid = self.getentryid('urllist', 'url', urlTo)
if fromid == toid: return
cur = self.con.execute("insert into link(fromid,toid) values (%d,%d)" % (fromid, toid))
linkid = cur.lastrowid
for word in words:
if word in ignorewords: continue
wordid = self.getentryid('wordlist', 'word', word)
self.con.execute("insert into linkwords(linkid,wordid) values (%d,%d)" % (linkid, wordid))
######1、简单计数
就是说在每一个网页上统计链接的数目,将其作为衡量网页质量的指标。
利用外部回指连接
def inboundlinkscore(self, rows):
uniqueurls = dict([(row[0], 1) for row in rows])
inboundcount = dict(
[(u, self.con.execute('select count(*) from link where toid=%d' % u).fetchone()[0]) for u in uniqueurls])
return self.normalizescores(inboundcount)
######2、PageRank算法
pagerank算法网上都有,我也不赘述了。我们这个实验当中,建立一个pagerank的表格,然后预先计算好Pagerank值
# pagerank算法
def calculatepagerank(self, iterations=20):
# 清除当前的pagerank
self.con.execute('drop table if exists pagerank')
self.con.execute('create table pagerank(urlid primary key,score)')
# 初始化每一个url,令它等于1
self.con.execute('insert into pagerank select rowid,1.0 from urllist')
self.dbcommit()
for i in range(iterations):
print"Iteration %d" % (i)
for (urlid,) in self.con.execute('select rowid from urllist'):
pr = 0.15
# 循环所有指向这个页面的外部链接
for (linker,) in self.con.execute('select distinct fromid from link where toid=%d' % urlid):
linkingpr = self.con.execute('select score from pagerank where urlid=%d' % linker).fetchone()[0]
# 根据链接源,求得总的连接数
linkingcount = self.con.execute('select count(*) from link where fromid=%d' % linker).fetchone()[0]
pr += 0.85 * (linkingpr / linkingcount)
self.con.execute('update pagerank set score=%f where urlid=%d' % (pr, urlid))
self.dbcommit()
这个函数最初将每一个网页的Pagerank值都设置为1,经过20次的迭代,计算出每一个网页的Pagerank值。
![pagerank](http://upload-images.jianshu.io/upload_images/2730963-e1f279e74899c123.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![pagerank1.png](http://upload-images.jianshu.io/upload_images/2730963-39dfde4d6201328b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
到目前为止,介绍了基于内容的网页排名和基于外部回指链接的网页排名算法。其实现在还有从用户的点击行为当中学习。利用神经网络来在线搜索的排名。这个接下一节再讲。
全部代码,戳这里------>https://github.com/GreenGitHuber/Programming-Collective-Intelligence/blob/master/chapter4_Search-Rank/searchengine.py
######周末快乐^D^