python全文检索引擎_python搜索引擎

用python如何实现一个站内搜索引擎?

先想想搜索引擎的工作流程:

1、网页搜集。用深度或者广度优先的方法搜索某个网站,保存下所有的网页,对于网页的维护采用定期搜集和增量搜集的方式。

2、建立索引库。首先,过滤掉重复的网页,虽然他们有不同的URL;然后,提取出网页的正文;最后,对正文切词,建立索引。索引总要有个顺序,利用pagerank算法给每个网页加个权值。

3、提供搜索服务。首先,切分查询词;然后,对索引结果排序,结合原来的权值和用户的查询历史等作为新的索引顺序;最后,还要显示文档摘要。

完整流程如下:

-----------------------------------以下文字引用自 万维网Web自动搜索引擎(技术报告)邓雄(Johnny Deng) 2006.12

“网络蜘蛛”从互联网上抓取网页,把网页送入“网页数据库”,从网页中“提取URL”,把URL送入“URL数据库”,“蜘蛛控制”得到网页的URL,控制“网络蜘蛛”抓取其它网页,反复循环直到把所有的网页抓取完成。

系统从“网页数据库”中得到文本信息,送入“文本索引”模块建立索引,形成“索引数据库”。同时进行“链接信息提取”,把链接信息(包括锚文本、链接本身等信息)送入“链接数据库”,为“网页评级”提供依据。

“用户”通过提交查询请求给“查询服务器”,服务器在“索引数据库”中进行相关网页的查找,同时“网页评级”把查询请求和链接信息结合起来对搜索结果进行相关度的评价,通过“查询服务器”按照相关度进行排序,并提取关键词的内容摘要,组织最后的页面返回给“用户”。

-----------------------------------引用到此结束

写一个搜索引擎的想法源自于我正在学习python语言,想借此来驱动自己。

目前有思路的三个模块:网页爬虫(广度优先搜索),提取网页正文(cx-extractor),中文分词(smallseg)。

网页爬虫

广度优先搜索,抓取新浪站内10000个页面(url中含有‘sina.com.cn/’的页面)

抓取: urllib2.urlopen()

解析:htmllib.HTMLParser

存储:redis

每个URL对应一个IDSEQ序列(从1000000开始增加)

URL:IDSEQ      存储URL

PAGE:IDSEQ      存储URL对应的HTML页面源码

URLSET:IDSEQ    每个URL对应一个指向它的URL(IDSEQ)集合

代码如下:

ContractedBlock.gif

ExpandedBlockStart.gifView Code

1 #!/usr/bin/python

2 from spdUtility importPriorityQueue,Parser3 importurllib24 importsys5 importos6 importinspect7 importtime8 g_url = 'http://www.sina.com.cn'

9 g_key = 'www'

10 """

11 def line():12 try:13 raise Exception14 except:15 return sys.exc_info()[2].tb_frame.f_back.f_lineno"""

16

17 defupdatePriQueue(priQueue, url):18 extraPrior = url.endswith('.html') and 2 or019 extraMyBlog = g_key in url and 5 or020 item =priQueue.getitem(url)21 ifitem:22 newitem = (item[0]+1+extraPrior+extraMyBlog, item[1])23 priQueue.remove(item)24 priQueue.push(newitem)25 else:26 priQueue.push( (1+extraPrior+extraMyBlog,url) )27

28 defgetmainurl(url):29 ix = url.find('/',len('http://') )30 if ix >0 :31 returnurl[:ix]32 else:33 returnurl34 defanalyseHtml(url, html, priQueue, downlist):35 p =Parser()36 try:37 p.feed(html)38 p.close()39 except:40 return

41 mainurl =getmainurl(url)42 printmainurl43 for (k, v) inp.anchors.items():44 for u inv :45 if not u.startswith('http://'):46 u = mainurl +u47 if notdownlist.count(u):48 updatePriQueue( priQueue, u)49

50 defdownloadUrl(id, url, priQueue, downlist,downFolder):51 downFileName = downFolder+'/%d.html' %(id,)52 print 'downloading', url, 'as', downFileName, time.ctime(),53 try:54 fp =urllib2.urlopen(url)55 except:56 print '[ failed ]'

57 returnFalse58 else:59 print '[ success ]'

