python 搭建一个简单的 搜索引擎

 

我把代码和爬好的数据放在了git上,欢迎大家来参考

https://github.com/linyi0604/linyiSearcher

 

我是在 manjaro linux下做的, 使用python3 语言, 爬虫部分涉及到 安装ChromeDriver 可以参考我之前写的博文。

建立索引部分参考: https://baijiahao.baidu.com/s?id=1597426056496128414&wfr=spider&for=pc

检索过程,衡量文档相似度使用了余弦相似度,参考:https://www.cnblogs.com/liangjf/p/8283519.html

 

为了完成我的信息检索选修课大作业,写下了这个简单的小项目。

这里是一个python3 实现的简易的搜索引擎

我把它取名叫linyiSearcher

--------

所需要的python依赖包在requirements.txt中
可以使用 pip install -r requirements.txt 一次性安装全部

--------

一共分成3部分完成(后面有稍微详细点的解读)

1_spider.py 是一个爬虫, 爬取搜索引擎的语料库

2_clean_data_and_make_index 是对爬下来的数据 进行一些清晰工作,并且将数据存入数据库,建立索引

这里使用了 sqlite数据库,为了方便数据和项目一同携带

3_searcher.py 简易的web后端, 实现了

1 在网页输入搜索关键字, 在后端接收到关键字

2 对关键字进行分词

3 在索引中查找和关键字有关的文档

4 按照余弦相似度 对文档进行排序

5 把相近的文档展示出来

--------

自己的知识储备和代码能力都捉襟见肘。

大神来看,还望海涵~欢迎大家批评指正共同学习

--------


1 爬虫:

因为没有数据,只能写爬虫来做, 又只有自己的笔记本来跑,所以数据量也做不到非常大

在这里 写了1程序 爬了百度贴吧 娱乐明星分类下面的所有1级页面帖子的标题 当做语料库

爬取下来的数据存在了 ./data/database.csv 下

数据有2列 分别是 title 和url


2 数据清洗 并 建立索引:

database.db 是一个sqlite数据库文件

首先将每个文档存到了数据库当中

数据库表为 page_info(id,keyword, title, url)

id 自增主键

keyword: 存了该文档文字用jieba分词打散后的词汇列表(用空格隔开所有词语的字符串)

title: 文档的文字内容

url: 该文档的网页链接

然后 把每个文档 使用jieba分词工具, 打散成词语,把所有词语放到一个集合中(集合能去重)

把所有词 存入数据库 建立索引

索引这样理解:

关键词: 你好 包含关键词的文档: <1,2,6,8,9>

表为 page_index(id, word, page_id)

id: 自增 主键

word: 当前关键词

page_id: 包含该关键词的文档id 也就是page_info.id



3 实现检索:

首先 使用了bottle框架,是一个非常轻巧的web后端框架,实现了一个简单的web后端

前端页面使用了bootstrap 的css样式,,毕竟自己什么垃圾的一p

检索的实现过程:

1 后端拿到检索的关键词,用jieba分词 把拿到的语句打散成词汇 形成关键词keyword_list

2 在建立的索引表page_index中,搜关keyword_list中出现的词汇的page_id

3 在包含所有keyword的文档上 计算和keyword的余弦相似度,然后降序排列

4 返回给前端显示搜索结果

看看检索结果:

 

 

 

 


 

 



 



1_spider.py 爬虫的代码
  1 import requests
  2 from lxml import etree
  3 import random
  4 import COMMON
  5 import os
  6 from selenium import webdriver
  7 import pandas as pd
  8 """
  9 这里是建立搜索引擎的第一步
 10 """
 11 
 12 
 13 class Spider_BaiduTieba(object):
 14 
 15     def __init__(self):
 16         self.start_url = "/f/index/forumpark?pcn=娱乐明星&pci=0&ct=1&rn=20&pn=1"
 17         self.base_url = "http://tieba.baidu.com"
 18         self.headers = COMMON.HEADERS
 19         self.driver = webdriver.Chrome()
 20         self.urlset = set()
 21         self.titleset = set()
 22 
 23     def get(self, url):
 24         header = random.choice(self.headers)
 25         response = requests.get(url=url, headers=header, timeout=10)
 26         return response.content
 27 
 28     def parse_url(self, url):
 29         """通过url 拿到xpath对象"""
 30         print(url)
 31         header = random.choice(self.headers)
 32         response = requests.get(url=url, headers=header, timeout=10)
 33         # 如果获取的状态码不是200 则抛出异常
 34         assert response.status_code == 200
 35         xhtml = etree.HTML(response.content)
 36         return xhtml
 37 
 38     def get_base_url_list(self):
 39         """获得第一层url列表"""
 40         if os.path.exists(COMMON.BASE_URL_LIST_FILE):
 41             li = self.read_base_url_list()
 42             return li
 43         next_page = [self.start_url]
 44         url_list = []
 45         while next_page:
 46             next_page = next_page[0]
 47             xhtml = self.parse_url(self.base_url + next_page)
 48             tmp_list = xhtml.xpath('//div[@id="ba_list"]/div/a/@href')
 49             url_list += tmp_list
 50             next_page = xhtml.xpath('//div[@class="pagination"]/a[@class="next"]/@href')
 51             print(next_page)
 52         self.save_base_url_list(url_list)
 53         return url_list
 54 
 55     def save_base_url_list(self, base_url_list):
 56         with open(COMMON.BASE_URL_LIST_FILE, "w") as f:
 57             for u in base_url_list:
 58                 f.write(self.base_url + u + "\n")
 59 
 60     def read_base_url_list(self):
 61         with open(COMMON.BASE_URL_LIST_FILE, "r") as f:
 62             line = f.readlines()
 63         li = [s.strip() for s in line]
 64         return li
 65 
 66     def driver_get(self, url):
 67         try:
 68             self.driver.set_script_timeout(5)
 69             self.driver.get(url)
 70         except:
 71             self.driver_get(url)
 72     def run(self):
 73         """爬虫程序入口"""
 74         # 爬取根网页地址
 75         base_url_list = self.get_base_url_list()
 76         data_list = []
 77         for url in base_url_list:
 78             self.driver_get(url)
 79             html = self.driver.page_source
 80             xhtml = etree.HTML(html)
 81             a_list = xhtml.xpath('//ul[@id="thread_list"]//a[@rel="noreferrer"]')
 82             for a in a_list:
 83                 title = a.xpath(".//@title")
 84                 url = a.xpath(".//@href")
 85                 if not url or not title or title[0]=="点击隐藏本贴":
 86                     continue
 87                 url = self.base_url + url[0]
 88                 title = title[0]
 89 
 90                 if url in self.urlset:
 91                     continue
 92 
 93                 data_list.append([title, url])
 94                 self.urlset.add(url)
 95                 data = pd.DataFrame(data_list, columns=["title,", "url"])
 96                 data.to_csv("./data/database.csv")
 97 
 98 
 99 
100 
101 if __name__ == '__main__':
102     s = Spider_BaiduTieba()
103     s.run()

 

 

 

2 清晰数据 和 建立索引部分代码  这里是notebook 完成的, 所以看起来有点奇怪

  1 #%%
  2 import pandas as pd
  3 import sqlite3
  4 import jieba
  5 #%%
  6 data = pd.read_csv("./data/database.csv")
  7 #%%
  8 def check_contain_chinese(check_str):
  9     for ch in check_str:
 10         if u'\u4e00' <= ch <= u'\u9fff':
 11             return True
 12         if "a" <= ch <= "z" or "A" <= ch <= "X":
 13             return True
 14         if "0" <= ch <= "9":
 15             return True
 16     return False
 17 #%%
 18 data2 = []
 19 for d in data.itertuples():
 20     title = d[1]
 21     url = d[2]
 22     cut = jieba.cut(title)
 23     keyword = ""
 24     for c in cut:
 25         if check_contain_chinese(c):
 26             keyword += " " + c
 27     keyword = keyword.strip()  
 28     data2.append([title, keyword, url])
 29 #%%
 30 data3 = pd.DataFrame(data2, columns=["title", "keyword", "url"])
 31 data3
 32 #%%
 33 data3.to_csv("./data/cleaned_database.csv", index=False)
 34 #%%
 35 for line in data3.itertuples():
 36     title, keyword, url = line[1],line[2],line[3]
 37     print(title)
 38     print(keyword)
 39     print(url)
 40     break
 41     
 42 #%%
 43 conn = sqlite3.connect("./data/database.db")
 44 c = conn.cursor()
 45 
 46 # 创建数据库
 47 sql = "drop table page_info;"
 48 c.execute(sql)
 49 conn.commit()
 50 
 51 sql = """
 52     create table page_info(
 53         id INTEGER PRIMARY KEY,
 54         keyword text not null,
 55         url text not null
 56     );
 57 """
 58 c.execute(sql)
 59 conn.commit()
 60 
 61 
 62 # 创建索引表
 63 sql = """
 64     create table page_index(
 65         id INTEGER PRIMARY KEY,
 66         keyword text not null,
 67         page_id INTEGER not null
 68     );
 69 """
 70 c.execute(sql)
 71 conn.commit()
 72 #%%
 73 sql = "delete from page_info;"
 74 c.execute(sql)
 75 conn.commit()
 76 
 77 
 78 # 插入到数据库
 79 i = 0
 80 for line in data3.itertuples():
 81     title, keyword, url = line[1],line[2],line[3]
 82     sql = """
 83         insert into page_info (url, keyword) 
 84         values('%s', '%s')
 85     """ % (url, keyword)
 86     c.execute(sql)
 87     conn.commit()
 88     i += 1
 89     if i % 50 == 0:
 90         print(i, len(data3))
 91         
 92         
 93 
 94 sql = "delete from page_index;"
 95 c.execute(sql)
 96 conn.commit()
 97 
 98 sql = "select * from page_info;"
 99 res = c.execute(sql)
