1. 程式人生 > >爬蟲基礎以及一個簡單的實例

爬蟲基礎以及一個簡單的實例

for 規律 ext offset 是什麽 循環 網絡 code 發送

最近在看爬蟲方面的知識,看到崔慶才所著的《Python3網絡爬蟲開發實戰》一書講的比較系統,果斷入手學習。下面根據書中的內容,簡單總結一下爬蟲的基礎知識,並且實際練習一下。詳細內容請見:https://cuiqingcai.com/5465.html(作者已把書的前幾章內容對外公開)。

在寫爬蟲程序之前需要了解的一些知識:

爬蟲基礎:我們平時訪問網頁就是對服務器發送請求(Request),然後得到響應(Response)的一個過程。爬蟲通過模仿瀏覽器,對網頁進行自動訪問。需要知道請求包含哪些內容,請求的方式有哪些,響應包含哪些內容。

網頁結構:網頁由HTMLCSSJaveScript組成。需要知道其各自的作用是什麽,還需要知道到哪個節點去獲取自己想要的信息。

其他:了解會話(Session),Cookie,代理(Proxy)的作用。

爬蟲流程:

  • 爬取網頁(獲取網頁源代碼):可使用的庫有urllib,requests等;當然,現在很多網頁都是動態加載的,對於這些網頁,還需使用Selenium等庫
  • 解析網頁(提取網頁中我們需要的信息):定位信息的方式有:正則表達式XPath選擇器CSS選擇器;可使用的庫有re,lxml,Beautiful Soup等
  • 保存結果(將結果保存至文件或數據庫):文件有txt,json等格式;數據庫可選擇MySQL,MangoDB等

下面讓我們來實際操練一下:

實例目標:用requests庫爬取貓眼電影網上top100的電影(排名,圖片,電影名稱,上映時間,評分),用正則表達式進行解析,然後將結果保存至txt文件

實例網址:https://maoyan.com/board/4

首先,導入requests庫和re,json模塊:

import requests
import re
import json

其次,先定義一個爬取一個網頁的方法:

def get_one_page(url):
    headers={User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3)              AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36
} response=requests.get(url,headers=headers) if response.status_code==200: return response.text return None

這樣,在main()方法裏,我們設定好url,就可以把該網頁源代碼打印出來:

def main():
    url="https://maoyan.com/board/4"
    html=get_one_page(url)
    print(html)

接下來,我們來仔細查看這個源代碼,看看怎樣用正則表達式把我們需要的信息提取出來。首先用瀏覽器打開這個網頁,然後在瀏覽器裏面選擇開發者工具,在Network裏查看網頁源代碼。下面截取一部分:

<div class="content">
    <div class="wrapper">
        <div class="main">
            <p class="update-time">2018-12-30<span class="has-fresh-text">已更新</span></p>
            <p class="board-content">榜單規則:將貓眼電影庫中的經典影片,按照評分和評分人數從高到低綜合排序取前100名,每天上午10點更新。相關數據來源於“貓眼電影庫”。</p>
            <dl class="board-wrapper">
                <dd>
                        <i class="board-index board-index-1">1</i>
    <a href="/films/1203" title="霸王別姬" class="image-link" data-act="boarditem-click" data-val="{movieId:1203}">
      <img src="//ms0.meituan.net/mywww/image/loading_2.e3d934bf.png" alt="" class="poster-default" />
      <img data-src="https://p1.meituan.net/movie/20803f59291c47e1e116c11963ce019e68711.jpg@160w_220h_1e_1c" alt="霸王別姬" class="board-img" />
    </a>
    <div class="board-item-main">
      <div class="board-item-content">
              <div class="movie-item-info">
        <p class="name"><a href="/films/1203" title="霸王別姬" data-act="boarditem-click" data-val="{movieId:1203}">霸王別姬</a></p>
        <p class="star">
                主演:張國榮,張豐毅,鞏俐
        </p>
<p class="releasetime">上映時間:1993-01-01</p>    </div>
    <div class="movie-item-number score-num">
<p class="score"><i class="integer">9.</i><i class="fraction">6</i></p>   

可以看到,電影的排名在一個dd節點下面:

 <dd>
                        <i class="board-index board-index-1">1</i>

因此,相應的正則表達式可以寫為:<dd>.*?board-index.*?>(.*?)</i>

接下來,我們發現圖片在一個a節點下面,但是有兩張圖片。經過檢查,第二個img節點下的data-src屬性是圖片的鏈接:

 <img data-src="https://p1.meituan.net/movie/20803f59291c47e1e116c11963ce019e68711.jpg@160w_220h_1e_1c" alt="霸王別姬" class="board-img" />

因此,相應的正則表達式可以寫為:.*?data-src="(.*?)" (註:因為這個會接在之前的正則表達式之後,因此最前面寫上.*?即可。下同。)

