1. 程式人生 > >利用Requests庫和正則表示式爬取豆瓣影評Top250

利用Requests庫和正則表示式爬取豆瓣影評Top250

說明

        最近看了下爬蟲基礎,想寫個部落格來記錄一下,一來是可以方便和我一樣剛入門的小白來參考學習,二來也當做自己的筆記供自己以後查閱。

        本文章是利用python3.6和Requests庫(需自行安裝,cmd裡執行pip install requests)以及正則表示式(其實利用正則表示式是比較麻煩的一種方式,但是正則表示式在很多語言都有應用,練習一下也是有好處的)來爬取豆瓣影評Top250的文字資訊以及然後儲存在本地的檔案當中,閱讀本文章前建議學習一下python基礎和正則表示式。

       第一次寫部落格而且剛剛入門python以及爬蟲基礎,本文的思路僅適用於不需要登入而且沒有反爬機制的HTML網頁,過程當中難免會有錯誤,還望大佬們能指出!

  抓取分析

https://movie.douban.com/top250?start=50&filter=,可以看出start引數的不同,分別為25和50,因此可以猜測後面url的start引數為75,100……利用這條規律就可以將url統一起來然後進行爬取

3、利用Requests下載目標網頁的HTML程式碼

import requests
from requests.exceptions import RequestException
def get_one_page(url):
    try:
        res=requests.get(url)
        if res.status_code==200:
            return  res.text
        return  None
    except RequestException:
        return  None
html=get_one_page('https://movie.douban.com/top250')
print(html)

本段程式碼是先用import匯入程式必要的包,定義了一個函式get_one_page,引數為目標url,呼叫這個函式會返回目標url的html文字。函式內部利用requests來下載網頁還是非常簡單的,requests利用了get方法請求了目標網頁,返回response物件,如果狀態碼是200(請求成功),就返回respose的text文字資訊,利用print進行列印測試

4、得到目標網頁的HTML程式碼之後,利用正則表示式進行解析。

   檢視網頁原始碼,可以發現每條電影的資訊都在ol標籤的li標籤下

接下來就是利用正則表示式對html文件進行解析了,在此我們設定爬取目標為每個電影的排名,名稱,描述,星級,評價人數以及主題等資訊。

首先可以看到肖申克的救贖這部電影的排名是在該li標籤下的em標籤內,提取排名的正則表示式:

<em class=" ">(\d+)</em>

關於正則表示式,可以利用線上測試工具http://tool.oschina.net/regex/#來測試自己寫出的正則表示式是否正確,如果在線上測試工具裡測試含有換行符的字串進行正則匹配會匹配不到的(可能是因為我的知識水平不夠哈,在python中可以新增re.S引數進行解決,因為一個點可以匹配除除了換行符以外的其他任意字元)這裡僅對相關的一些正則的語法進行解釋:

\d表示數字匹配,加上括號表示要進行提取的部分

提取電影名稱的正則表示式:

<span class="title">(.*?)</span>   #注意星號前面有個英文的點,這個點表示任意字元(換行符除外),型號表示任意長度,問號表示以非貪婪匹配的方式來匹配字串。

關於貪婪匹配:

比如說給定字串  hello54188hahaha   給定正則表示式  ^h.*(\d+)hahaha$以及 ^h.*?(\d+)hahaha$

# ^h表示查詢以字母h開頭的字串,hahaha$表示以hahaha結尾的字串,\d+表示匹配至少一個長度的數字

因為\d+代表提取出來至少長度為1的數字,所以第一種貪婪匹配的方式會把5418算進在.*中去,第一種方式得到的匹配出的數字的結果是8,而第二種非貪婪匹配的方式是把54188都算在\d+當中去的,所以第二中方式匹配除的結果為54188。所以說,你明白我的意思吧?

之後再對描述,星級,評價人數以及電影主演進行正則提取,在此都是同樣的原理,不再一一贅述,最後匹配出的綜合正則表示式為:

<em class="">(\d+)</em>.*?<span class="title">(.*?)</span>.*?<p class="">(.*?)</p>.*?<span class="rating_num" property="v:average">(.*?)</span>.*?<span>(.*?)</span>.*?<span class="inq">(.*?)</span>

利用該正則表示式對下載的HTML網頁進行解析

import requests
import re
from requests.exceptions import RequestException
def get_one_page(url):
    try:
        res=requests.get(url)
        if res.status_code==200:
            return  res.text
        return  None
    except RequestException:
        return  None
