爬蟲實戰【4】Python獲取貓眼電影最受期待榜的50部電影


前面幾天介紹的都是博客園的內容,今天我們切換一下,了解一下大家都感興趣的信息,比如最近有啥電影是萬眾期待的?
貓眼電影是了解這些信息的好地方,在貓眼電影中有5個榜單,其中最受期待榜就是我們今天要爬取的對象。這個榜單的數據來源於貓眼電影庫,按照之前30天的想看總數量從高到低排列,取前50名。
我們先看一下這個表單中包含什么內容:
【插入圖片,6貓眼榜單示例】

具體的信息有”排名,電影海報,電影名稱,主演,上映時間“以及想看人數,今天我們主要關注前面5個信息的收集。
之前我們用正則表達式,在網頁源代碼中匹配了某一篇文章的標題,大家可能還有印象,這次我們還要用正則表達式來一次爬取多個內容。
另外,也嘗試一下requests庫。

第一步 如何獲取網頁的源碼?

我們先分析一下這個榜單頁面,跟之前博客園的大概是類似的。
url=http://maoyan.com/board/6?offset=0
上面是第一頁的榜單地址,我們一眼就關注到了offset這個值,毫無疑問,后面的頁面都是將offset改變就能獲取到了。
來看一下第二頁:
http://maoyan.com/board/6?offset=10
不一樣的地方,offset每次增加了10,而不是之前博客園中的1.
無所謂,都是小case。
來來來,我們使用requests來爬一下第一頁的源碼看看。

import requests
#初始的代碼
def get_html(url):
    response=requests.get(url)
    if response.status_code==200:
        html=response.content.decode('utf-8')
        return html
    else:
        return None

requests的get方法返回了一個response對象,我們根據這個response的狀態碼status_code就可以判斷是否返回正常,200一般是OK的。
然后要對返回的內容解碼,decode為utf-8的格式。
打印一下,看看得到什么結果。
【插入圖片,1顯示請求錯誤】

竟然失敗了,被禁止訪問了。
估計是貓眼設置了反爬蟲,不過應該比較容易解決,我們看一下get方法的請求頭信息。(在FireFox里面按F12,打開調試,點擊網絡,先把已經加載的內容刪除,刷新一下頁面,我們只看html格式的返回,如下圖所示。
【插入圖片,2user-agent】

我們先給requests添加一個user-agent的頭信息,嘗試一下能否獲取到源碼信息。

import requests
#改進后的代碼,插入headers
def get_html(url):
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                      'AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393'
    }
    response=requests.get(url,headers=headers)
    if response.status_code==200:
        html=response.content.decode('utf-8')
        return html
    else:
        return None

好了,成功了,獲取到了網頁源碼
【插入圖片,獲取源碼成功】

第二步 解析獲取的源碼

得到源碼后,就可以開始解析了,我們要得到的5個信息,包括排名、海報、名稱、主演和上映時間等信息。
【插入圖片 5某一電影標簽的內容】
所有的內容包含在一個dd標簽內。
我們想要用正則表達式來獲取這5個信息,就要用到分組的格式,詳見前面對正則表達式的介紹。

r'<dd.*?board-index.*?">(\d+)</i.*?data-src="(.*?)".*?<p class="name"><a.*?>(.*?)</a>.*?<p class="star">(.*?)</p.*?class="releasetime">(.*?)</p.*?</dd>'

這個pattern有點長,我們主要關注5個()里面的內容,這就是我們要獲取的5個信息。
使用re的findall方法來匹配整個源碼,會得到一個list,里面有這5個內容。
但是每一頁上有10個電影,假如每個電影一個item,那么我們會得到有10個item組成的一個list。
代碼如下:

def parse_html(html):
    #為什么一定要開啟非貪婪模式?
    pattern=re.compile('<dd.*?board-index.*?">(\d+)</i.*?data-src="(.*?)".*?<p class="name"><a.*?>(.*?)</a>.*?<p class="star">(.*?)</p.*?class="releasetime">(.*?)</p.*?</dd>',re.S)
    items=re.findall(pattern,html)
    result=[]
    for item in items:
        result.append({
            '排名':item[0],
            '海報':item[1],
            '名稱':item[2],
            '主演':item[3].strip()[3:],
            '上映時間':item[4].strip()[5:]
        })
    return result

尤其要注意的是,.*表示可以匹配任意數量的字符,這個匹配是貪婪的,我們要在后面加上一個?才能保證匹配到第一個符合的內容就結束。
一開始沒有注意,導致匹配失敗很多次。
另外,由於獲取主演的內容是這樣的"主演:XXXXX",所以要對list切片,把前面3個字符去掉。
上映時間也是同樣的道理。
這樣,對一個頁面的解析就完成了。

第三步 保存信息

之前我們嘗試過將信息保存在文本中,今天試一下json。
因為我們要保存的內容中有中文信息,所以在寫入的時候設置編碼為utf-8,同時在json的dump方法中設置ascii為False。

def save_one_page(offset_no):
    url=url_base+str(offset_no)
    html=get_html(url)
    items=parse_html(html)
    for item in items:
        print(item)
        with open('most wanted.json','a',encoding='utf-8') as f:
            json.dump(item,f,ensure_ascii=False)

第四步 開啟多進程

如果想要提高運行效率,我們可以開始多進程,尤其對於這種多頁、多條目的下載情況。當然我們目前的情況並不是十分要求,以備后面的情況。
Windows下,python的multiprocessing模塊,提供了一個Process類來代表一個進程對象。
創建子進程時,只需要傳入一個執行函數名和函數的參數,創建一個Process實例,用start()方法啟動,join方法可以等待子進程結束后在繼續往下運行。舉個栗子:

from multiprocessing import Process
import os

# 子進程要執行的代碼
def run_proc(name):
    print('Run child process %s (%s)...' % (name, os.getpid()))

if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    #target是要運行的函數名稱,args是傳入的參數,可以看出是一個元組,
    #如果只有一個參數,后面要加一個逗號
    p = Process(target=run_proc, args=('test',))
    print('Process will start.')
    p.start()
    p.join()
    print('Process end.')

如果要啟動大量的子進程,可以用進程池的方式批量創建子進程,這里的進程池就是multiprocessing模塊中的Pool類。
如果使用進程池的話,就不要使用start方法了,使用apply或者apply_async方法,async是
舉個例子:

from multiprocessing import Pool
import os, time, random

def long_time_task(name):
    print('Run task %s (%s)...' % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Pool()
    for i in range(5):
        p.apply_async(long_time_task, args=(i,))
    print('Waiting for all subprocesses done...')
    p.close()
    p.join()
    print('All subprocesses done.')

根據上面的說明,我們的代碼應該如下所示:

if __name__=='__main__':
    pool = Pool()
    for i in range(5):
        pool.apply_async(save_one_page,args=(i*10,))
    pool.close()
    pool.join()
    #map函數是python中的一種內置函數,用法后面再介紹
    #pool.map(save_one_page, [i * 10 for i in range(5)])

至此為止,我們的代碼就全部完成了。
附上效果圖:【插入圖片,結果】

未完成

但是仍然有一個內容,沒有達到我的目標。
【插入圖片,亂碼】

【插入圖片,對應數字】

&#x格式的編碼,一直沒有搞清楚,否則就能得到總共想看該電影的人數了。
這個東西后面一定會解決的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM