简单搜索引擎的实现

it2022-05-05  287

代码地址 https://github.com/ckl666/search

文章目录

代码地址 https://github.com/ckl666/search 环境系统设计masterServer模块slaveServer模块爬虫模块webServer模块 数据库设计模块的具体实现masterServerslaveServer根据关键字占的权重进行计算比分根据超链接的说明文字计算比分根据链接之间长度关系计算比分 爬虫模块格式化爬取的网页提取网页中的url获取网页的文本内容并提取出关键字 webServer 运行结果

环境

mysql数据库redis数据库ubuntu 系统python3flask 框架gevent库

系统设计

系统分为四个模块:

masterServer模块

中间的master服务器模块,整个系统的核心部件,主要负责接收webServer的请求,将请求分发给solve服务器,然后接收slave的查询结果,返回给webServer爬虫服务器会将即将要爬取的url发送给master服务器,master负责去除已经爬取过的url和重复的url,然后反馈给爬虫服务器

考虑到master服务器是整个系统的核心部分,与各个组件都有关联,其效率一定要高,所以网络通信的方式采用异步IO+ 协程方式,即能简化编程,又能提高并发效率,master服务器要进行url的比对过程,要存储大量的爬取过的url,如果存放在磁盘上,要牵扯到大量的磁盘IO的操作,所以这里采用了redis数据库,将数据存放在内存上,提高速度。

slaveServer模块

负责存储爬取的数据 根据关键字与url页面建立倒排索引存储在MySQL数据库中 负责处理masterServer发来的查询的请求 对查询的结果按照一定的算法计算出页面对于用户的价值度,按价值度的高低进行排序,返回给masterServer 根据关键字在网页中的词频来计算当前网页的权重提出超链接的说明文字,这部分文字更能代表网页的主要内容,所占的权重较大根据网页间的链接的关系,采用PageRank算法计算网页的权重根据上面的三个网页的影响因素按照一定的比例相加,最终所得到的网页的权重越大,页面链接的排名越靠前

solveServer是分布式存储的子节点,这里单个的solveServer节点之间不存在通信的问题,简化了编程,每个solve节点只需要与masterServer节点进行通信即可。

爬虫模块

1、给定一个初始的url进行爬取 2、提取出爬取的网页中的url,发送给master,接收经过master处理后的url,迭代进行爬取

采用bs4获取网页的文本的内容采用jieba库提取网页的关键字与关键字在网页中的权重 (爬虫模块采用集群式的爬虫,每个单个的节点采用多线程的并发模式)

webServer模块

1、获取用户输入的关键字,将关键字发送给masterServer 2、接收masterServer返回的结果,将结果反馈给用户 这里将webServer于文件存储模块分开,方便webServer的集群的设计,也方便了数据库的分布式部署。这里并没有将webServer设计为集群模式,我只是做了基本的功能实现,webSerber与数据的存储已经分开了,所以webServer的集群设计应该会很容易实现

数据库设计

wordlist 存放网页中提取出来的关键字urllist 存放网页的链接wordlocation 存放关键自在网页中所占的比重(对查询结果排序的时候用)link 存放链接之间的关系(排序)linkword 存放超链接的说明文字与超链接的对应关系(排序)

模块的具体实现

masterServer

master监听两个端口,一个负责接受web端的链接,一个负责接受solve端的链接

def createSockte(): serverSocket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverSocket2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = socket.gethostname() port1 = 6666 serverSocket1.bind((host,port1)) serverSocket1.listen(5) port2 = 6667 serverSocket2.bind((host,port2)) serverSocket2.listen(5) return serverSocket1, serverSocket2

master采用多协程的并发模式,简化编程,提高并发

def main(): while True: g1 = gevent.spawn(acceptConn, serverSocket1) g2 = gevent.spawn(acceptConn, serverSocket2) # gevent.sleep(1) g1.join() g2.join()

在redis数据库中存储已经爬取过的url,

# 处理url是否重复 def dealSolveResoult(urls): # 查询是否重复 non_urls = [] for url in urls: if not Redis.Sismember(r, url, 1): non_urls.append(url) Redis.Add(r, url, 1) count = len(conn_solve) gap = (len(non_urls) // count) + 1 i = 0 for conn in conn_solve: res_pack, res_pro = Pack.packUrl(non_urls[i:i+gap]) i += gap send_str = res_pro + "#" len_str = str(len(send_str) + len(res_pack)) while len(len_str) < 10: len_str += "#" send_str = len_str + send_str conn.send(send_str.encode("gb2312")) conn.send(res_pack)