def parse_one_html(html):
    regex='<em class="">(\d+)</em>.*?<span class="title">(.*?)</span>.*?<p class="">(.*?)</p>.*?<span class="rating_num" property="v:average">(.*?)</span>.*?<span>(.*?)</span>.*?<span class="inq">(.*?)</span>'
    pattern=re.compile(regex,re.S)
    items=re.findall(pattern,html)
    print(items)
def main(url):
    html=get_one_page(url)
    parse_one_html(html)
main('https://movie.douban.com/top250')

在此又新定義了一個parse_one_html()函式,傳入引數為html字串,利用re模組的compile函式會對正則字串進行封裝,傳入引數re.S使得該正則物件能夠有效的解析出換行字串,前面提到過正常情況下一個點只能匹配出除了換行字元的任意字元,最終會解析除如下list列表(只列舉出一條資料):

[('1', '肖申克的救贖', '\n                            導演: 弗蘭克·德拉邦特 Frank Darabont&nbsp;&nbsp;&nbsp;主演: 蒂姆·羅賓斯 Tim Robbins /...<br>\n                            1994&nbsp;/&nbsp;美國&nbsp;/&nbsp;犯罪 劇情\n                        ', '9.6', '1170822人評價', '希望讓人自由。')]

這樣雖然得到了想要的結果,但是得到的資料比較雜亂,將匹配結果進一步進行處理,這裡對"描述“裡面的特殊字元&nbsp;和<br>進行處理,利用re模組的sub函式將其替換為空字串

#對得到的list列表進行處理
    for item in items:
        content=""
        for every_list in item[2].split():
            content=content+"".join(every_list)
        content=re.sub('&nbsp;',' ',content)
        content=re.sub('<br>', '', content)
        print(content)

得到描述的內容列印如下:

導演:弗蘭克·德拉邦特FrankDarabont   主演:蒂姆·羅賓斯TimRobbins/...1994 / 美國 / 犯罪劇情
導演:陳凱歌KaigeChen   主演:張國榮LeslieCheung/張豐毅FengyiZha...1993 / 中國大陸香港 / 劇情愛情同性
導演:呂克·貝鬆LucBesson   主演:讓·雷諾JeanReno/娜塔莉·波特曼...1994 / 法國 / 劇情動作犯罪
導演:RobertZemeckis   主演:TomHanks/RobinWrightPenn/GarySinise1994 / 美國 / 劇情愛情
導演:羅伯託·貝尼尼RobertoBenigni   主演:羅伯託·貝尼尼RobertoBeni...1997 / 義大利 / 劇情喜劇愛情戰爭
導演:詹姆斯·卡梅隆JamesCameron   主演:萊昂納多·迪卡普里奧Leonardo...1997 / 美國 / 劇情愛情災難
導演:宮崎駿HayaoMiyazaki   主演:柊瑠美RumiHîragi/入野自由Miy...2001 / 日本 / 劇情動畫奇幻
導演:史蒂文·斯皮爾伯格StevenSpielberg   主演:連姆·尼森LiamNeeson...1993 / 美國 / 劇情歷史戰爭
導演:克里斯托弗·諾蘭ChristopherNolan   主演:萊昂納多·迪卡普里奧Le...2010 / 美國英國 / 劇情科幻懸疑冒險
導演:安德魯·斯坦頓AndrewStanton   主演:本·貝爾特BenBurtt/艾麗...2008 / 美國 / 愛情科幻動畫冒險
導演:萊塞·霍爾斯道姆LasseHallström   主演:理查·基爾RichardGer...2009 / 美國英國 / 劇情
導演:拉庫馬·希拉尼RajkumarHirani   主演:阿米爾·汗AamirKhan/卡...2009 / 印度 / 劇情喜劇愛情歌舞
導演:朱塞佩·託納多雷GiuseppeTornatore   主演:蒂姆·羅斯TimRoth/...1998 / 義大利 / 劇情音樂
導演:克里斯托夫·巴拉蒂ChristopheBarratier   主演:傑拉爾·朱諾Géra...2004 / 法國瑞士德國 / 劇情音樂
導演:劉鎮偉JeffreyLau   主演:周星馳StephenChow/吳孟達ManTatNg...1995 / 香港中國大陸 / 喜劇愛情奇幻冒險
導演:彼得·威爾PeterWeir   主演:金·凱瑞JimCarrey/勞拉·琳妮Lau...1998 / 美國 / 劇情科幻
導演:弗朗西斯·福特·科波拉FrancisFordCoppola   主演:馬龍·白蘭度M...1972 / 美國 / 劇情犯罪
導演:宮崎駿HayaoMiyazaki   主演:日高法子NorikoHidaka/阪本千夏Ch...1988 / 日本 / 動畫奇幻冒險
導演:克里斯托弗·諾蘭ChristopherNolan   主演:馬修·麥康納MatthewMc...2014 / 美國英國加拿大冰島 / 劇情科幻冒險
導演:黃東赫Dong-hyukHwang   主演:孔侑YooGong/鄭有美Yu-miJeong...2011 / 韓國 / 劇情
導演:劉偉強/麥兆輝   主演:劉德華/梁朝偉/黃秋生2002 / 香港 / 劇情犯罪懸疑
導演:奧利維·那卡什OlivierNakache/艾力克·託蘭達EricToledano   主...2011 / 法國 / 劇情喜劇
導演:加布里爾·穆奇諾GabrieleMuccino   主演:威爾·史密斯WillSmith...2006 / 美國 / 劇情傳記家庭
導演:維克多·弗萊明VictorFleming/喬治·庫克GeorgeCukor   主演:費...1939 / 美國 / 劇情歷史愛情戰爭
導演:羅伯·萊納RobReiner   主演:瑪德琳·卡羅爾MadelineCarroll/卡...2010 / 美國 / 劇情喜劇愛情

