Chapter4 搜索和排序_2

到目前为止,我们已经可以为网页建立真正的索引了。实验的结果如图所示:
查询.png

这里返回的是包含查询单词的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^


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容