天天看點

如何一天做出新聞搜尋引擎(1)——新聞的搜集與資料庫的建立寫在前面寫在後面

新聞的搜集與資料庫的建立

  • 寫在前面
    • 1. 爬取什麼
    • 2. 怎麼爬
      • 2.1 分析網頁的HTML源碼,找到規律
        • 2.1.1 分析滾動頁面
        • 2.1.2 分析新聞頁面
      • 2.2 分析完畢,開始動手寫代碼爬網頁
        • 2.2.1 爬滾動頁面
        • 2.2.2 爬新聞頁面
    • 3. 爬完幹什麼
        • 3.1 分析新聞内容,提取出有關搜尋的關鍵資訊
        • 3.2 儲存入資料庫
    • 4. 重新整理一下,馬上回來
  • 寫在後面

寫在前面

大家好,這一章主要來介紹如何選取爬取的頁面,如何确定爬取内容的方法和怎樣建立資料庫。

如需檢視完整源代碼,請移步我的github頁面,本文所講内容在

Refresh.py

檔案裡。

其他章節請通路我的這篇部落格。

1. 爬取什麼

我們選取頁面的原則是頁面要盡可能地“整齊”。什麼是“整齊”呢,請看下面兩圖:

如何一天做出新聞搜尋引擎(1)——新聞的搜集與資料庫的建立寫在前面寫在後面
如何一天做出新聞搜尋引擎(1)——新聞的搜集與資料庫的建立寫在前面寫在後面

其中圖一是新浪新聞的首頁,圖二是新浪新聞的滾動頁面。顯然從視覺直覺來說,圖二比圖一更為地整齊。

為什麼我們要選擇比較整齊地頁面進行爬取呢?原因在于這種頁面的HTML代碼也比較地有規律,爬網頁的時候也更為地省心。比如圖二的每一則新聞的代碼都是這樣的:

如何一天做出新聞搜尋引擎(1)——新聞的搜集與資料庫的建立寫在前面寫在後面

是以我們隻要在爬取的過程中,找到每個c_tit類下的href就可以找到每條新聞對應的網頁位址了。

2. 怎麼爬

(大家可以參考網易雲上的免費視訊課程,當時我就是就着這個視訊學的,雖然視訊中的爬取方法因為新浪新聞網頁架構的更改失效了,但可以從中學到網絡爬蟲的基本方法)

2.1 分析網頁的HTML源碼,找到規律

2.1.1 分析滾動頁面

我們使用google的chrome浏覽器進行分析。在chrome浏覽器中搜尋“新浪新聞”,點選進入新浪新聞首頁。也就是這樣:

如何一天做出新聞搜尋引擎(1)——新聞的搜集與資料庫的建立寫在前面寫在後面

然後點選左上角的“滾動”(圖中用紅圈标出的地方),進入滾動頁面。

之後我們進入開發者模式。如下圖所示,點選“開發者工具”:

如何一天做出新聞搜尋引擎(1)——新聞的搜集與資料庫的建立寫在前面寫在後面

然後按下圖的步驟點選:

(由于上一條新聞《27歲未婚女孩…》太過驚悚,我們選一個溫和點的)

如何一天做出新聞搜尋引擎(1)——新聞的搜集與資料庫的建立寫在前面寫在後面

然後就出現了下圖:

如何一天做出新聞搜尋引擎(1)——新聞的搜集與資料庫的建立寫在前面寫在後面

圖中被選中的代碼就是我們想從這個頁面上提取的資訊(即每條新聞的網址)。

隻要知道每條新聞的網址,然後我們再通路每條新聞的頁面,提取出關于該新聞的資訊即可。

是以接下來我們進入這條新聞的頁面,找出新聞頁面的HTML代碼規律。

2.1.2 分析新聞頁面

我們要找的資訊:新聞的标題、日期、正文和關鍵字。

按照之前的套路,我們打開開發者工具,并找到正文的位置:

如何一天做出新聞搜尋引擎(1)——新聞的搜集與資料庫的建立寫在前面寫在後面

标題、日期以及關鍵字的尋找方法請看下文。

明确了這些,接下來我們就可以開始動手爬新聞了。?

2.2 分析完畢,開始動手寫代碼爬網頁

2.2.1 爬滾動頁面

經過一番尋找與嘗試(具體嘗試的步驟請在上文所列出的視訊教程中檢視,說實話,當初嘗試了挺久才碰巧找到的),我發現在Source中有一個feed啥的比較關鍵:

如何一天做出新聞搜尋引擎(1)——新聞的搜集與資料庫的建立寫在前面寫在後面

請注意紅下劃線的地方。我們發現位址可以拼接成:

https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid=2509&k=&num=50&page=%d

的形式。其中%d可以代入數字,表示第幾頁的滾動頁面。

後面的

r=0.02768994656458923&callback=jQuery1112035449799366314805_1550924012864&_=1550924012872