Process finished with exit code 0

將得到的新的描述以及爬取到的其他內容資訊儲存到dict字典當中去

#對得到的list列表進行處理
    for item in items:
        content=""
        for every_list in item[2].split():
            content=content+"".join(every_list)
        content=re.sub('&nbsp;',' ',content)
        content=re.sub('<br>', '', content)
        print(content)
        #將獲取到的資訊儲存到
        dict={
            "number":item[0],
            "name":item[1],
            "describe":content,
            "star":item[3],
            "evaluate":item[4],
            "title":item[5]

        }
        print(dict)

打印出dict結果如下,可以看出來經過處理後的結果比較整齊規範

接下來就是要利用json庫將dict內的資料儲存到本地檔案當中去,在此先匯入json模組 import json,在for迴圈裡面接下來寫入如下程式碼,以utf-8編碼追加的方式開啟本地txt並迴圈將每條記錄寫入txt檔案當中

        with open('result.txt','a',encoding='utf-8') as f:
            f.write(json.dumps(dict,ensure_ascii=False)+'\n')

至此首頁的25條記錄寫入本地txt檔案完成 

因為我們要爬取的是250條記錄,所以一共要遍歷10個url進行爬取,前面已經說過每個url只是start引數不同,因此需要給主函式傳遞一個start函式,呼叫10次主函式得到完整的250條資料。

def main(start):
    url='https://movie.douban.com/top250'
    if start!=0:
        url='https://movie.douban.com/top250?start='+str(start)+'&filter='
    html=get_one_page(url)
    parse_one_html(html)
if __name__=='__main__':
    for i in range(10):
        main(i*25)

在主函式內根據傳入的start引數不同,請求的url也不同,然後呼叫10次主函式傳入不同的引數就可以發現在本地檔案中儲存了250條記錄。

完整程式碼如下:

#!/usr/bin/env python 
# -*- coding:utf-8 -*-
import requests
import re
import json
from requests.exceptions import RequestException
#得到網頁原始碼
def get_one_page(url):
    try:
        res=requests.get(url)
        if res.status_code==200:
            return  res.text
        return  None
    except RequestException:
        return  None
#對原始碼進行解析將要獲取的資料儲存到dict當中去然後寫入本地檔案
def parse_one_html(html):
    regex='<em class="">(\d+)</em>.*?<span class="title">(.*?)</span>.*?<p class="">(.*?)</p>.*?<span class="rating_num" property="v:average">(.*?)</span>.*?<span>(.*?)</span>.*?<span class="inq">(.*?)</span>'
    pattern=re.compile(regex,re.S)
    items=re.findall(pattern,html)
    #對得到的list列表進行處理,將每條記錄變成一個dict
    for item in items:
        content=""
        for every_list in item[2].split():
            content=content+"".join(every_list)
        content=re.sub('&nbsp;',' ',content)
        content=re.sub('<br>', '', content)
        #將獲取到的資訊儲存到dict字典當中
        dict={
            "number":item[0],
            "name":item[1],
            "describe":content,
            "star":item[3],
            "evaluate":item[4],
            "title":item[5]

        }
        #將得到的dict寫入本地檔案當中去
        with open('result.txt','a',encoding='utf-8') as f:
            f.write(json.dumps(dict,ensure_ascii=False)+'\n')
def main(start):
    url='https://movie.douban.com/top250'
    if start!=0:
        url='https://movie.douban.com/top250?start='+str(start)+'&filter='
    html=get_one_page(url)
    parse_one_html(html)
if __name__=='__main__':
    for i in range(10):
        main(i*25)

 所以說,第一篇部落格完成了!