信息檢索 - SDU新聞網站Python全站爬取+索引構建+搜索引擎


信息檢索課程設計sdu視點新聞全站Python爬蟲爬取+索引構建+搜索引擎查詢練習程序(1805)。

以前在gh倉庫總結的內容,沒想到被人轉載不帶出處,不如我自己來發一遍叭。

源代碼:Github

爬蟲功能使用Python的scrapy庫實現,並用MongoDB數據庫進行存儲。

索引構建和搜索功能用Python的Whoosh和jieba庫實現。(由於lucene是java庫,所以pyLucene庫的安裝極其麻煩,因此選用Python原生庫Whoosh實現,並使用jieba進行中文分詞。)

搜索網頁界面用django實現,頁面模板套用BootCDN

1 要求

以下是檢索的基本要求:可以利用lucene、nutch等開源工具,利用Python、Java等編程語言,但需要分別演示並說明原理。

  1. Web網頁信息抽取
    以山東大學新聞網為起點進行網頁的循環爬取,保持爬蟲在view.sdu.edu.cn之內(即只爬取這個站點的網頁),爬取的網頁數量越多越好。

  2. 索引構建
    對上一步爬取到的網頁進行結構化預處理,包括基於模板的信息抽取、分字段解析、分詞、構建索引等。

  3. 檢索排序
    對上一步構建的索引庫進行查詢,對於給定的查詢,給出檢索結果,明白排序的原理及方法。

2 運行方式

  • 運行sduspider/run.py來進行網絡爬蟲,這個過程將持續十多個小時,但可以隨時終止,在下次運行時繼續。

  • 運行indexbuilder/index_builder.py來對數據庫中的72000條數據構建索引,該過程將持續幾個小時,但可以隨時終止。

  • 如果不熟悉Whoosh庫的構建索引和query搜索功能,可以參考運行indexbuilder/sample.py

  • 運行indexbuilder/query.py來測試搜索功能。

  • 運行searchengine/run_server.py打開搜索網頁服務器,在瀏覽器中打開127.0.0.1:8000進入搜索頁面執行搜索。

3 所需python庫

  • scrapy
  • requests
  • pymongo
  • whoosh
  • jieba
  • django

4 所需數據庫

  • MongoDB
  • Mongo Management Studio 可視化工具(可選)

5 爬蟲特性

爬蟲代碼位於sduspider/目錄下。

5.1 爬取內容

爬蟲爬取以 http://www.view.sdu.edu.cn/info/ 打頭的所有新聞頁面的內容,這些內容包括:

Item Item name
標題 newsTitle
鏈接 newsUrl
閱讀量 newsCliek
發布時間 newsPublishTime
文章內容 newsContent
# spider.py
# 爬取當前網頁
        print('start parse : ' + response.url)
        self.destination_list.remove(response.url)
        if response.url.startswith("http://www.view.sdu.edu.cn/info/"):
            item = NewsItem()
            for box in response.xpath('//div[@class="new_show clearfix"]/div[@class="le"]'):
                # article title
                item['newsTitle'] = box.xpath('.//div[@class="news_tit"]/h3/text()').extract()[0].strip()

                # article url
                item['newsUrl'] = response.url
                item['newsUrlMd5'] = self.md5(response.url)

                # article click time
                item['newsClick'] = box.xpath('.//div[@class="news_tit"]/p/span/script/text()').extract()[0].strip()
                pattern = re.compile(r'\(.*?\)')
                parameters = re.search(pattern, item['newsClick']).group(0)
                parameters = parameters[1:-1].split(',')
                parameters[0] = re.search(re.compile(r'\".*?\"'), parameters[0]).group(0)[1:-1]
                parameters[1] = parameters[1].strip()
                parameters[2] = parameters[2].strip()
                request_url = 'http://www.view.sdu.edu.cn/system/resource/code/news/click/dynclicks.jsp'
                request_data = {'clicktype': parameters[0], 'owner': parameters[1], 'clickid': parameters[2]}
                request_get = requests.get(request_url, params=request_data)
                item['newsClick'] = request_get.text

                # article publish time
                item['newsPublishTime'] = box.xpath('.//div[@class="news_tit"]/p[not(@style)]/text()').extract()[0].strip()[5:]

                # article content
                item['newsContent'] = box.xpath('.//div[@class="news_content"]').extract()[0].strip()
                regexp = re.compile(r'<[^>]+>', re.S)
                item['newsContent'] = regexp.sub('',item['newsContent'])    # delete templates <>

                # 索引構建flag
                item['indexed'] = 'False'

                # yield it
                yield item

5.2 寬度優先搜索爬取

爬蟲基於寬度優先搜索,對http://www.view.sdu.edu.cn/區段的網址進行爬取,並將http://www.view.sdu.edu.cn/info/區段的新聞內容提取出來。

# settings.py
# 先進先出,廣度優先
DEPTH_PRIORITY = 1
SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue'

5.3 二分法去重

所有已經爬取過的網址都會以MD5特征的形式順序存儲在list中,當獲取新的url時,通過二分法查找list中是否存在該url的特征值,以達到去重的目的。

Scrapy庫自帶了查重去重的功能,但為了保證效率,自行編寫了二分法去重,但並未關閉scrapy庫自帶的去重功能。

