前面幾天介紹的都是博客園的內容,今天我們切換一下,了解一下大家都感興趣的信息,比如最近有啥電影是萬眾期待的?
貓眼電影是了解這些信息的好地方,在貓眼電影中有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格式的編碼,一直沒有搞清楚,否則就能得到總共想看該電影的人數了。
這個東西后面一定會解決的。