60 downlist.push( url )61 op = open(downFileName, "wb")62 html =fp.read()63 op.write( html )64 op.close()65 fp.close()66 analyseHtml(url, html, priQueue, downlist)67 returnTrue68

69 defspider(beginurl, pages, downFolder):70 priQueue =PriorityQueue()71 downlist =PriorityQueue()72 priQueue.push( (1,beginurl) )73 i =074 while not priQueue.empty() and i

78 print '\nDownload',i,'pages, Totally.'

79

80 defmain():81 beginurl =g_url82 pages = 20000

83 downloadFolder = './spiderDown'

84 if notos.path.isdir(downloadFolder):85 os.mkdir(downloadFolder)86 spider( beginurl, pages, downloadFolder)87

88 if __name__ == '__main__':89 main()

后期优化:

目前程序抓取速度较慢,瓶颈主要在urllib2.urlopen等待网页返回,此处可提出来单独做一个模块,改成多进程实现,redis的list可用作此时的消息队列。

扩展程序,实现增量定期更新。

抽取网页正文

根据提陈鑫的论文《基于行块分布函数的通用网页正文抽取算法》,googlecode上的开源项目(http://code.google.com/p/cx-extractor/)

“作者将网页正文抽取问题转化为求页面的行块分布函数,这种方法不用建立Dom树,不被病态HTML所累(事实上与HTML标签完全无关)。通过在线性时间内建立的行块分布函数图,直接准确定位网页正文。同时采用了统计与规则相结合的方法来处理通用性问题。作者相信简单的事情总应该用最简单的办法来解决这一亘古不变的道理。整个算法实现代码不足百行。但量不在多,在法。”

他的项目中并没有python版本的程序,下面的我根据他的论文和其他代码写的python程序,短小精悍,全文不过50行代码:

ContractedBlock.gif

ExpandedBlockStart.gifView Code

1 #!/usr/bin/python

2 #coding=utf-8

3 #根据 陈鑫《基于行块分布函数的通用网页正文抽取算法》

4 #Usage: ./getcontent.py filename.html

5 importre6 importsys7 defPreProcess():8 globalg_HTML9 _doctype = re.compile(r'*?>', re.I|re.S)10 _comment = re.compile(r'', re.S)11 _javascript = re.compile(r'.*?<\/script>', re.I|re.S)12 _css = re.compile(r'.*?<\/style>', re.I|re.S)13 _other_tag = re.compile(r'<.*?>', re.S)14 _special_char = re.compile(r'&.{1,5};|.{1,5};')15 g_HTML = _doctype.sub('', g_HTML)16 g_HTML = _comment.sub('', g_HTML)17 g_HTML = _javascript.sub('', g_HTML)18 g_HTML = _css.sub('', g_HTML)19 g_HTML = _other_tag.sub('', g_HTML)20 g_HTML = _special_char.sub('', g_HTML)21 defGetContent(threshold):22 globalg_HTMLBlock23 nMaxSize =len(g_HTMLBlock)24 nBegin =025 nEnd =026 for i inrange(0, nMaxSize):27 if g_HTMLBlock[i]>threshold and i+30 and g_HTMLBlock[i+2]>0 and g_HTMLBlock[i+3]>0:28 nBegin =i29 break

30 else:31 returnNone32 for i in range(nBegin+1, nMaxSize):33 if g_HTMLBlock[i]==0 and i+1

36 else:37 returnNone38 return '\n'.join(g_HTMLLine[nBegin:nEnd+1])39 if __name__ == '__main__' and len(sys.argv) > 1:40 f = file(sys.argv[1], 'r')41 globalg_HTML42 globalg_HTMLLine43 globalg_HTMLBlock44 g_HTML =f.read()45 PreProcess()46 g_HTMLLine = [i.strip() for i in g_HTML.splitlines()] #先分割成行list,再过滤掉每行前后的空字符

47 HTMLLength = [len(i) for i in g_HTMLLine] #计算每行的长度

48 g_HTMLBlock = [HTMLLength[i] + HTMLLength[i+1] + HTMLLength[i+2] for i in range(0, len(g_HTMLLine)-3)] #计算每块的长度

49 print GetContent(200)50

上面是一个demo程序,真正用使用起来需要增加存储功能。

依然是采用redis存储,读出所有的page页(keys 'PAGE:*'),提取正文,判断正文是否已在容器中(排除URL不同的重复页面),如果在容器中则做下一次循环,不在容器中则加入容器并存储到 CONTENT:IDSEQ 中。

代码如下:

ContractedBlock.gif

ExpandedBlockStart.gifView Code

1 #!/usr/bin/python

2 #coding=utf-8

3 #根据 陈鑫《基于行块分布函数的通用网页正文抽取算法》

4 importre5 importsys6 importredis7 importbisect8 defPreProcess():9 globalg_HTML10 _doctype = re.compile(r'*?>', re.I|re.S)11 _comment = re.compile(r'', re.S)12 _javascript = re.compile(r'.*?<\/script>', re.I|re.S)13 _css = re.compile(r'.*?<\/style>', re.I|re.S)14 _other_tag = re.compile(r'<.*?>', re.S)15 _special_char = re.compile(r'&.{1,5};|.{1,5};')16 g_HTML = _doctype.sub('', g_HTML)17 g_HTML = _comment.sub('', g_HTML)18 g_HTML = _javascript.sub('', g_HTML)19 g_HTML = _css.sub('', g_HTML)20 g_HTML = _other_tag.sub('', g_HTML)21 g_HTML = _special_char.sub('', g_HTML)22 defGetContent(threshold):23 globalg_HTMLBlock24 nMaxSize =len(g_HTMLBlock)25 nBegin =026 nEnd =027 for i inrange(0, nMaxSize):28 if g_HTMLBlock[i]>threshold and i+30 and g_HTMLBlock[i+2]>0 and g_HTMLBlock[i+3]>0:29 nBegin =i30 break

31 else:32 returnNone33 for i in range(nBegin+1, nMaxSize):34 if g_HTMLBlock[i]==0 and i+1

37 else:38 returnNone39 return '\n'.join(g_HTMLLine[nBegin:nEnd+1])40 defBinarySearch(UniqueSet, item):41 if len(UniqueSet) ==0:42 return043 left =044 right = len(UniqueSet)-1

45 mid = -1

46 while left <=right:47 mid = (left+right)/2

48 if UniqueSet[mid]

50 elif UniqueSet[mid] >item :51 right = mid -1

52 else:53 break

54 return UniqueSet[mid] == item and 1 or055 if __name__ == '__main__':56 globalg_redisconn57 globalg_HTML58 globalg_HTMLLine59 globalg_HTMLBlock60 g_redisconn =redis.Redis()61 UniqueSet =[]62 keys = g_redisconn.keys('PAGE:*')63 nI =064 for key inkeys:65 g_HTML =g_redisconn.get(key)66 PreProcess()67 g_HTMLLine = [i.strip() for i in g_HTML.splitlines()] #先分割成行list,再过滤掉每行前后的空字符

68 HTMLLength = [len(i) for i in g_HTMLLine] #计算每行的长度

69 g_HTMLBlock = [HTMLLength[i] + HTMLLength[i+1] + HTMLLength[i+2] for i in range(0, len(g_HTMLLine)-3)] #计算每块的长度

70 sContent = GetContent(200)71 if sContent !=None:72 sContentKey = key.replace('PAGE', 'CONTENT')73 if BinarySearch(UniqueSet, sContent) ==0:74 bisect.insort(UniqueSet, sContent)75 g_redisconn.set(sContentKey, sContent)

中文分词

smallseg -- 开源的,基于DFA的轻量级的中文分词工具包

特点:可自定义词典、切割后返回登录词列表和未登录词列表、有一定的新词识别能力。

总结

python简洁易用,类库齐全,但在国内应用还不是十分广泛,各类中文资源不是很多,尤其是深入讲解文档很少。

到目前为止我已经简单实现了搜索引擎的几个部分,可以说是十分粗糙,没有写这个搜索的客户端,因为到目前为止已经发现了足够多的问题待解决和优化。

优化

爬虫部分:采用pycurl替换urllib2,更快速,支持user-agent配置;增加字符集识别,统一转换成一种字符后再存储;多进程优化。

url去重复:取url的fingerprint,用redis存储

page去重:取页面的fingerprint,用redis存储,比较相似性

如果有更大的数据量,就要考虑采用分布式存储;如果有更精准的要求,就要考虑一个新的分词系统。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值