1. 程式人生 > >Python網路爬蟲練習

Python網路爬蟲練習

1. 豆瓣top250電影

1.1 檢視網頁

目標網址:https://movie.douban.com/top250?start=0&filter=

start=後面的數字從0,25,50一直到225,共10頁,每頁25條資訊

頁面截圖:

由此主頁面獲取各個電影的連結,然後分別跳轉至對應對應的連結爬取資訊。

主頁面原始碼:

<li>
<div class="item">
    <div class="pic">
        <em class="">1</em>
        <a href="https://movie.douban.com/subject/1292052/">
<img width="100" alt="肖申克的救贖" src="https://img3.doubanio.com/view/photo/s_ratio_poster/public/p480747492.webp" class="">
        </a>
    </div>
    <div class="info">
        <div class="hd">
<a href="https://movie.douban.com/subject/1292052/" class="">
    <span class="title">肖申克的救贖</span>
<span class="title">&nbsp;/&nbsp;The Shawshank Redemption</span>
        <span class="other">&nbsp;/&nbsp;月黑高飛(港)  /  刺激1995(臺)</span>
</a>


    <span class="playable">[可播放]</span>
        </div>
        <div class="bd">
<p class="">
    導演: 弗蘭克·德拉邦特 Frank Darabont&nbsp;&nbsp;&nbsp;主演: 蒂姆·羅賓斯 Tim Robbins /...<br>
    1994&nbsp;/&nbsp;美國&nbsp;/&nbsp;犯罪 劇情
</p>


<div class="star">
        <span class="rating5-t"></span>
        <span class="rating_num" property="v:average">9.7</span>
        <span property="v:best" content="10.0"></span>
        <span>2009428人評價</span>
</div>

    <p class="quote">
        <span class="inq">希望讓人自由。</span>
    </p>
        </div>
    </div>
</div>
        </li>

可以看到,連結藏在<div class="hd">中。

然後我們跳轉到第一個電影(肖申克的救贖)頁面,檢視待爬取資訊所在的位置。

1. 基本資訊

片名:

<h1>
        <span property="v:itemreviewed">肖申克的救贖 The Shawshank Redemption</span>
            <span class="year">(1994)</span>
    </h1>

其他資訊:

<div id="info">
        <span ><span class='pl'>導演</span>: <span class='attrs'><a href="/celebrity/1047973/" rel="v:directedBy">弗蘭克·德拉邦特</a></span></span><br/>
        <span ><span class='pl'>編劇</span>: <span class='attrs'><a href="/celebrity/1047973/">弗蘭克·德拉邦特</a> / <a href="/celebrity/1049547/">斯蒂芬·金</a></span></span><br/>
        <span class="actor"><span class='pl'>主演</span>: <span class='attrs'><a href="/celebrity/1054521/" rel="v:starring">蒂姆·羅賓斯</a> / <a href="/celebrity/1054534/" rel="v:starring">摩根·弗里曼</a> / <a href="/celebrity/1041179/" rel="v:starring">鮑勃·岡頓</a> / <a href="/celebrity/1000095/" rel="v:starring">威廉姆·賽德勒</a> / <a href="/celebrity/1013817/" rel="v:starring">克蘭西·布朗</a> / <a href="/celebrity/1010612/" rel="v:starring">吉爾·貝羅斯</a> / <a href="/celebrity/1054892/" rel="v:starring">馬克·羅斯頓</a> / <a href="/celebrity/1027897/" rel="v:starring">詹姆斯·惠特摩</a> / <a href="/celebrity/1087302/" rel="v:starring">傑弗裡·德曼</a> / <a href="/celebrity/1074035/" rel="v:starring">拉里·布蘭登伯格</a> / <a href="/celebrity/1099030/" rel="v:starring">尼爾·吉恩託利</a> / <a href="/celebrity/1343305/" rel="v:starring">布賴恩·利比</a> / <a href="/celebrity/1048222/" rel="v:starring">大衛·普羅瓦爾</a> / <a href="/celebrity/1343306/" rel="v:starring">約瑟夫·勞格諾</a> / <a href="/celebrity/1315528/" rel="v:starring">祖德·塞克利拉</a> / <a href="/celebrity/1014040/" rel="v:starring">保羅·麥克蘭尼</a> / <a href="/celebrity/1390795/" rel="v:starring">芮妮·布萊恩</a> / <a href="/celebrity/1083603/" rel="v:starring">阿方索·弗里曼</a> / <a href="/celebrity/1330490/" rel="v:starring">V·J·福斯特</a> / <a href="/celebrity/1000635/" rel="v:starring">弗蘭克·梅德拉諾</a> / <a href="/celebrity/1390797/" rel="v:starring">馬克·邁爾斯</a> / <a href="/celebrity/1150160/" rel="v:starring">尼爾·薩默斯</a> / <a href="/celebrity/1048233/" rel="v:starring">耐德·巴拉米</a> / <a href="/celebrity/1000721/" rel="v:starring">布賴恩·戴拉特</a> / <a href="/celebrity/1333685/" rel="v:starring">唐·麥克馬納斯</a></span></span><br/>
        <span class="pl">型別:</span> <span property="v:genre">劇情</span> / <span property="v:genre">犯罪</span><br/>
        
        <span class="pl">製片國家/地區:</span> 美國<br/>
        <span class="pl">語言:</span> 英語<br/>
        <span class="pl">上映日期:</span> <span property="v:initialReleaseDate" content="1994-09-10(多倫多電影節)">1994-09-10(多倫多電影節)</span> / <span property="v:initialReleaseDate" content="1994-10-14(美國)">1994-10-14(美國)</span><br/>
        <span class="pl">片長:</span> <span property="v:runtime" content="142">142分鐘</span><br/>
        <span class="pl">又名:</span> 月黑高飛(港) / 刺激1995(臺) / 地獄諾言 / 鐵窗歲月 / 消香克的救贖<br/>
        <span class="pl">IMDb連結:</span> <a href="https://www.imdb.com/title/tt0111161" target="_blank" rel="nofollow">tt0111161</a><br>

</div>

可以看到,該部分內容中包含了導演、編劇、主演、型別、製片國家/地區、語言、上映日期、片長、又名這些資訊,它們以分隔符<br/>進行分隔,下面我們將通過正則表示式進行提取。

2. 評分資訊

<div class="ratings-on-weight">
    
        <div class="item">
        
        <span class="stars5 starstop" title="力薦">
            5星
        </span>
        <div class="power" style="width:64px"></div>
        <span class="rating_per">85.1%</span>
        <br />
        </div>
        <div class="item">
        
        <span class="stars4 starstop" title="推薦">
            4星
        </span>
        <div class="power" style="width:10px"></div>
        <span class="rating_per">13.4%</span>
        <br />
        </div>
        <div class="item">
        
        <span class="stars3 starstop" title="還行">
            3星
        </span>
        <div class="power" style="width:0px"></div>
        <span class="rating_per">1.3%</span>
        <br />
        </div>
        <div class="item">
        
        <span class="stars2 starstop" title="較差">
            2星
        </span>
        <div class="power" style="width:0px"></div>
        <span class="rating_per">0.1%</span>
        <br />
        </div>
        <div class="item">
        
        <span class="stars1 starstop" title="很差">
            1星
        </span>
        <div class="power" style="width:0px"></div>
        <span class="rating_per">0.1%</span>
        <br />
        </div>
</div>

這部分原始碼包含了評分5星至1星所佔的百分數比例。

3. 影片簡介

<div class="related-info" style="margin-bottom:-10px;">
    <a name="intro"></a>
    
        
            
            
    <h2>
        <i class="">肖申克的救贖的劇情簡介</i>
              · · · · · ·
    </h2>

            <div class="indent" id="link-report">
                    
                        <span class="short">
                            <span property="v:summary">
                                      20世紀40年代末,小有成就的青年銀行家安迪(蒂姆·羅賓斯 Tim Robbins 飾)因涉嫌殺害妻子及她的情人而鋃鐺入獄。在這座名為肖申克的監獄內,希望似乎虛無縹緲,終身監禁的懲罰無疑註定了安迪接下來灰暗絕望的人生。未過多久,安迪嘗試接近囚犯中頗有聲望的瑞德(摩根·弗里曼 Morgan Freeman 飾),請求對方幫自己搞來小錘子。以此為契機,二人逐漸熟稔,安迪也彷彿在魚龍混雜、罪惡橫生、黑白混淆的牢獄中找到屬於自己的求生之道。他利用自身的專業知識,幫助監獄管理層逃稅、洗黑錢,同時憑藉與瑞德的交往在犯人中間也漸漸受到禮遇。表面看來,他已如瑞德那樣對那堵高牆從憎恨轉變為處之泰然,但是對自由的渴望仍促使他朝著心中的希望和目標前進。而關於其罪行的真相,似乎更使這一切朝前推進了一步……
                                        <br />
                                      本片根據著名作家斯蒂芬·金(Stephen Edwin King)的...
                            </span>
                            <a href="javascript:void(0)" class="j a_show_full">(展開全部)</a>
                        </span>
                        <span class="all hidden">
                                  20世紀40年代末,小有成就的青年銀行家安迪(蒂姆·羅賓斯 Tim Robbins 飾)因涉嫌殺害妻子及她的情人而鋃鐺入獄。在這座名為肖申克的監獄內,希望似乎虛無縹緲,終身監禁的懲罰無疑註定了安迪接下來灰暗絕望的人生。未過多久,安迪嘗試接近囚犯中頗有聲望的瑞德(摩根·弗里曼 Morgan Freeman 飾),請求對方幫自己搞來小錘子。以此為契機,二人逐漸熟稔,安迪也彷彿在魚龍混雜、罪惡橫生、黑白混淆的牢獄中找到屬於自己的求生之道。他利用自身的專業知識,幫助監獄管理層逃稅、洗黑錢,同時憑藉與瑞德的交往在犯人中間也漸漸受到禮遇。表面看來,他已如瑞德那樣對那堵高牆從憎恨轉變為處之泰然,但是對自由的渴望仍促使他朝著心中的希望和目標前進。而關於其罪行的真相,似乎更使這一切朝前推進了一步……
                                    <br />
                                  本片根據著名作家斯蒂芬·金(Stephen Edwin King)的原著改編。
                        </span>
                        <span class="pl"><a href="https://movie.douban.com/help/movie#t0-qs">&copy;豆瓣</a></span>
            </div>
</div>

在影片簡介較長時,全部資訊在class屬性為"all hidden"的span中,而簡介較短時,不存在這一標籤,property屬性為"x:summary"的span中就已包含了全部文字。

1.2 爬蟲程式碼

from urllib.request import urlopen, Request
from bs4 import BeautifulSoup
import re
import numpy as np
import pandas as pd
import time

#設定請求頭
headers = {'User-Agent':'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)'}

def getLinks(articleUrl):
    """通過頁面獲取各個電影的連結"""
    req = Request(url=articleUrl, headers=headers)
    html = urlopen(req)
    bsObj = BeautifulSoup(html)
    links = bsObj.findAll("div", {"class": "hd"})
    return [link.a.attrs["href"] for link in links]

#目標連結
head = "https://movie.douban.com/top250?start="
pages = [head + str(num) + "&filter=" for num in np.arange(0,250,25)]
links = []
for page in pages:
    #拼接所有連結,共250條
    links += getLinks(page)

def Merge(dict1, dict2):
    """合併字典"""
    res = {**dict1, **dict2}
    return res
allInfo = pd.DataFrame()
keyList1 = ['導演', '編劇', '主演', '型別', '製片國家/地區', 
          '語言', '上映日期', '片長', '又名']
keyList2 = ['5星', '4星', '3星', '2星', '1星']
def getInfo(articleUrl):
    """獲取每部電影的詳細資訊"""
    global allInfo
    req = Request(url=articleUrl, headers=headers)
    html = urlopen(req)
    bsObj = BeautifulSoup(html)
    #獲取基礎資訊info1
    info1 = bsObj.find("div", {"id": "info"})
    info1 = info1.get_text().split('\n')[1:-2]
    #通過正則表示式刪選資訊
    #貪婪匹配+?,無匹配生成空字串|$
    pattern1 = re.compile(".+?(?=:)|$")
    pattern2 = re.compile("(?<=:).+|$")
    keyList_prepare = [pattern1.findall(i)[0] for i in info1]
    valueList_prepare = [pattern2.findall(i)[0] for i in info1]
    valueList1=[None] * len(keyList1)

    for key in keyList1:
        if key in keyList_prepare:
            valueList1[keyList1.index(key)] = valueList_prepare[keyList_prepare.index(key)]
    dict1 = dict(zip(keyList1,valueList1))
    #獲取評分資訊info2
    info2 = bsObj.find("div", {"class": "ratings-on-weight"})
    info2 = info2.findAll("span", {"class", "rating_per"})
    valueList2 = [i.get_text() for i in info2]
    dict2 = dict(zip(keyList2, valueList2))
    dictAll = Merge(dict1, dict2)
    #獲取影片簡介
    try:
        dictAll = Merge(dictAll, {"簡介": bsObj.find("span", {"class": "all hidden"}).get_text().strip()})
    except:
        dictAll = Merge(dictAll, {"簡介": bsObj.find("span", {"property": "v:summary"}).get_text().strip()})
    allInfo = allInfo.append(dictAll,ignore_index=True)
    #返回影片片名
    return bsObj.h1.get_text().strip()

num = 0
title = []
startTime = time.time()
for link in links:
    title.append(getInfo(link))
    num += 1
    print("正在爬取第", num, "條資訊")
endTime = time.time()
#片名作為索引
allInfo.rename(index=dict(zip(range(0,allInfo.shape[0]),title)),inplace=True)
#資料框寫入CSV檔案
allInfo.to_excel('MovieInfo.xlsx', encoding="utf-8")
print("共爬取", allInfo.shape[0], "條資訊,用時", endTime-startTime, "秒")

1.3 部分說明

  1. 對於豆瓣的爬蟲,需要設定請求頭,否則訪問行為會被拒絕,發生HTTPError;
  2. 共10個頁面分別爬取,所以採用了字串相加生成列表存放這10個頁面的URL;
  3. links存放250個電影頁面的URL,列表合併直接用加號就行,links += getLinks(page);
  4. 學習了字典合併的方法,def Merge(dict1, dict2);
  5. 複習了正則表示式,貪婪匹配+?,有時匹配失敗會得到空列表,再引用元素[0]會報錯,使用|$可在無匹配時生成空字串,避免錯誤;
  6. 由於某些影片缺失部分資訊或多出部分資訊,所以需要以目標資訊keyList1中元素進行篩選;
  7. 由列表生成字典的方法,dict(zip(list1,list1));
  8. 由字典向資料框新增行的方法,allInfo = allInfo.append(dictAll,ignore_index=True);
  9. 由於爬蟲程式執行時間較長,使用了time.time()的方法進行計時;
  10. 最後採用.to_excel方法將資料框寫入Excel檔案中。

1.4 結果展示

耐心等待後……

開啟Excel檔案檢視:

2. Scrapy爬取千與千尋評論

2.1 檢視網頁

https://movie.douban.com/subject/1291561/comments?start=0&limit=20&sort=new_score&status=P

start=後面的數字從0開始以20為公差遞增,我們爬取0,20,40直到180,即前200條評論。

頁面截圖:

原始碼:

<div class="comment">
    <h3>
        <span class="comment-vote">
            <span class="votes">3492</span>
            <input value="82042845" type="hidden"/>
            <a href="javascript:;" class="j a_show_login" onclick="">有用</a>
        </span>
        <span class="comment-info">
            <a href="https://www.douban.com/people/oceanheart/" class="">深海的心</a>
                <span>看過</span>
                <span class="allstar40 rating" title="推薦"></span>
            <span class="comment-time " title="2008-12-22 22:28:54">
                2008-12-22
            </span>
        </span>
    </h3>
    <p class="">
        
            <span class="short">有那麼那麼經典嗎?還是我老了?</span>
    </p>
</div>

待爬取資訊:

評論使用者、使用者連結、評論內容、評論投票數、評論時間。

2.2 items.py

import scrapy

class TutorialItem(scrapy.Item):
    author = scrapy.Field()#評論使用者
    link = scrapy.Field()#使用者連結
    comment = scrapy.Field()#評論內容
    vote = scrapy.Field()#評論投票數
    time = scrapy.Field()#評論時間

2.3 myspider.py

import scrapy
from tutorial.items import TutorialItem
import numpy as np

head = "https://movie.douban.com/subject/1291561/comments?start="
class MySpider(scrapy.Spider):

    # 設定name
    name = "douban"
    # 設定域名
    allowed_domains = ["movie.douban.com"]
    # 填寫爬取地址
    start_urls = [head + str(num) + "&limit=20&sort=new_score&status=P" for num in np.arange(0,200,20)]
 
    # 編寫爬取方法
    def parse(self, response):
        for line in response.xpath('//div[@class="comment"]'):
            # 初始化item物件儲存爬取的資訊
            item = TutorialItem()
            # 這部分是爬取部分,使用xpath的方式選擇資訊,具體方法根據網頁結構而定
            item['author'] = line.xpath('.//span[@class="comment-info"]/a/text()').extract()
            item['link'] = line.xpath('.//span[@class="comment-info"]/a/@href').extract()
            item['comment'] = line.xpath('.//span[@class="short"]/text()').extract()
            item['vote'] = line.xpath('.//span[@class="comment-vote"]/span[@class="votes"]/text()').extract()
            item['time'] = line.xpath('.//span[@class="comment-info"]/span[@class="comment-time "]/text()').extract()
            yield item

仍採用列表存放URL,複習了XPath知識。

2.4 settings.py

設定編碼:

FEED_EXPORT_ENCODING ='utf-8'

修改請求頭:

USER_AGENT = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"

其他設定保持預設。

2.5 資料儲存

另外新建一個py指令碼,放在和工程資料夾tutorial相同的目錄下。

import pandas as pd
import os
import json