# spider.py
# md5 check
md5_url = self.md5(real_url)
if self.binary_md5_url_search(md5_url) > -1:    # 二分法查找存在當前MD5
    pass
else:
    self.binary_md5_url_insert(md5_url) # 二分法插入當前MD5
    self.destination_list.append(real_url)  # 插入爬蟲等待序列
    yield scrapy.Request(real_url, callback=self.parse, errback=self.errback_httpbin)

5.4 斷點續爬

每爬取一定次數后都會將當前爬蟲狀態存儲在pause文件夾下,重新運行爬蟲時會繼續上一次保存的斷點進行爬取。Scrapy有自帶的斷點續爬功能(在settings.py中設置),但貌似在Pycharm中行不通。

# spider.py
# counter++,並在合適的時候保存斷點
def counter_plus(self):
    print('待爬取網址數:' + (str)(len(self.destination_list)))
    # 斷點續爬功能之保存斷點
    if self.counter % self.save_frequency == 0:  # 爬蟲經過save_frequency次爬取后
        print('Rayiooo:正在保存爬蟲斷點....')

        f = open('./pause/response.seen', 'wb')
        pickle.dump(self.url_md5_seen, f)
        f.close()

        f = open('./pause/response.dest', 'wb')
        pickle.dump(self.destination_list, f)
        f.close()

        self.counter = self.save_frequency

    self.counter += 1  # 計數器+1

5.5 數據存入MongoDB

關系類數據庫不適用於爬蟲數據存儲,因此使用非關系類數據庫MongoDB。數據庫可以用可視化工具方便查看,例如Mongo Management Studio。

# pipelines.py
class MongoDBPipeline(object):
    def __init__(self):
        host = settings["MONGODB_HOST"]
        port = settings["MONGODB_PORT"]
        dbname = settings["MONGODB_DBNAME"]
        sheetname = settings["MONGODB_SHEETNAME"]
        # 創建MONGODB數據庫鏈接
        client = pymongo.MongoClient(host=host, port=port)
        # 指定數據庫
        mydb = client[dbname]
        # 存放數據的數據庫表名
        self.post = mydb[sheetname]

    def process_item(self, item, spider):
        data = dict(item)
        # self.post.insert(data)    # 直接插入的方式有可能導致數據重復
        # 更新數據庫中的數據,如果upsert為Ture,那么當沒有找到指定的數據時就直接插入,反之不執行插入
        self.post.update({'newsUrlMd5': item['newsUrlMd5']}, data, upsert=True)
        return item

6 索引構建特性

索引構建代碼位於indexbuilder/目錄下。

6.1 斷點續構

構建倒排索引的過程比較緩慢,每小時只能構建10000條新聞的索引,因此在索引構建時及時存儲新構建的索引,以保證能夠斷點續構。

6.2 中文分詞

Whoosh自帶的Analyzer分詞僅針對英文文章,而不適用於中文。從jieba庫中引用的ChineseAnalyzer保證了能夠對Documents進行中文分詞。同樣,ChineseAnalyzer在search時也能夠對中文查詢query提取關鍵字並進行搜索。

# index_builder.py
from jieba.analyse import ChineseAnalyzer

analyzer = ChineseAnalyzer()
# 創建索引模板
schema = Schema(
    newsId=ID(stored=True),
    newsTitle=TEXT(stored=True, analyzer=analyzer),
    newsUrl=ID(stored=True),
    newsClick=NUMERIC(stored=True, sortable=True),
    newsPublishTime=TEXT(stored=True),
    newsContent=TEXT(stored=False, analyzer=analyzer),  # 文章內容太長了,不存
)

6.3 Query類提供搜索API

Query類自動執行了從index索引文件夾中取倒排索引來執行搜索,並返回一個結果數組。

# query.py
if __name__ == '__main__':
    q = Query()
    q.standard_search('軟件園校區')

7 搜索引擎特性

搜索引擎代碼位於searchengine/目錄下。

7.1 Django搭建Web界面

Django適合Web快速開發。result頁面繼承了main頁面,搜索結果可以按照result中的指示顯示在頁面中。在django模板繼承下,改變main.html中的頁面布局,result.html的布局也會相應改變而不必使用Ctrl+c、Ctrl+v的方式改變。

# view.py
def search(request):
    res = None
    if 'q' in request.GET and request.GET['q']:
        res = q.standard_search(request.GET['q'])   # 獲取搜索結果
        c = {
            'query': request.GET['q'],
            'resAmount': len(res),
            'results': res,
        }
    else:
        return render_to_response('main.html')

    return render_to_response('result.html', c) # 展示搜索結果

7.2 搜索迅速

第一次搜索時,可能因為倒排索引index的取出時間較長而搜索緩慢,但一旦index取出,對於70000余條新聞的搜索將非常迅速,秒出結果。

參考資料

[1]scrapy爬蟲框架入門實例
[2]筆記:scrapy爬取的數據存入MySQL,MongoDB
[3]搜索那些事 - 用Golang寫一個搜索引擎(0x00) --- 從零開始(分享自知乎網)
[4]Whoosh + jieba 中文檢索
[5]利用whoosh對mongoDB的中文文檔建立全文檢索
[6]Django 創建第一個項目
[7]Django模板系統(非常詳細)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM