zoukankan      html  css  js  c++  java
  • 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 + "
    ")
     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)
    
    
    
     
  • 相关阅读:
    Java Lambda 表达式 对 Map 对象排序
    比较两个list对象是否相同
    ubuntu redis 自启动配置文件(关机有密码)
    spring中订阅redis键值过期消息通知
    网站架构之性能优化(转)
    Json转Java Bean
    spring mvc 4 校验
    java @ResponseBody返回值中去掉NULL字段
    合并两个java bean对象非空属性(泛型)
    spring mvc 删除返回字符串中值为null的字段
  • 原文地址:https://www.cnblogs.com/Lin-Yi/p/10739327.html
Copyright © 2011-2022 走看看