搜索引擎系统的工作流程分为四步,从网上抓取网页,建立索引数据库,根据用户的关键词在索引数据库中进行搜索,对搜索结果进行相关度排序处理。它主要由网络爬虫、索引器和检索器三部分组成。网络爬虫,英文名称Web Spider,它的主要功能是从指定的IP出发,便利整个互联网中所存在的网页,它可以借助网页中的URL爬到其他网页中去,不停的对这个过程进行重复,将爬过的网页搜集起来并存储到页面存储库中去。Indexer,索引器,它的主要功能是将Web Spider收集回来的网页进行分析,提取网页中所含的信息,并按照相关度进行计算,将这些相关信息建立完整的网页索引数据库。Searcher,检索器,用户输入关键词进行搜索,它负责对关键词进行分解,根据搜索器从网页索引数据库中查询到相关度较高的网页,对其进行排序处理,然后交给系统,系统会把结果进行组织处理,反馈给用户。搜索引擎对于文件的存储要求较高,操作系统又有自身的缺陷,在大型文件的管理方面存在诸多局限,这就需要搜索引擎自行定义和管理大型文件系统。大型文件是一种跨多文件系统的虚拟文件,支持64位寻址方式,可自动处理多文件系统的分配和寻址。
在进行软件开发前期,我们需要进行Python开发环境的搭建,需要Python程序开发包:Python3.6 ,开发IDE:JetBrains PyCharm2017.1,结巴中文分词包:jieba,网页实现框架:Flask,实现HTTP的第三方库requests。后面第三节会对本搜索引擎所用到的相关技术进行介绍,包括爬虫技术,结巴分词,向量空间模型,BM25算法等
一、系统架构设计
搜索引擎有基本的五大模块,分别是信息采集,信息处理,建立索引,查询和 web 交互。本次系统设计的目的是的是如何在信息处理分析的基础上,建立一个完整的中文搜索引擎。所以该系统主要由以下几个详细部分组成:爬取数据,中文分词,相关度排序,建立索引库,建立查询服务器,建立web交互。
网络爬虫部分前文已经讲过,在此不再赘述,在此直接使用中国地质大学的课程设计文件,即对某文献库的爬取结果做分词排序与查询。
二、结巴分词与建立索引
1、结巴分词简介
jieba 是一个基于Python的中文分词工具对于一长段文字,其分词原理大体可分为三步:
1.首先用正则表达式将中文段落粗略的分成一个个句子。
2.将每个句子构造成有向无环图,之后寻找最佳切分方案。
3.最后对于连续的单字,采用HMM模型将其再次划分。
jieba分词分为“默认模式”(cut_all=False),“全模式”(cut_all=True)以及搜索引擎模式。对于“默认模式”,又可以选择是否使用 HMM 模型(HMM=True,HMM=False)。
2、倒排索引
倒排索引(英语:Inverted index),也常被称为反向索引、置入档案或反向档案,是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。它是文档检索系统中最常用的数据结构。通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。倒排索引主要由两个部分组成:“单词词典”和“倒排文件”。
倒排索引有两种不同的反向索引形式:
一条记录的水平反向索引(或者反向档案索引)包含每个引用单词的文档的列表。
一个单词的水平反向索引(完全反向索引)又包含每个单词在一个文档中的位置。
后者的形式提供了更多的兼容性(比如短语搜索),但是需要更多的时间和空间来创建。
通过上面的定义可以知道,一个倒排索引包含一个单词词典和一个倒排文件。其中单词词典包含了所有粒度的拆分词;倒排文件则保存了该词对应的所有相关信息。
3、代码实现
1)读取爬虫文件夹下所有文件名,并保存为doc_list。
def index_writer(self):
path="answer/C3-Art"
self.doc_list=os.listdir(path)
self.index()
(2)读取文件内容并用jieba分词,分词内容保存为term_list,方便接下来进行建立倒序索引表。其中,inverted为记录词所在文档及词频,idf为词的逆文档频率。
for i in range(len(self.doc_list)):
print(self.doc_list[i])
with open("answer/C3-Art/"+self.doc_list[i],"r",encoding='gb18030', errors='ignore') as f:
data=f.read()
term_list = jieba.lcut_for_search(data)
(3)对分词结果进行倒序索引
for j in term_list:
if j in self.inverted:
if i not in self.inverted[j]:
self.inverted[j][i]=1
else:
self.inverted[j][i]+=1
else:
self.inverted[j]={i:1}
for t in self.inverted:
self.idf[t]=math.log10(doc_num/len(self.inverted[t]))
(4)完整代码呈现:
import jieba
import os
import math
class Indexer:
inverted = {} # 记录词所在文档及词频
idf = {} # 词的逆文档频率
def __init__(self):
self.doc_list=[]
self.index_writer()
def index_writer(self):
path="answer/C3-Art"
self.doc_list=os.listdir(path)
self.index()
def index(self):
doc_num=len(self.doc_list)
for i in range(len(self.doc_list)):
print(self.doc_list[i])
with open("answer/C3-Art/"+self.doc_list[i],"r",encoding='gb18030', errors='ignore') as f:
data=f.read()
term_list = jieba.lcut_for_search(data)
for j in term_list:
if j in self.inverted:
if i not in self.inverted[j]:
self.inverted[j][i]=1
else:
self.inverted[j][i]+=1
else:
self.inverted[j]={i:1}
for t in self.inverted:
self.idf[t]=math.log10(doc_num/len(self.inverted[t]))
print("inverted terms:%d"% len(self.inverted))
print("index done")
index=Indexer()
三、向量空间模型
1、向量空间模型原理
向量空间模型(vector space model)概念简单,把对文本内容的处理简化为向量空间中的向量运算,并且它以空间上的相似度表达语义的相似度,直观易懂。当文档被表示为文档空间的向量,就可以通过计算向量之间的相似性来度量文档间的相似性。文本处理中最常用的相似性度量方式是余弦距离。
搜索引擎需要计算“用户查询”和爬下来的众多”网页“之间的相似度,从而把最相似的排在最前返回给用户。
2、BM25算法原理
BM25 是一种用来评价搜索词和文档之间相关性的算法,它是一种基于概率检索模型提出的算法。BM25属于bag-of-words模型,bag-of-words模型只考虑document中词频,不考虑句子结构或者语法关系之类,把document当做装words的袋子,具体袋子里面可以是杂乱无章的。对每一个搜索查询,我们很容易给每个文档定义一个“相关分数”。当用户进行搜索时,我们可以使用相关分数进行排序而不是使用文档出现时间来进行排序。这样,最相关的文档将排在第一个,无论它是多久之前创建的(当然,有的时候和文档的创建时间也是有关的)。
我们要从最简单的、基于统计的方法说起。这种方法不需要理解语言本身,而是通过统计词语的使用、匹配和基于文档中特有词的普及率的权重等情况来决定“相关分数”。
这个算法不关心词语是名词还是动词,也不关心词语的意义。它唯一关心的是哪些是常用词,那些是稀有词。如果一个搜索语句中包括常用词和稀有词,最好让包含稀有词的文档的评分高一些,同时降低常用词的权重。
3、代码实现
(1)对搜索词进行结巴分词
term_list=[]
query=query.split()
for i in query:
term_list.extend(jieba.cut_for_search(i))
(2)计算tf-idf,找出候选doc
tf_idf={}
for i in term_list:
if i in self.index.inverted:
for doc_id,idf in self.index.inverted[i].items():
if doc_id in tf_idf:
tf_idf[doc_id]+=(1+math.log10(idf))*self.index.idf[i]
else:
tf_idf[doc_id]=(1+math.log10(idf))*self.index.idf[i]
(3)对候选doc进行排序
sorted_doc=sorted(tf_idf.items(),key=operator.itemgetter(1),reverse=True)
res=[self.index.doc_id[id_doc]for id_doc,score in sorted_doc]
(4)完整代码呈现
from index import Indexer
import jieba
import math
import operator
class Searcher:
def __init__(self,index:Indexer):
self.index=index
def search(self,query:str):
term_list=[]
query=query.split()
for i in query:
term_list.extend(jieba.cut_for_search(i))
tf_idf={}
for i in term_list:
if i in self.index.inverted:
for doc_id,idf in self.index.inverted[i].items():
if doc_id in tf_idf:
tf_idf[doc_id]+=(1+math.log10(idf))*self.index.idf[i]
else:
tf_idf[doc_id]=(1+math.log10(idf))*self.index.idf[i]
sorted_doc=sorted(tf_idf.items(),key=operator.itemgetter(1),reverse=True)
res=[self.index.doc_id[id_doc]for id_doc,score in sorted_doc]
return res
index=Indexer()
search=Searcher(index)
print(search.search("中华民族的伟大成就"))
四、网页框架
1、Flask简介
Flask 是一个 Python 实现的 Web 开发微框架。基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
2、代码实现
from flask import Flask, redirect, url_for, request, render_template
from index import Indexer
from BM25 import Searcher
import jieba
app = Flask(__name__)
#@app.route('/success/<name>')
@app.route('/<name>')
def success(name):
docs = searcher.search(name)
terms = list(jieba.cut_for_search(name))
reslut=highlight(docs,terms)
#return render_template('test.html')
print(reslut)
return render_template('search.html', docs=reslut, value=name, length=len(docs))
#return data
@app.route('/login',methods = ['POST', 'GET'])
def login():
if request.method == 'POST':
user = request.form['nm']
return redirect(url_for('success',name = user))
else:
user = request.args.get('nm')
return redirect(url_for('success',name = user))
def highlight(docs, terms):
result = []
print()
content=""
title=''
len_1=0
for doc in docs:
result.append([])
with open("answer/C4-Literature/" + doc, "r", encoding='gb18030', errors='ignore') as f:
data = f.read()
#print(lines)
for term in terms:
#print(term)
num1=data.find(term)
content=data[num1-50:num1+50]
#content = content.replace(term, '<em><font color="red">{}</font></em>'.format(term))
for i in range(3):
if i==0:
result[len_1].append(doc)
elif i==1:
result[len_1].append(content)
elif i==2:
with open("answer/C4-Literature/" + doc, "r", encoding='gb18030', errors='ignore') as f:
#lines=f.readline()
#print(lines)
for i in f:
print("!!!!!!!!!!!!")
print(i)
if "【 标 题 】" in i:
print(i)
title=i
result[len_1].append(title)
len_1=len_1+1
return result
index = Indexer()
searcher = Searcher(index)
if __name__ == '__main__':
app.run(debug = True)
3、Web设计
超文本标记语言(标准通用标记语言下的一个应用,外语缩写HTML),是迄今为止网络上应用最为广泛的语言,也是构成网页文档的主要语言。HTML文本是由HTML命令组成的描述性文本,HTML命令可以说明文字、图形、动画、声音、表格、链接等。HTML的结构包括头部(Head)、主体(Body)两大部分,其中头部描述浏览器所需的信息,而主体则包含所要说明的具体内容。
4、代码实现
1、login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body background="image.jpeg">
<form action = "http://localhost:5000/login" method = "get">
<p align="center" size="3" color="red"> 搜索引擎设计:</p>
<p align="center">
<input type = "text" name = "nm" />
</p>
<p align="center"><input type = "submit" value = "搜索" /></p>
</form>
</body>
</html>
2、Search.html
<!doctype html>
<html ng-app="app">
<head>
<meta charset="UTF-8">
<title>搜索</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
</head>
<body ng-controller="ctrl">
<div class="container">
<br>
<br>
<form action = "http://localhost:5000/login" method = "post">
<div class="form-group">
<input type="text" style="height: 40px; width: 360px;" class="form-control" ng-model="query"
value="{{ value }}" name="query" id="query"/>
</div>
<input class="btn btn-small btn-success" role="button" type="submit" value="搜一下" id="btn">
</form>
<br>
<br>共有{{ length }}条结果
<br>
<div id="box">
{% for doc in docs %}
<p>{{ doc[2]|safe }}</p>
<p>摘要: {{ doc[1]|safe }}</p>
<p>来源:{{ doc[0] }}</p>
<br>
{% endfor %}
</div>
</div>
</body>
</html>
五、成果展示