{ Jieba }

  • 使用python搭建小型搜索引擎

    | /

    项目代码链接

    一、目标

    为准备信息检索课程的期末大作业,因此我使用python搭建了一个小型的搜索引擎,其功能是检索大工新闻网的新闻。

    二、原理和工具简介

    2.1 原理

    该搜索引擎的原理是采用scrapy对大工新闻网进行爬虫,提取出文字新闻,并将新闻内容存入数据库A,再利用Django框架搭建一个搜索服务器,在服务器上部署Haystack+Whoosh搜索引擎,使用jieba分词工具来进行中文分词和停用词过滤。通过搜索引擎工具建立索引文件后,在前端完成用户交互界面,实现一个完整的小型搜索引擎。

    2.2 工具简介

    • Scrapy

      Python开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。

      在本项目中用来爬取网站数据。

    • Django

      Django是一个开放源代码的Web应用框架,由Python写成。采用了MVC的框架模式,即模型M,视图V和控制器C。

      在本项目用来做搜索服务器的框架。

    • Haystack+Whoosh

      Haystack是一个Django上的应用,可以用于整合搜索引擎,它只依赖于它自身的代码,利用它可以切换不同的搜索引擎工具。Whoosh是一个索引文本和搜索文本的类库,它可以提供搜索文本的服务。

      在本项目中使用Haystack将Whoosh部署到Django服务器作为搜索引擎后端。

    • Jieba

      Jieba是一个python实现的分词库,对中文有着很强大的分词能力。

      在本项目用于对新闻进行中文分词和停用词过滤。

    三、开发环境和运行环境

    3.1 开发环境

    3.2 运行环境

    同开发环境

    四、系统模块

    4.1 网络爬虫模块

    定义用于Scrapy爬虫的数据类型,包括链接、标题和文章内容:

    1
    2
    3
    4
    class NewsItem(scrapy.Item):
    url = scrapy.Field()
    title = scrapy.Field()
    content = scrapy.Field()

    编写爬虫策略:

    爬虫策略的制定依据于网页源代码的链接形式,由于要爬取的是文字类新闻,所以要跟进与文字类新闻的链接。对于具体的新闻页利用回调函数爬取其链接、标题和内容。而对于新闻列表页,需要跟进下一页的链接。具体规则代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class NewsSpider(CrawlSpider):
    print("news spider starting")
    name = 'news'
    allowed_domains = ['news.dlut.edu.cn']
    start_urls = ['http://news.dlut.edu.cn/']

    rules = (
    # 对于新闻页链接进行跟进
    Rule(LinkExtractor(allow=("xw/[a-z]+.htm"))),
    # 对于详细新闻页利用parse_item回调函数进行内容爬取
    Rule(LinkExtractor(allow=("info/\d{4,}/\d{3,}\.htm")),callback="parse_item"),
    # 对于新闻列表中的下一页链接进行跟进
    Rule(LinkExtractor(allow=("\d{1,}.htm"),restrict_xpaths="//a[@class='Next']")),
    )

    def parse_item(self, response):
    self.log("Hi, this is a new page! %s"% response.url)
    item = NewsItem()
    item['title'] = response.xpath('/html/head/title/text()').extract()[0]
    item['url'] = response.url
    item['content']=response.xpath("//div[@class='cont-detail fs-small']/p/text()").extract()
    yield item

    使用pipline机制将数据保存至数据库当中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    class SpiderprojectPipeline(object):
    def process_item(self, item, spider):
    if spider.name == 'news':
    conn = sqlite3.connect('db.sqlite3')
    cursor = conn.cursor()
    title = item['title']
    url = item['url']
    content_tmp = item['content']
    content=""
    for p in content_tmp:
    content+=p.strip()
    sql_search = 'select arturl from search_article where arturl=="%s"' % (url)
    sql = 'insert into articles_article(title, content, arturl) values("%s", "%s", "%s")'%(title, content, url)
    try:
    #如果当前数据库中不存在该条新闻,则将新闻保存至数据库当中
    cursor.execute(sql_search)
    result_search = cursor.fetchone()
    if result_search is None or result_search[0].strip()=='':
    cursor.execute(sql)
    result=cursor.fetchone()
    conn.commit()
    cursor.execute(sql)
    result=cursor.fetchone()
    conn.commit()
    except Exception as e:
    print(">>> catch exception !")
    print(e)
    conn.rollback()
    return item

    4.2 搜索模块

    在要进行搜索的应用的models.py文件中建立model类用来表示要进行搜索的新闻文章

    1
    2
    3
    4
    class Article(models.Model):
    title = models.CharField(max_length=50)
    arturl = models.CharField(max_length=200)
    content = models.CharField(max_length=1000)

    同时使用django命令python manage.py makemigrationspython manage.py migrate生成数据库文件,并用爬虫得到的数据库替换生成的数据库。

    在django框架中配置Haystack+Whoosh来引入搜索模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 配置搜索引擎后端
    HAYSTACK_CONNECTIONS={
    'default':{
    'ENGINE': 'articles.whoosh_cn_backend.WhooshEngine',
    # 索引文件路径
    'PATH': os.path.join(BASE_DIR, 'whoosh_index'), # 在项目目录下创建文件夹 whoosh_index
    }
    }
    # 当添加、修改、删除数据时,自动生成索引
    HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
    # 每页显示十条搜索结果
    HAYSTACK_SEARCH_RESULTS_PER_PAGE = 10

    在要进行搜索的应用的目录下建立search_indexes.py文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    from haystack import indexes
    from articles.models import Article

    class ArticleIndex(indexes.SearchIndex, indexes.Indexable): #类名必须为需要检索的Model_name+Index,这里需要检索Article,所以创建ArticleIndex
    text = indexes.CharField(document=True, use_template=True) #创建一个text字段

    def get_model(self): #重载get_model方法,必须要有!
    return Article

    def index_queryset(self, using=None): #重载index_..函数
    """Used when the entire index for model is updated."""
    return self.get_model().objects.all()

    在articles\templates\search\indexes\articles\下建立article_text.txt文件确定搜索内容

    1
    2
    3
    {{ object.title }}
    {{ object.content }}
    {{ object.url }}

    4.2 预处理模块

    使用jieba来进行中文分词,需要在whoosh_cn_backend文件中替换StemmingAnalyzer为ChineseAnalyzer

    同时引入停用词表,配置ChineseAnalyzer使其支持停用词过滤

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    import jieba
    import re
    import os
    # 导入停用词过滤表
    stop_file_dir=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    STOP_WORDS = frozenset([line.strip() for line in open(os.path.join(stop_file_dir, 'stop.txt'),'r',encoding='gbk').readlines()])

    accepted_chars = re.compile(r"[\u4E00-\u9FD5]+")

    class ChineseTokenizer(Tokenizer):

    def __call__(self, text, **kargs):
    words = jieba.tokenize(text, mode="search")
    token = Token()
    for (w, start_pos, stop_pos) in words:
    if not accepted_chars.match(w) and len(w) <= 1:
    continue
    token.original = token.text = w
    token.pos = start_pos
    token.startchar = start_pos
    token.endchar = stop_pos
    yield token
    def ChineseAnalyzer(stoplist=STOP_WORDS, minsize=1, stemfn=stem, cachesize=50000):
    return (ChineseTokenizer() | LowercaseFilter() |
    StopFilter(stoplist=stoplist, minsize=minsize) |
    StemFilter(stemfn=stemfn, ignore=None, cachesize=cachesize))

    配置完成后使用python manage.py rebuild_index建立搜索索引

    4.4 交互模块

    搜索首页html关键代码:

    1
    2
    3
    4
    5
    <form method='get' action="/search/" target="_blank">
    <input type="text" name="q">
    <br>
    <input type="submit" value="查询">
    </form>

    首页如下图所示:

    查询结果页html关键代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {% load highlight %}
    <h3>搜索&nbsp;<b>{{query}}</b>&nbsp;结果如下:</h3>
    <ul>
    {%for item in page%}
    <li>{{item.object.title|safe}}</li>
    <p>{% highlight item.object.content with query %}</p>
    <a href="{{item.object.arturl}}">{{item.object.arturl}}</a>
    {%empty%}
    <li>啥也没找到</li>
    {%endfor%}
    </ul>
    <hr>
    {%for pindex in page.paginator.page_range%}
    {%if pindex == page.number%}
    {{pindex}}&nbsp;&nbsp;
    {%else%}
    <a href="?q={{query}}&amp;page={{pindex}}">{{pindex}}</a>&nbsp;&nbsp;
    {%endif%}
    {%endfor%}

    其中使用

    1
    2
    {% load highlight %}
    <p>{% highlight item.object.content with query %}</p>

    进行搜索结果的高亮显示

    最终搜索页面如下图所示:

    五、项目代码

    代码链接