與頁面的實時重新整理有關,我們不去管他。

然後我們編寫代碼去通路這個位址:(其中maxPage是我設定的滾動頁面的最大頁碼)

for page in range(1,maxPage+1):
        url = 'https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid=2509&k=&num=50&page=%d'%(page)
        try:
            res = requests.get(url)
        except:
            continue
           

注意try – except,因為可能有些網頁是無法通路的,為了提高容錯性,我們加一個try–except,如果通路錯誤則忽略,直接通路下一頁滾動頁面。

如果成功通路,我們得到的res就是一個response對象,然後對這個response對象進行處理。

把它丢進BeautifulSoup中進行解析:(BeautifulSoup是一個從HTML或XML檔案中提取出資料的Python庫,簡單來說,他可以将檔案中的标簽整理成樹狀結構,友善查閱和應用。不了解的讀者請自行百度或google)

for page in range(1,maxPage+1):
        url = 'https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid=2509&k=&num=50&page=%d'%(page)

        #url = 'https://news.sina.com.cn/roll/#pageid=153&lid=2509&k=&num=50&page=%d'%(page)
        try:
            res = requests.get(url)
        except:
            continue

        pageInfo = []

        res.encoding = 'utf-8'
        soup = BeautifulSoup(res.text, 'html.parser')
        jd = json.loads(soup.text)
           

我們看jd是一個什麼東東:

如何一天做出新聞搜尋引擎(1)——新聞的搜集與資料庫的建立寫在前面寫在後面

原來jd是一個字典,

jd['result']['data']

中的每個元素都是滾動頁面中的一則新聞,裡面中有很多我們要找的資訊:url, title, keywords等。

for page in range(1,maxPage+1):
        url = 'https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid=2509&k=&num=50&page=%d'%(page)

        try:
            res = requests.get(url)
        except:
            continue

        pageInfo = []

        res.encoding = 'utf-8'
        soup = BeautifulSoup(res.text, 'html.parser')
        jd = json.loads(soup.text)
        for info in jd['result']['data']:
            url = info['url']
            title = info['title']
            keywords = info['keywords']

           

2.2.2 爬新聞頁面

結束了嗎?好像還少了點啥…對,少了時間和最重要的正文。這就要我們從每則新聞的新聞頁面中爬取。

在2.2.1中我們已經得到了所有新聞的網頁位址(url),我們進入這個編寫一個名為

getUrlInfo

的函數,提取網頁中的各類資訊:

def getUrlInfo(url):
    try:
        res = requests.get(url)
        res.encoding = 'utf-8'
        #print(res.text)
        soup = BeautifulSoup(res.text, 'html.parser')
        article = soup.select('.article p')[:-1]
        article = '\n\t'.join([p.text.strip() for p in article])  #将文章中的每一段用\n\t隔開

        date = soup.select('.date')[0].text
        date = datetime.strptime(date, '%Y年%m月%d日 %H:%M')

        result = {}
        result['date'] = date
        result['article'] = article
    
    except:
        return  #如果出錯,則傳回None
    
    return result

           

然後将date, article 等資訊與上文的url, title, keywords合并,并将每則新聞的這些資訊提取出來儲存在

news

字典中,再将每頁滾動頁面中所有新聞對應的字典插入到

pageInfo

的數組中,最後将pageInfo中的資訊插入到

allPages

數組中:

def dealPages(maxPage):
    global allPages
    allPages = []
    for page in range(1,maxPage+1):
        url = 'https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid=2509&k=&num=50&page=%d'%(page)

        try:
            res = requests.get(url)
        except:
            continue

        pageInfo = []

        res.encoding = 'utf-8'
        soup = BeautifulSoup(res.text, 'html.parser')
        jd = json.loads(soup.text)
        for info in jd['result']['data']:
            url = info['url']
            title = info['title']
            keywords = info['keywords']

            date_article = getUrlInfo(url)

            if date_article == None:
                continue

            date = date_article['date']
            date = date.strftime('%Y-%m-%d %H:%M')
            article = date_article['article']

            news = {}
            news['url'] = url
            news['title'] = title
            news['keywords'] = keywords
            news['date'] = date
            news['article'] = article

            pageInfo.append(news)

        allPages.extend(pageInfo)

           

news --- 每條新聞的資訊
pageInfo --- 每頁滾動頁面的資訊
allPages --- 所有滾動頁面的資訊
           

這樣,dealPages和getUrlInfo兩個函數雙劍合璧,頁碼為1-maxPage的滾動頁面的資訊就被我提取出來了。

3. 爬完幹什麼

3.1 分析新聞内容,提取出有關搜尋的關鍵資訊

首先我們要去掉新聞中與搜尋無關的詞。這裡就要引入“停用詞”的概念。