再接下來,電影的名稱,在一個p節點下面,class為"name":

<p class="name"><a href="/films/1203" title="霸王別姬" data-act="boarditem-click" data-val="{movieId:1203}">霸王別姬</a></p>

相應的正則表達式可以寫為:.*?name.*?a.*?>(.*?)</a>

上映時間,在一個p節點下面,class為"releasetime":

<p class="releasetime">上映時間:1993-01-01</p>

相應的正則表達式可以寫為:.*?releasetime.*?>(.*?)</p>

評分,在一個p節點下面,class為"score":

<p class="score"><i class="integer">9.</i><i class="fraction">6</i></p>

相應的正則表達式可以寫為:.*?score.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd> (註:最後用dd節點收尾)

把這些正則表達式連接起來,然後就可以用findall()方法查找出所有符合條件的內容。完整的正則表達式如下:

<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?releasetime.*?>(.*?)</p>.*?score.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>

下面,我們再定義一個解析網頁的方法:

def parse_one_page(html):
    pattern=re.compile(<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?releasetime.*?>(.*?)</p>.*?score.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>, re.S)
    result=re.findall(pattern, html)
    return result

這裏需要註意,在定義正則表達式的pattern時,必須加上re.S修飾符(匹配包括換行符在內的所有字符),否則碰到換行就無法進行匹配。

輸出的匹配結果如下:

[(1, https://p1.meituan.net/movie/20803f59291c47e1e116c11963ce019e68711.jpg@160w_220h_1e_1c, 霸王別姬, 上映時間:1993-01-01, 9., 6), (2, https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@160w_220h_1e_1c, 肖申克的救贖, 上映時間:1994-10-14(美國), 9., 5), (3, https://p0.meituan.net/movie/54617769d96807e4d81804284ffe2a27239007.jpg@160w_220h_1e_1c, 羅馬假日, 上映時間:1953-09-02(美國), 9., 1), (4, https://p0.meituan.net/movie/e55ec5d18ccc83ba7db68caae54f165f95924.jpg@160w_220h_1e_1c, 這個殺手不太冷, 上映時間:1994-09-14(法國), 9., 5), (5, https://p1.meituan.net/movie/f5a924f362f050881f2b8f82e852747c118515.jpg@160w_220h_1e_1c, 教父, 上映時間:1972-03-24(美國), 9., 3), (6, https://p1.meituan.net/movie/0699ac97c82cf01638aa5023562d6134351277.jpg@160w_220h_1e_1c, 泰坦尼克號, 上映時間:1998-04-03, 9., 5), (7, https://p0.meituan.net/movie/da64660f82b98cdc1b8a3804e69609e041108.jpg@160w_220h_1e_1c, 唐伯虎點秋香, 上映時間:1993-07-01(中國香港), 9., 2), (8, https://p0.meituan.net/movie/b076ce63e9860ecf1ee9839badee5228329384.jpg@160w_220h_1e_1c, 千與千尋, 上映時間:2001-07-20(日本), 9., 3), (9, https://p0.meituan.net/movie/46c29a8b8d8424bdda7715e6fd779c66235684.jpg@160w_220h_1e_1c, 魂斷藍橋, 上映時間:1940-05-17(美國), 9., 2), (10, https://p0.meituan.net/movie/230e71d398e0c54730d58dc4bb6e4cca51662.jpg@160w_220h_1e_1c, 亂世佳人, 上映時間:1939-12-15(美國), 9., 1)]

可以看出,上述的格式還是有些雜亂,讓我們修改一下解析網頁的方法,使其變為整齊的結構化數據:

def parse_one_page(html):
    pattern=re.compile(<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?releasetime.*?>(.*?)</p>.*?score.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>, re.S)
    result=re.findall(pattern, html)
    for item in result:
        yield {"index": item[0], "movie_name": item[2],                "pic": item[1], "release": item[3],                "score": item[4]+item[5]}

現在匹配結果變成了字典格式:

{index: 1, movie_name: 霸王別姬, pic: https://p1.meituan.net/movie/20803f59291c47e1e116c11963ce019e68711.jpg@160w_220h_1e_1c, release: 上映時間:1993-01-01, score: 9.6}
{index: 2, movie_name: 肖申克的救贖, pic: https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@160w_220h_1e_1c, release: 上映時間:1994-10-14(美國), score: 9.5}
{index: 3, movie_name: 羅馬假日, pic: https://p0.meituan.net/movie/54617769d96807e4d81804284ffe2a27239007.jpg@160w_220h_1e_1c, release: 上映時間:1953-09-02(美國), score: 9.1}
{index: 4, movie_name: 這個殺手不太冷, pic: https://p0.meituan.net/movie/e55ec5d18ccc83ba7db68caae54f165f95924.jpg@160w_220h_1e_1c, release: 上映時間:1994-09-14(法國), score: 9.5}
{index: 5, movie_name: 教父, pic: https://p1.meituan.net/movie/f5a924f362f050881f2b8f82e852747c118515.jpg@160w_220h_1e_1c, release: 上映時間:1972-03-24(美國), score: 9.3}
{index: 6, movie_name: 泰坦尼克號, pic: https://p1.meituan.net/movie/0699ac97c82cf01638aa5023562d6134351277.jpg@160w_220h_1e_1c, release: 上映時間:1998-04-03, score: 9.5}
{index: 7, movie_name: 唐伯虎點秋香, pic: https://p0.meituan.net/movie/da64660f82b98cdc1b8a3804e69609e041108.jpg@160w_220h_1e_1c, release: 上映時間:1993-07-01(中國香港), score: 9.2}
{index: 8, movie_name: 千與千尋, pic: https://p0.meituan.net/movie/b076ce63e9860ecf1ee9839badee5228329384.jpg@160w_220h_1e_1c, release: 上映時間:2001-07-20(日本), score: 9.3}
{index: 9, movie_name: 魂斷藍橋, pic: https://p0.meituan.net/movie/46c29a8b8d8424bdda7715e6fd779c66235684.jpg@160w_220h_1e_1c, release: 上映時間:1940-05-17(美國), score: 9.2}
{index: 10, movie_name: 亂世佳人, pic: https://p0.meituan.net/movie/230e71d398e0c54730d58dc4bb6e4cca51662.jpg@160w_220h_1e_1c, release: 上映時間:1939-12-15(美國), score: 9.1}

接下來要將結果寫入txt文件,這裏定義一個寫入文件的方法:

def write_to_file(result):
    with open ("result.txt","a") as f:
        f.write(json.dumps(result, ensure_ascii=False)+\n)

然後在main方法裏將結果逐行寫入文件:

def main():
    url="https://maoyan.com/board/4"
    html=get_one_page(url)
    result=parse_one_page(html)
    for i in result:
        write_to_file(i)

這裏有幾個需要註意的地方:1,由於需要將結果逐行寫入,因此文件用"a"方式打開,a也就是append。

2,由於需要將結果逐行寫入,因此將結果寫入文件時最後加上換行符"\n"。

3,由於結果是字典格式,無法直接寫入文件,需要先用json.dumps方法把字典轉為字符串,但是這樣會導致中文亂碼。根據json.dumps方法的註釋,如果將ensure_ascii設為false,那麽寫入的字符串可以包含非ASCII字符,否則,所有這些字符都會在JSON字符串中轉義。也就是說將參數ensure_ascii設為False可以使中文(UTF-8編碼)不經過轉義,也就不會亂碼。

至此,第一頁網頁就已經全部爬取成功了。但是一共有10頁這樣的網頁,我們打開第二個網頁和第三個網頁看一下。可以發現,第二個網頁的url變為:https://maoyan.com/board/4?offset=10,第三頁網頁的url則是:https://maoyan.com/board/4?offset=20。可以發現規律就是多了一個offset值,那麽我們把1~10頁的網頁爬取url設置從offset為0,一直到offset為90,就可以爬取所有網頁了。

由於我們在main方法裏設定了爬取url,因此我們給main方法增加一個輸入參數,也就是offset偏移值,這樣,我們就能爬取我們想要的網頁了。最後,再增添一個循環語句,用於爬取各種offset的網頁,這樣,一個簡單的爬蟲程序就完成了。

我們再把代碼重新整合一下,並且由於現在貓眼多了反爬蟲,如果爬取速度過快,會沒有響應,因此,需要加上一個延時。

完整代碼如下:

import requests
import re
import json
import time

def get_one_page(url):
    try:
        headers={User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3)                  AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36}
        response=requests.get(url, headers=headers)
        if response.status_code==200:
            return response.text
        return None
    except requests.RequestException:
        print("Fail")

def parse_one_page(html):
    pattern=re.compile(<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?releasetime.*?>(.*?)</p>.*?score.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>, re.S)
    result=re.findall(pattern, html)
    for item in result:
        yield {"index": item[0], "movie_name": item[2],                "pic": item[1], "release": item[3],                "score": item[4]+item[5]}

def write_to_file(result):
    with open ("result.txt","a") as f:
        f.write(json.dumps(result, ensure_ascii=False)+\n)

def main(offset):
    url="https://maoyan.com/board/4?offset={}".format(offset)
    html=get_one_page(url)
    result=parse_one_page(html)
    for i in result:
        write_to_file(i)

if __name__==__main__:
    for i in range(10):
        main(offset=i*10)
        time.sleep(1)

爬蟲基礎以及一個簡單的實例