项目作者: mbinary

项目描述 :
:mag: 用 python, django 实现的一个很简单的搜索引擎
高级语言: HTML
项目地址: git://github.com/mbinary/dbworld-search.git
创建时间: 2019-01-09T04:40:03Z
项目社区:https://github.com/mbinary/dbworld-search

开源协议:MIT License

关键词:
crawler django search-engine

下载


搜索引擎实现

使用 Django-2.1.3, python3.6 实现的一个非常非常 naive 的搜索引擎.

我初学 django, 写得并不熟练, 所以此代码仅供参考.

需要

  • 编程语言: python3
  • 运行环境: linux, shell
  • 使用工具:
    • Django-2.1.3
    • python3.6
      • summa (text-rank)
      • dj-pagination
      • BeautifulSoup

结果展示

  • 首页

  • 分页

设计

设计数据结构

我们要保存一个倒排索引, 以及一个 主题对应的发送时间, 发送者, 主题, 主题链接等内容. 所以我设计了下面的数据库结构.

  • Doc: 一个文件, 也就是一个网页, 包含一些主要信息.
  • File: 外键是Doc, 包含了 网页文件的文本内容, 以及标记是否已经被索引(isIndexed)
  • Wordindex: 这就是倒排索引中的一个项, 包含一个 term, 和倒排索引表, 倒排索引表设计成 hashtable 形式, 键为 Doc. id, 值为 在 Doc 中出现的次数. 为了简便,在数据库库中的存储形式是将上面的 hashtable (在 python 中 为 dict 类型) 用 json 格式保存为文本字符串形式.

需要注意的是增加一个键值对 不能 使用下面代码

  1. word.index [ doc.id] = num
  2. word.save()

应该

  1. dic = word.index
  2. dic[doc.id] = num
  3. word.index = dic
  4. word.save()

下面给出的是 django 中 model 的代码

  1. from django.db import models
  2. class Doc(models.Model):
  3. sendTime= models.DateField() # 2018-12-12 , differ from DateTimeField which can be datetime or date
  4. sender = models.CharField(max_length=20)
  5. messageType = models.CharField(max_length = 20) # Journal, conf, et al
  6. subject = models.CharField(max_length=100)
  7. begin= models.DateField()
  8. deadline= models.DateField()
  9. subjectUrl= models.CharField(max_length=100)
  10. webpageUrl= models.CharField(max_length=100)
  11. desc = models.CharField(max_length= 250,default='')
  12. loc = models.CharField(max_length=40,default='')
  13. keywords = models.CharField(max_length=200,default='')
  14. def __str__(self):
  15. return self.subjectUrl
  16. import json
  17. class Wordindex(models.Model):
  18. word= models.CharField(max_length=45)
  19. # model to store a list, another way is to create a custom field
  20. _index = models.TextField(null=True)
  21. @property
  22. def index(self):
  23. return json.loads(self._index)
  24. @index.setter
  25. def index(self,li):
  26. self._index = json.dumps(li)
  27. def __str__(self):
  28. return self.word
  29. class File(models.Model):
  30. doc = models.OneToOneField(Doc,on_delete=models.CASCADE)
  31. content = models.TextField(null=True)
  32. isIndexed = models.BooleanField(default=False)
  33. def __str__(self):
  34. return 'file: {} -> doc: {}'.format(self.id,self.doc.id)

网页提取

首先是 主页
其结构是这样

  1. <TBODY>
  2. <TR VALIGN=TOP>
  3. <TD>03-Jan-2019 </TD>
  4. <TD>conf. ann. </TD>
  5. <TD>marta cimitile </TD>
  6. <TD><A HREF="http://www.cs.wisc.edu/dbworld/messages/2019-01/1546520301.html" rel="nofollow">Call forFUZZ IEEE Special Session</A> </TD>
  7. <TD>13-Jan-2019</TD>
  8. <TD><A rel="nofollow" HREF="http://sites.ieee.org/fuzzieee-2019/special-sessions/">web page</A></TD>
  9. </TR></TBODY>

有规律性, 可以直接提取. 在实现时, 我用的 python 的 BeautifulSoup 包来提取.

使用过程中, 关键是传递 解析器, 试过了 html, lxml 有问题, 最后用的 html5lib

然后是上面一行表格中的第四列(即第四个 td 标签), 其中的 <a>标签是 主题所在的网页链接. 也要进行提取

提取时间, 地点

由于时间, 地点具有一般的模式, 可以列举出常见的模式, 使用正则表达式匹配

提取摘要, 关键字

使用了 textrank 算法
最开始我自己实现了一个很基础的 textrank 算法, 效果很差, 后来就使用了 text-rank 的官方版本.

建立索引

这部分就是按照 倒排索引的原理,
将网页文本分词, 去除标点符号等,
然后使用上面介绍的数据库模型存储倒排索引.

设计网页

首先是标题
下面是一行是一排选项, 可以根据这些字段排序.
接着一行有一个 update 按钮, 一个搜索提交表格,

下面的内容就是用 div 排列起来的搜索结果.

每个结果包含一个标题, 关键字, 时间,地点, 还有摘要.

查找排序

这里我自己实现了 tf-idf算法 来排序结果.
代码如下

  1. def tfidf(words):
  2. if not words:return docs
  3. ct = process(words)
  4. weight = {}
  5. tf = {}
  6. for term in ct:
  7. try:
  8. tf[term] = Wordindex.objects.get(word=term).index
  9. except Exception as e:
  10. print(e)
  11. tf[term]={}
  12. continue
  13. for docid in tf[term]:
  14. if docid not in weight:
  15. weight[docid]=0
  16. N = len(weight)
  17. for term in ct:
  18. dic = tf[term]
  19. for docid, freq in dic.items():
  20. w = (1+log10(freq))*(log10(N/len(dic)))*ct[term]
  21. if term in stopWords:
  22. w*=0.3
  23. weight[docid]+=w
  24. ids = sorted(weight,key = lambda k:weight[k],reverse=True)
  25. if len(ids)<8: pass #???
  26. return [Doc.objects.get(id=int(i)).__dict__ for i in ids]

不足

  1. 提取网页主题还需要改进, 提取地点方面, 有时可能提取不到.
  2. 网页设计还可以更美观一点.
  3. 还未对搜索引擎进行性能评估