学习《集体智慧编程》第4章的思路整理:
本章的主要内容就是建立一个模拟的全文搜索引擎,主要的大步骤为:1.检索网页,2.建立索引,3.对网页进行搜索 4.多种方式对搜索结果进行排名
一、检索网页:主要利用python写了一个爬虫软件,通过爬取一个网站上链接来不断的扩充爬取的内容。主要利用了python的urllib库和BeautifulSoup库。这部分比较简单,核心代码如下:
def crawl(self,pages,depth=2): for i in range(depth): newpages=set() for page in pages: try: c=request.urlopen(page) except: print(‘Could not open %s‘ %page) continue soup=BeautifulSoup(c.read(),‘html5lib‘) self.addtoindex(page,soup) links=soup(‘a‘) for link in links: if (‘href‘ in dict(link.attrs)): url=urljoin(page,link[‘href‘]) if url.find("‘")!=-1: continue url=url.split(‘ ‘)[0]#去掉位置信息 if url[0:4]==‘http‘ and not self.isindexed(url): newpages.add(url) linkText=self.gettextonly(link) self.addlinkref(page,url,linkText) self.dbcommit() pages=newpages ####位置!!!将一个网页里面的连接保存起来
这里采用了广度优先搜索方式
二、建立索引
1.sqlite数据库的使用:
在python2.x版本中需要自己下载安装这个数据库,但是在python3.x中我们只需要 import sqlite3即可。首先需要修改类的一些内建函数:
###初始化crawler类并且传入数据库名称 def __init__(self,dbname): self.con=sqlite3.connect(dbname) def __del__(self): self.con.close() def dbcommit(self): self.con.commit()
然后建立数据库,这里首先需要准备5张数据库表,分别为:urllist(保存的是已经过索引的url列表)、wordlist(保存的是单词列表)、wordlocation(保存的是单词在文档中的所处的位置列表)、link(保存两个Url ID 指明从一个表到另一张表的连接关系) linkwords(利用了字段wordid和linkid记录了哪些单词和链接实际相关),并且建立一些索引完整的数据库schema如下图:
具体代码如下:
def createindextables(self): # self.con.execute(‘drop table if exists urllist,wordlist,wordlocation,link,linkwords‘) self.con.execute(‘create table urllist(url)‘) self.con.execute(‘create table wordlist(word)‘) self.con.execute(‘create table wordlocation(urlid,wordid,location)‘) self.con.execute(‘create table link(fromid INTEGER ,toid INTEGER )‘) self.con.execute(‘create table linkwords(wordid,linkid)‘) self.con.execute(‘create index wordidx on wordlist(word)‘) self.con.execute(‘create index urlidx on urllist(url)‘) self.con.execute(‘create index wordurlidx on wordlocation(wordid)‘) self.con.execute(‘create index urltoidx on link(toid)‘) self.con.execute(‘create index urlfromidx on link(fromid) ‘) self.dbcommit()
2.在网页中查找单词,此处还是利用了BeautifulSoup的强大功能,将网页中的所有的文字信息提取出来,去掉一些标签,属性之类的。然后进行单词的分词。在这里的单词分词都是针对英文单词的,关于中文单词的分词,有个名为jieba的库可以实现。感兴趣的可以看一下:https://github.com/fxsjy/jieba/tree/jieba3k
##从一个Html网页中抓取文字(不带标签的) def gettextonly(self,soup): v=soup.string if v==None: c=soup.contents ####把content是错误的写成了content resulttext=‘‘ for t in c: subtext=self.gettextonly(t) resulttext+=subtext+‘\n‘ return resulttext else: return v.strip() ###根据任何非空白字符进行分词处理 def separatewords(self,text): ##该方法仅仅局限于分英文单词 splitter=re.compile(‘\\W*‘) return [s.lower() for s in splitter.split(text) if s!=‘ ‘] ###中文的应用了jieba库,关于该库可以参见https://github.com/fxsjy/jieba/tree/jieba3k # return (list(jieba.cut_for_search(text))) ##从一个Html网页中抓取文字(不带标签的) def gettextonly(self,soup): v=soup.string if v==None: c=soup.contents ####把content是错误的写成了content resulttext=‘‘ for t in c: subtext=self.gettextonly(t) resulttext+=subtext+‘\n‘ return resulttext else: return v.strip() ###根据任何非空白字符进行分词处理 def separatewords(self,text): ##该方法仅仅局限于分英文单词 splitter=re.compile(‘\\W*‘) return [s.lower() for s in splitter.split(text) if s!=‘ ‘] ###中文的应用了jieba库,关于该库可以参见https://github.com/fxsjy/jieba/tree/jieba3k # return (list(jieba.cut_for_search(text)))
3、加入索引,将网页以及所有的单词加入索引,在网页和单词之间建立关联,并且保存单词在文档中的位置。在本例中,文档的位置就是在列表中的索引号
###为每个网页建立索引 def addtoindex(self,url,soup): if self.isindexed(url): return print ("Indexing:"+url) ##获取每个单词 text=self.gettextonly(soup) words=self.separatewords(text) ##得到URL的id urlid=self.getentryid(‘urllist‘,‘url‘,url) ##将每个单词与该url相关联 for i in range(len(words)): word=words[i] if word in ignorewords: continue wordid=self.getentryid(‘wordlist‘,‘word‘,word)####此处出现错误 self.con.execute("insert into wordlocation(urlid,wordid,location) values (%d,%d,%d)" % (urlid, wordid, i)
三、查询
def getmatcheows(self,q): ###构造查询的字符串 fieldlist=‘w0.urlid‘ tablelist=‘‘ clauselist=‘‘ wordids=[] ##根据空格拆分单词 words=q.split(‘ ‘) print(words) 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 # Create the query from the separate parts 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
四、排名
1.基于内容的排名:评价度量包括以下三种:单词频度,文档位置,单词距离
首先我们要设定一个归一化函数,对结果进行归一化处理,使得他们具有相同的值域和变化方向。
###归一化函数 返回一个介于0~1之间的值 def normalizescores(self,scores,smallIsBetter=0): vsmall=0.00001 #避免被零整除 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()])
基于内容的排名的算法属于传统的早期搜索引擎使用的度量方式。其三种评价度量的代码分别如下:
###单词频度作为评价度量 def frequencyscore(self,rows): count=dict([row[0],0] for row in rows) for row in rows: count[row[0]]+=1 return self.normalizescores(count) ###文档位置 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]]=loc return self.normalizescores(locations,smallIsBetter=1) #单词距离 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)
2.著名的PageRank算法
Google使用的算法,为每一个网页都赋予了一个只是网页重要程度的评价值。网页的重要性是依指向该网页的所有的其他网页的重要性以及这些网页所包含的连接数求得的。
PageRank 使用一个阻尼因子:0.85
关于该算法的具体内容,可以自行百度。。。也可以查看http://www.cnblogs.com/FengYan/archive/2011/11/12/2246461.html的博文
代码实现:
####PageRank def calculatepagerank(self,iteration=20): ###清除当前的PageRank表 self.con.execute("drop table if exists pagerank") self.con.execute("create table pagerank(urlid primary key,score)") ####初始化每一个url,令其PageRank值为1 self.con.execute("insert into pagerank select rowid,1.0 from urllist") self.dbcommit() for i in range(iteration): 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): ###得到相应的PR值 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()