slaveServer

计算网页的对用户的价值,按照价值的大小排序后返回

根据关键字占的权重进行计算比分

for url_info in urlid_weight: url = getUrl(db, url_info[0]) # 保存urlid 用于后面计算链接之间的分值 if url is None: continue linkid_set.add((url_info[0],url)) setUrlScore(url_score, url, url_info[1])

根据超链接的说明文字计算比分

linkid_res = getLinkId(db, word) if linkid_res is None: continue else: for linkid in linkid_res: url = getUrl(db, linkid[0]) if url is None: continue else: linkid_set.add((linkid[0],url)) setUrlScore(url_score, url, 0.5)

根据链接之间长度关系计算比分

# 根据链接之间的关系计算比分 def countLinkScore(db, url_score, linkid_set): # 存放url 对应的pr page_rank = dict() # 存放当前url链接向哪些url urlfrom_to = dict() # 存放当前url 来自那些url urlto_from = dict() # 初始化pr for linkid in linkid_set: page_rank[linkid[0]] = 1.0 urlfrom_to[linkid[0]] = list() urlto_from[linkid[0]] = list() # 初始其他链接指向的当前链接 for linkid in linkid_set: linkfromid = getFromUrlId(db, linkid[0]) if linkfromid is None: continue else: for tmp_id in linkfromid: urlto_from[linkid[0]].append(tmp_id[0]) iteratortion = 20 for i in range(iteratortion): for linkid in linkid_set: linkid_list = urlto_from[linkid[0]] page_rank[linkid[0]] = len(linkid_list) * 0.85 for linkid in linkid_set: setUrlScore(url_score, linkid[1], page_rank[linkid[0]]) return url_score

爬虫模块

格式化爬取的网页

# 爬取网页 def getSoup(url): try: c = urllib.request.urlopen(url) except: print("Could not open %s" %(url)) return None html = c.read() soup = BeautifulSoup(html, "lxml") return soup

提取网页中的url

将网页中的url提取出来,并且提取出链接的说明文字,建立链接说明文字与链接之间的关系

# 获取网页中的超链接 def getUrl(db, soup, page): links = soup('a') urls = set() # words = [] for link in links: if 'href' in dict(link.attrs): url = urllib.parse.urljoin(page, link['href']) url = url.split('<')[0] url = url.split('#')[0] if url[:4] != 'http': continue urls.add(url) link = str(link) index_i = 0 index_j = 0 for i in range(len(link)): if link[i] == '>': index_i = i+1 if index_i != 0 and link[i] == '<': index_j = i break res = splitWord(link[index_i:index_j]) createLinkWordIndex(db, res, url) return urls

获取网页的文本内容并提取出关键字

# 获取网页的文本 def getText(soup): [script.extract() for script in soup.findAll('script')] [style.extract() for style in soup.findAll('style')] reg = re.compile("<[^>]*>") content = reg.sub('', soup.prettify()).strip() content = " ".join(content.split()) return content # 获取文本的关键字 def splitWord(content): seg_list = jieba.analyse.textrank(content, topK = 20, withWeight = True, allowPOS = ("ns", "n", "vn", "v")) res = [] for seg in seg_list: if seg[0] in ignorewords: continue else: res.append(seg) return res

webServer

负责发送查询的数据到masterServer 接受master的相应数据

def readData(conn): len_pack = conn.recv(10).decode("gb2312") len_pack = int(len_pack.split("#")[0]) li = list() while len_pack != 0: data = conn.recv(len_pack) if len(data) <= len_pack: li.append(data) len_pack -= len(data) resoult = bytes() for l in li: resoult += l resoult = resoult.decode("gb2312").split("#") resoult = Pack.unpackUrl(resoult[0], resoult[1].encode("gb2312")) return resoult def writeData(conn, sentence): len_str = str(len(sentence.encode("gb2312"))) while len(len_str) < 10: len_str += "#" send_str = len_str + str(sentence) print(send_str) conn.send(send_str.encode("gb2312"))

运行结果

主界面 查询结果界面


最新回复(0)