#切換工作目錄至當前指令碼路徑
os.chdir(os.path.split(os.path.realpath(__file__))[0]+'\\tutorial')
os.system("rm items.json")
os.system("scrapy crawl douban -o items.json")

with open('items.json', 'r', encoding='utf-8') as f:
    data = json.load(f)
for i in range(0,len(data)):
    for k in data[i].keys():
        data[i][k] = data[i][k][0].strip()
allInfo = pd.DataFrame()

allInfo = allInfo.append(data,ignore_index=True)

allInfo.to_excel('../commentInfo.xlsx', encoding="utf-8")
  1. 採用os包中的函式執行shell命令;
  2. 關於工作目錄的切換;
  3. 使用-o items.json儲存json檔案;
  4. 由於json難以直接閱讀,所以將其轉換為資料框並以Excel格式寫出。

2.6 檢視資料

執行程式後,在tutorial資料夾下新增items.json檔案。

在notepad++中開啟檢視:

在py指令碼檔案所在目錄新增commentInfo.xlsx檔案,開啟檢視:

3. 六度分隔爬蟲遊戲

六度分隔(Six Degrees of Separation)理論。簡單地說:“你和任何一個陌生人之間所間隔的人不會超五個,也就是說,最多通過六個人你就能夠認識任何一個陌生人。(來自百度百科)

百度百科中有很多詞條,並且相互直接有連結溝通,所以我們嘗試從某一詞條連結出發,不斷向其他詞條頁面跳轉,看看是否能在6次之內到達目標詞條。創意來自《Python網路資料採集》。

3.1 檢視網頁

這裡以我喜歡的歌手鄧麗君的詞條頁面為例。

部分原始碼:

通過觀察可以發現,指向其他詞條的連結都帶有屬性target="_blank",且連結樣式為類似於/item/%E6%B5%B7%E9%9F%B5的utf-8編碼。

以鄧麗君頁面為起始頁面,以我喜歡的演員劉亦菲的詞條頁面為終止頁面。

3.2 爬蟲程式碼

from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
import random

start = "鄧麗君"
end = "劉亦菲"

def getLinks(articleUrl):
    html = urlopen("https://baike.baidu.com"+articleUrl)
    bsObj = BeautifulSoup(html)
    allLinks = bsObj.findAll("a", {"target": "_blank"})
    links = []
    for i in range(len(allLinks)):
        isEntry = re.match("/item/(%[0-9A-F])+.+", allLinks[i].attrs["href"])
        text = allLinks[i].get_text().strip()
        repetition = allLinks[i].attrs["href"] in [link.attrs["href"] for link in links]
        if isEntry!=None and text!="" and not(repetition):
            links.append(allLinks[i])
    return links
def getUTF8(string):
    UTF8=str(string.encode('utf-8')).upper()
    pattern = re.compile("(?<=').+(?=')")
    return re.sub("\\\\X", "%", pattern.findall(UTF8)[0])

startLinks = getLinks("/item/"+getUTF8(start))
flag = 0
#試驗100次
for i in range(0,100):
    links = startLinks
    if(flag == 1):
        break
    for j in range(0,6):
        if len(links) > 0:
            if end in [link.get_text().strip() for link in links]:
                print("在第",i+1, "輪尋找", "第", j, "次跳轉", "中找到")
                flag = 1
                break
            ran = random.randint(0, len(links)-1)
            newArticle = links[ran].attrs["href"]
            print("第",i+1, "輪尋找", "第", j+1, "次跳轉", links[ran].get_text().strip())
            links = getLinks(newArticle)
        else:
            print("爬蟲中斷")
            break
if(flag == 0):
    print("100輪尋找已完成,很遺憾未能找到。請檢查start和end變數是否是百度百科中存在的詞條。")

3.3 部分說明

  1. 觀察發現百度百科詞條連結中的utf-8編碼就是詞條中文名的編碼,所以構建了getUTF8函式;
  2. 在getLinks函式中,對獲取到的連結進行如下檢查:是否符合詞條連結的格式(正則表示式),是否為空,是否重複,將符合格式、非空且不重複的連結列表返回;
  3. 最大跳轉次數為6,預設進行100輪嘗試,如果找到則停止。

3.4 結果展示

運行了兩次試驗一下,結果如上所示。

去瀏覽器看一下,確實可以根據爬蟲給出的路線找到“劉亦菲”。

因為這兩個明星比較有名氣吧,關聯到的詞條比較多,所以一般總是能在100輪尋找中找到,不過這也和運氣有關呢。

可以修改start和end進行其他測試,但是需要保證該詞語在百度百科的詞條中存在,否則肯定會報