比如在一句話中:“小明和小紅去北京天安門玩了”,“小紅”、“小明”、“北京”、“天安門”都是對于搜尋有用的資訊,而“和”、“了”這些詞就和搜尋無關了。這些與搜尋無關的詞就叫“停用詞”。網上有停用詞的彙總,本文用的是這一個停用詞表。

為了提高搜尋速度,我們隻針對标題和關鍵詞進行搜尋,而關鍵詞中是沒有停用詞的,是以隻需去掉标題中的停用詞。

首先我們用

jieba

對标題進行分詞,然後去除停用詞以及标點。将剩下的詞連同關鍵詞一起作為搜尋的依據。統計每個詞出現的頻率,計入名為

Terms

的字典中,再将每則新聞

Terms

字典彙總入

Termdict

的list中:

def build_TermDict():
    global TermDict, N, avg_l
    global allPages
    TermDict.clear()
    avg_l = 0  #文檔平均長度,即平均每個新聞中有多少個有效詞
    N = len(allPages)
    cloudTerm = []  #用于雲圖建構
    for doc_id in range(0,N):
        doc = allPages[doc_id]
        #toCut = doc['title']*30 + doc['keywords']*15+doc['article'] #設定不同的權重
        toCut = doc['title']+doc['keywords']
        terms = jieba.cut_for_search(toCut)

        ld = 0  #文檔長度,即文章中有效詞的個數

        #去除标點和停用詞
        Terms = {}
        
        for p in terms:
            p = p.strip()
            if len(p)>0 and p not in stop_words and not p.isdigit():
                if p in Terms:
                    Terms[p] += 1
                else:
                    Terms[p] = 1
                cloudTerm.append(p)
                ld += 1
                avg_l += 1
        #将Terms中元素加入TermDict
        
        for p in Terms:
            if p in TermDict:
                TermDict[p][0] += Terms[p]
            else:
                TermDict[p] = [1,[]]
            TermDict[p][1].append('%d\t%s\t%d\t%d'%(doc_id, doc['date'], Terms[p],ld ))

    avg_l /= N
    
    #生成雲圖
    cloudText = ','.join(cloudTerm)
    wc = WordCloud(
    background_color="white", #背景顔色
    max_words=200, #顯示最大詞數
    font_path="simhei.ttf",  #使用字型
    min_font_size=15,
    max_font_size=50, 
    width=400  #圖幅寬度
    )
    wc.generate(cloudText)
    wc.to_file("wordcloud.gif")

    #将TermDict中文檔資訊合并成一個字元串,用\n隔開
    for i in TermDict:
        TermDict[i][1] = '\n'.join(TermDict[i][1])
           

ok✌️

3.2 儲存入資料庫

資訊提取完…當然是把資料存到資料庫中啊?

資料庫的好處在于:每次搜尋時不需要重新提取網頁中的資訊,直接在資料庫中查找就ok了。想象一下,如果每次搜尋都要等待幾分鐘讓程式爬取網頁的資訊,這酸爽,不敢相信啊。

在這裡我們用sqlite3存儲資料,代碼如下(如不熟悉sqlite3請自行百度,網上很多講解的?):

def WriteInDataBase():
    #将TermDict寫入資料庫
    global TermDict
    df = pandas.DataFrame(TermDict).T

    db = sqlite3.connect('news.sqlite')
    df.to_sql('TermDict',con = db,if_exists='replace')  #如果表存在就将表替代
    db.close()
    #将N 和 avg_l寫入txt檔案中
    paraF = open('parameter.txt','w')
    paraF.write('%d\t%d'%(N,avg_l))
    paraF.close()
    
    #将allPages寫入資料庫
    df = pandas.DataFrame(allPages)
    db = sqlite3.connect('news.sqlite')
    df.T.to_sql('allPages',con = db,if_exists='replace')  #如果表存在就将表替代
    
    db.close()
           

4. 重新整理一下,馬上回來

到此為止,搜尋新聞、處理新聞資訊以及建立資料庫的工作就完成了,我們最後用一個

refresh

函數來将上面寫的函數串在一起,完成重新整理的工作:

def refresh():
    global maxPage
    global allPages, TermDict, stop_words, N, avg_l
  
    #處理滾動頁面
    allPages = []
    
    dealPages(maxPage)
    
    #建構清單stop_words,包含停用詞和标點
    stop_words = []
    build_StopWords()
    
    #建構TermDict
    TermDict={}
    N = 0
    avg_l = 0
    build_TermDict()
    
    #将TermDict轉化為pandas中的DataFrame,再寫入資料庫sqlite
    WriteInDataBase()
           

小小地測試一下,嗯,不錯:

if __name__ == '__main__':
    refresh()
           

這樣,這一章的任務——新聞的搜集與資料庫的建立,就完成了?

寫在後面

如需檢視完整源代碼,請移步我的github頁面,本文所講内容在

Refresh.py

檔案裡。

如有不當,歡迎大神指出!