100 res = list(res)
101 length = len(res)
102 
103 i = 0
104 for line in res:
105     pid, words, url = line[0], line[1], line[2]
106     words = words.split(" ")
107     for w in words:
108         sql = """
109         insert into page_index (keyword, page_id) 
110         values('%s', '%s')
111         """ % (w, pid)
112         c.execute(sql)
113         conn.commit()
114     i += 1
115     if i % 100 == 0:
116         print(i, length)
117 #%%
118 
119 #%%
120 
121 
122 #%%
123 titles = list(words)
124 colums = ["title", "url"] + titles
125 word_vector = pd.DataFrame(columns=colums)
126 word_vector
127 #%%
128 
129 #%%
130 data = pd.read_csv("./data/database.csv")
131 #%%
132 data
133 #%%
134 sql = "alter table page_info add title text;"
135 conn = sqlite3.connect("./data/database.db")
136 c = conn.cursor()
137 c.execute(sql)
138 conn.commit()
139 #%%
140 conn = sqlite3.connect("./data/database.db")
141 c = conn.cursor()
142 length = len(data)
143 i = 0
144 for line in data.itertuples():
145     pid = line[0]+1
146     title = line[1]
147     sql = "UPDATE page_info SET title = '%s' WHERE id = %s "%(title,pid)
148     try:
149         c.execute(sql)
150         conn.commit()
151     except:
152         continue
153     i += 1
154     if i % 50 == 0:
155         print(i, length)
156 
157 
158 #%%
159 
160 #%%

 

3 web后端 完成检索功能代码
 1 # coding=utf-8
 2 import jieba
 3 import sqlite3
 4 from bottle import route, run, template, request, static_file, redirect
 5 
 6 
 7 @route('/static/<filename>')
 8 def server_static(filename):
 9     if filename == "jquery.min.js":
10         return static_file("jquery.min.js", root='./data/front/js/')
11     elif filename == "bootstrap.min.js":
12         return static_file("bootstrap.js", root='./data/front/js/')
13     elif filename == "bootstrap.min.css":
14         return static_file("bootstrap.css", root='./data/front/css/')
15 
16 
17 @route('/')
18 def index():
19     return redirect("/hello/")
20 
21 
22 @route('/hello/')
23 def index():
24     form = request.GET.decode("utf-8")
25     keyword = form.get("keyword", "")
26     cut = list(jieba.cut(keyword))
27     # 根据索引查询包含关键词的网页编号
28     page_id_list = get_page_id_list_from_key_word_cut(cut)
29     # 根据网页编号 查询网页具体内容
30     page_list = get_page_list_from_page_id_list(page_id_list)
31     # 根据查询关键字和网页包含的关键字,进行相关度排序 余弦相似度
32     page_list = sort_page_list(page_list, cut)
33     context = {
34         "page_list": page_list[:20],
35         "keyword": keyword
36     }
37     return template("./data/front/searcher.html", context)
38 
39 
40 # 计算page_list中每个page 和 cut的余弦相似度
41 def sort_page_list(page_list, cut):
42     con_list = []
43     for page in page_list:
44         url = page[2]
45         words = page[1]
46         title = page[3]
47         vector = words.split(" ")
48         same = 0
49         for i in vector:
50             if i in cut:
51                 same += 1
52         cos = same / (len(vector)*len(cut))
53         con_list.append([cos, url, words, title])
54     con_list = sorted(con_list, key=lambda i: i[0], reverse=True)
55     return con_list
56 
57 
58 
59 # 根据网页id列表获取网页详细内容列表
60 def get_page_list_from_page_id_list(page_id_list):
61     id_list = "("
62     for k in page_id_list:
63         id_list += "%s,"%k
64     id_list = id_list.strip(",") + ")"
65     conn = sqlite3.connect("./data/database.db")
66     c = conn.cursor()
67     sql = "select * " \
68           + "from page_info  " \
69           + "where id in " + id_list + ";"
70     res = c.execute(sql)
71     res = [r for r in res]
72     return res
73 
74 
75 # 根据关键词在索引中获取网页编号
76 def get_page_id_list_from_key_word_cut(cut):
77     keyword = "("
78     for k in cut:
79         if k == " ":
80             continue
81         keyword += "'%s',"%k
82     keyword = keyword.strip(",") + ")"
83     conn = sqlite3.connect("./data/database.db")
84     c = conn.cursor()
85     sql = "select page_id " \
86             + "from page_index  " \
87             + "where keyword in " + keyword + ";"
88     res = c.execute(sql)
89     res = [r[0] for r in res]
90     return res
91 
92 
93 
94 if __name__ == '__main__':
95     run(host='localhost', port=8080)

 

 
posted @ 2019-04-19 22:50  稀里糊涂林老冷  阅读(9292)  评论(1编辑  收藏  举报