requests+正則表達式+multiprocessing多線程抓取貓眼電影TOP100

本文介紹利用Requests庫、multiprocessing庫和正則表達式爬取貓眼電影TOP100電影的相關信息,提取出電影名稱、上映時間、評分、封面圖片等信息,將爬取的內容寫入到文件中。站點URL爲 http://maoyan.com/board/4html

準備

本文使用了Requests庫,使用pip安裝: pip install requestspython

分析

打開http://maoyan.com/board/4,能夠看到榜單信息。以下圖所示
maoyanTop100git

排名第一的電影是霸王別姬,能夠提取的信息有電影名稱、主演、上映時間、評分、封面圖等。
點擊頁面下方的分頁列表翻頁到第二頁,會發現URL會變成https://maoyan.com/board/4?offset=10,比首頁多了個offset=10 參數,而目前顯示的是排名11-20的電影,初步判斷這是偏移量參數。再點擊下一頁,URL變成了https://maoyan.com/board/4?offset=20offset變成了20,顯示的是排名21-30的電影。
因而可知,offset表明偏移量,偏移量爲n,則顯示的是排名n+1~n+10的電影,每頁顯示10個電影。因此,想要獲取TOP100電影信息,只要分開獲取10次,只需把10次請求的URL中offset參數分別設爲 0,10,20,30...90便可(首頁的offset值爲0)。獲取到不一樣的網頁後使用正則表達式提取出咱們要的信息,就能夠獲得TOP100電影信息了,可使用多線程加速爬取。github

爬取實現

爬取首頁

實現get_page()方法,傳入url參數能夠將抓取的頁面結果返回。如下代碼獲取首頁內容:正則表達式

import requests
from requests.exceptions import RequestException

def get_page(url):
    headers = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
    }
    response = requests.get(url,headers = headers)
    try:
        if response.status_code == 200:
            return response.text
        return None
    except RequestException:
        print("request error")
        return None

def main():
    html = get_page('https://maoyan.com/board/4')
    print(html)

main()

運行以後就成功獲取到了首頁的源代碼,接下來使用正則表達式進行解析,提取出咱們想要的信息。json

正則提取

回到瀏覽器頁面,在開發者工具Network監聽組件中查看源代碼。如圖:
maoyan
值得注意的是這裏不是從Elements選項卡里查看的源代碼,由於Elements裏看到的源代碼頗有可能通過Javascript處理過從而和原始請求不一樣,因此要從Network選項卡里查看原始請求獲得的源碼。瀏覽器

查看此處代碼:
maoyan
FadC4g.png
不難發現,要爬取的每部電影信息都在<dd>標籤裏,接下來使用正則表達式提取信息。多線程

首先,提取它的排名信息,它的排名信息在classboard-indexi標籤裏,使用非貪婪匹配來提取i內的信息,正則表達式能夠寫爲:<dd>.*?board-index.*?>(\d+)</i>函數

接下來提取電影的封面圖片。在排名後面的a便籤裏有兩個img便籤,通過檢查,第二個img是電影的封面圖片,正則:.*?data-src="(.*?)"工具

而後提取電影的名稱,它在classname<p>便籤內,可使用name做爲標誌位進一步提取到其內a的文本內容,正則寫爲:.*?name.*?a.*?>(.*?)</a>

提取主演:.*?star">(.*?)</p>
提取上映時間:.*?releasetime">(.*?)</p>
提取評分:.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>

最後正則表達式寫爲:
<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>

上面的正則表達式能夠匹配一個電影,匹配了7條信息,接下來能夠經過findall()方法提取全部內容。能夠定義一個用來解析頁面的方法parse_page(),代碼以下:

def parse_page(html):
    pattern = re.compile('<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star">(.*?)</p>'
    + '.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',r.S) #re.S使.能匹配任意字符
    items = pattern.findall(str(html))

這樣就成功得拿到了一頁10個電影的信息,這是一個列表,獲取到的結果以下:

[('1', 'https://p1.meituan.net/movie/20803f59291c47e1e116c11963ce019e68711.jpg@160w_220h_1e_1c', '霸王別姬', '\n                主演:張國榮,張豐毅,鞏俐\n        ', '上映時間:1993-01-01', '9.', '6'), ('2', 'https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@160w_220h_1e_1c', '肖申克的救贖', '\n                主演:蒂姆·羅賓斯,摩
根·弗里曼,鮑勃·岡頓\n        ', '上映時間:1994-10-14(美國)', '9.', '5'), ('3', 'https://p0.meituan.net/movie/54617769d96807e4d81804284ffe2a27239007.jpg@160w_220h_1e_1c', '羅
馬假日', '\n                主演:格利高裏·派克,奧黛麗·赫本,埃迪·艾伯特\n        ', '上映時間:1953-09-02(美國)', '9.', '1'), ('4', 'https://p0.meituan.net/movie/e55ec5d18ccc
83ba7db68caae54f165f95924.jpg@160w_220h_1e_1c', '這個殺手不太冷', '\n                主演:讓·雷諾,加里·奧德曼,娜塔莉·波特曼\n        ', '上映時間:1994-09-14(法國)', '9.', '
5'), ('5', 'https://p1.meituan.net/movie/f5a924f362f050881f2b8f82e852747c118515.jpg@160w_220h_1e_1c', '教父', '\n                主演:馬龍·白蘭度,阿爾·帕西諾,詹姆斯·肯恩\n
      ', '上映時間:1972-03-24(美國)', '9.', '3'), ('6', 'https://p1.meituan.net/movie/0699ac97c82cf01638aa5023562d6134351277.jpg@160w_220h_1e_1c', '泰坦尼克號', '\n
    主演:萊昂納多·迪卡普里奧,凱特·溫絲萊特,比利·贊恩\n        ', '上映時間:1998-04-03', '9.', '5'), ('7', 'https://p0.meituan.net/movie/da64660f82b98cdc1b8a3804e69609e04110
8.jpg@160w_220h_1e_1c', '唐伯虎點秋香', '\n                主演:周星馳,鞏俐,鄭佩佩\n        ', '上映時間:1993-07-01(中國香港)', '9.', '2'), ('8', 'https://p0.meituan.net/movie/b076ce63e9860ecf1ee9839badee5228329384.jpg@160w_220h_1e_1c', '千與千尋', '\n                主演:柊瑠美,入野自由,夏木真理\n        ', '上映時間:2001-07-20(日本)', '9.', '3'), ('9', 'https://p0.meituan.net/movie/46c29a8b8d8424bdda7715e6fd779c66235684.jpg@160w_220h_1e_1c', '魂斷藍橋', '\n                主演:費雯·麗,羅伯特·泰勒,露塞爾·沃特森\n
    ', '上映時間:1940-05-17(美國)', '9.', '2'), ('10', 'https://p0.meituan.net/movie/230e71d398e0c54730d58dc4bb6e4cca51662.jpg@160w_220h_1e_1c', '亂世佳人', '\n
主演:費雯·麗,克拉克·蓋博,奧利維婭·德哈維蘭\n        ', '上映時間:1939-12-15(美國)', '9.', '1')]

這樣的數據看上去很雜亂,使用字典將數據格式化:

for item in items:
    yield {
        'top':item[0],
        'image_src':item[1],
        'name':item[2],
        'actor':item[3].strip()[3:] if len(item[3]) > 3 else '',
        'releasetime':item[4].strip()[5:],
        'score':item[5] + item[6]
    }

這樣就能夠得到電影信息的結構化數據了,每一個電影的信息都包含在一個字典裏。得到的結果以下:

{'top': '1', 'image_src': 'https://p1.meituan.net/movie/20803f59291c47e1e116c11963ce019e68711.jpg@160w_220h_1e_1c', 'name': '霸王別姬', 'actor': '張國榮,張豐毅,鞏俐', 'releasetime': '1993-01-01', 'score': '9.6'}
{'top': '2', 'image_src': 'https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@160w_220h_1e_1c', 'name': '肖申克的救贖', 'actor': '蒂姆·羅賓斯,摩根·弗里曼,鮑勃·岡頓', 'releasetime': '1994-10-14(美國)', 'score': '9.5'}
{'top': '3', 'image_src': 'https://p0.meituan.net/movie/54617769d96807e4d81804284ffe2a27239007.jpg@160w_220h_1e_1c','name': '羅馬假日', 'actor': '格利高裏·派克,奧黛麗·赫本,埃迪·艾伯特', 'releasetime': '1953-09-02(美國)', 'score': '9.1'}
{'top': '4', 'image_src': 'https://p0.meituan.net/movie/e55ec5d18ccc83ba7db68caae54f165f95924.jpg@160w_220h_1e_1c', 'name': '這個殺手不太冷', 'actor': '讓·雷諾,加里·奧德曼,娜塔莉·波特曼', 'releasetime': '1994-09-14(法國)', 'score': '9.5'}
{'top': '5', 'image_src': 'https://p1.meituan.net/movie/f5a924f362f050881f2b8f82e852747c118515.jpg@160w_220h_1e_1c', 'name': '教父', 'actor': '馬龍·白蘭度,阿爾·帕西諾,詹姆斯·肯恩', 'releasetime': '1972-03-24(美國)', 'score': '9.3'}
{'top': '6', 'image_src': 'https://p1.meituan.net/movie/0699ac97c82cf01638aa5023562d6134351277.jpg@160w_220h_1e_1c', 'name': '泰坦尼克號', 'actor': '萊昂納多·迪卡普里奧,凱特·溫絲萊特,比利·贊恩', 'releasetime': '1998-04-03', 'score': '9.5'}
{'top': '7', 'image_src': 'https://p0.meituan.net/movie/da64660f82b98cdc1b8a3804e69609e041108.jpg@160w_220h_1e_1c', 'name': '唐伯虎點秋香', 'actor': '周星馳,鞏俐,鄭佩佩', 'releasetime': '1993-07-01(中國香港)', 'score': '9.2'}
{'top': '8', 'image_src': 'https://p0.meituan.net/movie/b076ce63e9860ecf1ee9839badee5228329384.jpg@160w_220h_1e_1c', 'name': '千與千尋', 'actor': '柊瑠美,入野自由,夏木真理', 'releasetime': '2001-07-20(日本)', 'score': '9.3'}
{'top': '9', 'image_src': 'https://p0.meituan.net/movie/46c29a8b8d8424bdda7715e6fd779c66235684.jpg@160w_220h_1e_1c', 'name': '魂斷藍橋', 'actor': '費雯·麗,羅伯特·泰勒,露塞爾·沃特森', 'releasetime': '1940-05-17(美國)', 'score': '9.2'}
{'top': '10', 'image_src': 'https://p0.meituan.net/movie/230e71d398e0c54730d58dc4bb6e4cca51662.jpg@160w_220h_1e_1c', 'name': '亂世佳人', 'actor': '費雯·麗,克拉克·蓋博,奧利維婭·德哈維蘭', 'releasetime': '1939-12-15(美國)', 'score': '9.1'}

寫入文件

獲得數據後最後將數據保存到文件,經過JOSN庫的dumps()方法能夠實現字典的序列化。由於這裏要處理中文,將ensure_ascii參數設爲False就能夠保證輸出結果是中文形式而不是Unicode編碼。代碼以下:

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

其中open()指定寫入方式爲a尾部寫入,這是由於此時是for循環寫入數據,若是用w寫入只會保留最後一組的數據。或者在這以前打開文件,等寫入完數據後再關閉也能夠。
經過調用write_to_file()方法便可實現將字典寫入到文本文件的過程。

main方法

實現main()方法接收一個offset值做爲偏移量,而後構造URL進行爬取。代碼以下:

def main(offset):
    url = "http://maoyan.com/board/4?offset=" + str(offset)
    html = get_page(url)
    for item in parse_page(html):
        print(item)
        write_of_file(item)

多線程分頁爬取

上面實現了給main()傳入一個offset值爬取單頁10個電影的數據,接下來使用多線程來抓取整個TOP100的電影數據。

from multiprocessing import Pool  # 引入多線程模塊

if __name__ == '__main__':
    #建立線程池
    pool = Pool()
    # pool.map第一個參數是函數,第二個參數是傳遞給函數的參數
    pool.map(main,[i*10 for i in range(10)])

Pool.map()函數第一個參數是函數,第二個參數是傳遞給函數的參數,在上面代碼中是一個迭代器,將迭代器中的數字做爲參數依次傳入函數中。
注意:使用多線程爬取會致使最後寫入到文件內的電影數據(top值)是亂序的,如需保證爬取到的電影信息寫入到文件是按照top值排序的,放棄多線程將代碼改成:

import time #引入時間模塊
if __name__ == '__main__':
    for i in range(10):
        main(offset=i * 10)
        time.sleep(1)

爲突破貓眼反爬蟲機制(速度過快會無響應),上面代碼增長了一個延時等待。

大功告成!完整代碼以下:

import requests
import re
import time
import json
from requests.exceptions import RequestException
from multiprocessing import Pool

def get_page(url):
    headers = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
    }
    response = requests.get(url,headers = headers)
    try:
        if response.status_code == 200:
            return response.text
        return None
    except RequestException:
        print("request error")
        return None
def parse_page(html):
    pattern = re.compile('<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star">(.*?)</p>'
    + '.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',re.S) #re.S使.能匹配任意字符
    items = pattern.findall(str(html))
    for item in items:
        yield {
            'top':item[0],
            'image_src':item[1],
            'name':item[2],
            'actor':item[3].strip()[3:] if len(item[3]) > 3 else '',
            'releasetime':item[4].strip()[5:],
            'score':item[5] + item[6]
        }
def write_to_file(content):
    with open('result.txt','a',encoding='utf-8') as f:
        f.write(json.dumps(content,ensure_ascii = False) + '\n')
        f.close()

def main(offset):
    url = "http://maoyan.com/board/4?offset=" + str(offset)
    html = get_page(url)
    for item in parse_page(html):
        print(item)
        write_to_file(item)

# 如需保證電影順序,則放棄使用多線程
# if __name__ == '__main__':
#     for i in range(10):
#         main(offset=i * 10)
#         time.sleep(1)

if __name__ == '__main__':
    pool = Pool()
    pool.map(main,[i*10 for i in range(10)])

本文中的代碼地址:https://github.com/grisse/Cra...

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息