實踐案例:用 Python 爬取分析每日票房數(shù)據(jù)

作者:小李子,某外企分析師,主要從事IT行業(yè),但個人非常喜歡電影市場分析,所以經(jīng)常會寫一些電影領(lǐng)域的文章。
博客:http://blog.sina.com.cn/leonmovie
不知不覺又過了一年,揮別2019,讓我們擁抱這全新的2020,祝各位新年快樂!
最近在處理一些和有關(guān)電影的工作,需要用到一些北美電影票房數(shù)據(jù),而這部分?jǐn)?shù)據(jù)最權(quán)威的網(wǎng)站當(dāng)屬Box Office Mojo(以下簡稱BOM),于是就上去查看了一下。估計經(jīng)常關(guān)注這個網(wǎng)站的盆友們都知道,這個網(wǎng)站最近剛剛進(jìn)行了改版,網(wǎng)頁排版全面更新,還專門針對移動設(shè)備進(jìn)行了優(yōu)化(以前的網(wǎng)站頁面只有電腦版的),頁面雖然好看了不少,但卻少了很多數(shù)據(jù),之前的網(wǎng)站幾乎所有數(shù)據(jù)都能查到,而現(xiàn)在則只能查到部分?jǐn)?shù)據(jù),有些數(shù)據(jù)則要到BOM Pro版才能查到,而這個服務(wù)是收費的。為了更好地使用數(shù)據(jù),還想不花錢,那就只有自己動手豐衣足食,所以筆者就自己寫了個Python爬蟲,爬取了過去多年的票房數(shù)據(jù)。以下就以“北美票房每日票房數(shù)據(jù)”為例,介紹一下如何爬取,其他票房數(shù)據(jù)類似,只需修改少數(shù)代碼即可。

圖一 要抓取的部分網(wǎng)頁的截圖
這個爬蟲程序完全采用Python語言完成,使用軟件為Anaconda 2019.10版(這個目前是最新版的,理論上其包含的各種Python庫也是最新的或接近最新的,所以下面的爬蟲程序在部分老版軟件上可能會出問題,如有問題請及時更新)。爬蟲程序主要包括兩部分:爬取并存儲數(shù)據(jù),以及根據(jù)數(shù)據(jù)簡單繪制圖片。下面就一一講解一下。
一、爬取和存儲數(shù)據(jù)
首先把需要的包都導(dǎo)入進(jìn)來。
import?requests
import?pandas?as?pd
import?time
import?matplotlib.pyplot?as?plt
import?matplotlib.dates?as?mdate
import?pylab?as?mpl??#?導(dǎo)入中文字體,避免顯示亂碼
這個是我們要用到的每日票房的URL,中間的%s是一會兒要替換的年份urltemplate?=?r'https://www.boxofficemojo.com/daily/%s/?view=year'?
這個是數(shù)據(jù)保存的地方,放在了桌面的一個Excel文檔中,因為數(shù)據(jù)很少,所以根本用不到數(shù)據(jù)庫,Excel足以,當(dāng)然這里也可以用CSV格式。這里我的路徑中包含中文,使用時沒有問題,如果大家出現(xiàn)問題,最好使用英文路徑。
fileLoc?=?r'C:\Users\leon\Desktop\BoxOffice\Box?Office?Mojo票房爬蟲\Daily-每日\daily-data.xlsx'
這個是爬蟲頭部,防止網(wǎng)站的反爬機(jī)制。headers?=?{'User-Agent':?'Mozilla/5.0?(Windows?NT?6.1;?WOW64)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/69.0.3497.100?Safari/537.36'}
下面是爬蟲主體部分,這里有三點要說明,一是mode='a'這里,這個是0.25.1版pandas以后才有的功能,之前的老版本沒有這個功能;二是,不知道是不是我的網(wǎng)絡(luò)有問題,在爬取過程中有掉線的現(xiàn)象出現(xiàn),所以在這里用了requests.ConnectionError來處理掉線問題;三是,用了一個小竅門,如果直接用pd.read_html(url)也可以讀取網(wǎng)頁中的數(shù)據(jù),但這里先用requests讀取網(wǎng)頁,再把requests讀取的網(wǎng)頁代碼放入pd.read_html中,這樣既可避免網(wǎng)站的反爬蟲機(jī)制,也可以加快讀取速度,因為pd.read_html直接讀取網(wǎng)頁實在太慢了。
def?scraper(file,?headers,?urltemp,?year_start,?year_end):
????writer?=?pd.ExcelWriter(file,?engine='openpyxl',?mode='a')?#?筆者用的文件是xlsx類型,所以這里要指定engine='openpyxl',如果是xls類型,則不用
????for?i?in?range(year_start,?year_end+1):
????????url?=?urltemp?%?i
????????try:
????????????r?=?requests.get(url,?headers=headers)
????????????if?r.status_code?==?200:
????????????????source_code?=?r.text
????????????????df?=?pd.read_html(source_code)
????????????????df?=?df[0]
????????????????df.to_excel(writer,?sheet_name=str(i),?index=False)
????????????????time.sleep(3)#?稍微放慢一下速度,別把人家網(wǎng)站累壞了
????????except?requests.ConnectionError:
????????????print('Can?not?get?access?to?the?%s?year?daily?data?now'?%?i)
????????????return
????writer.save()
????writer.close()
scraper(fileLoc,?headers,?urltemplate,?1977,?2019)
因為網(wǎng)站只提供到最早1977年的數(shù)據(jù),所以就把1977年到2019年數(shù)據(jù)都給抓下來。

圖二 抓取的部分?jǐn)?shù)據(jù)的截圖
二、根據(jù)數(shù)據(jù)簡單繪圖
下面這個str_to_datetime函數(shù),是除掉數(shù)據(jù)Date列中一些不必要的文字,比如有些數(shù)據(jù)帶有“New Year’s Eve”字樣,要把這些東西去掉
def?str_to_datetime(x):
????if?len(x)?>?14:
????????temp?=?x.split('2019')
????????x?=?temp[0]+'2019'
????return?x
這個str_to_num函數(shù)是把“Top 10 Gross”列的數(shù)據(jù)都轉(zhuǎn)換成數(shù)值,因為這些數(shù)據(jù)從Excel讀取到pandas后,都是string格式的數(shù)據(jù),要轉(zhuǎn)換成數(shù)值格式
def?str_to_num(x):
????x?=?x.replace('$',?'')
????x?=?x.replace(',',?'')
????x?=?int(x)
????return?x
在這里我們要做一個“2019年每日票房數(shù)據(jù)的線形圖”,所以要在剛才抓取的文件中讀取相應(yīng)數(shù)據(jù),并簡單處理一下
table?=?pd.read_excel(fileLoc,?sheet_name='2019')
data?=?table[['Date',?'Top?10?Gross']]
data['Date']?=?data['Date'].apply(str_to_datetime)
data['Top?10?Gross']?=?data['Top?10?Gross'].apply(str_to_num)
設(shè)置x軸和y軸的數(shù)據(jù),x軸是時間數(shù)據(jù),y軸是票房數(shù)據(jù),其值太大,所以改小點,方便作圖
x?=?pd.to_datetime(data['Date'])
y?=?data['Top?10?Gross']/1000000
找出票房數(shù)據(jù)中最大的那個值和其在序列y中的位置,然后找出對應(yīng)x序列的位置,也就是對應(yīng)的哪一天
max_loc?=?y.idxmax()
max_y?=?y.max()
max_date?=?x.loc[max_loc]
設(shè)置相關(guān)參數(shù)
mpl.rcParams['font.sans-serif']?=?['SimHei']??#?設(shè)置為黑體字
fig?=?plt.figure(figsize=(16,?6.5))
ax?=?fig.add_subplot(111)??#?本例的figure中只包含一個圖表
ax.set_ylim([0,?200])
plt.tick_params(labelsize=13)
下面這行代碼是設(shè)置x軸為時間格式,這點很重要,否則x軸顯示的將是類似于‘796366’這樣的轉(zhuǎn)碼后的數(shù)字格式
ax.xaxis.set_major_formatter(mdate.DateFormatter('%Y-%m-%d'))
plt.xticks(pd.date_range(x[len(x)-1],?x[0],?freq='M'),?rotation=90)
text?=?r'票房最高的一天是%s,其票房為%.2f億'?%?(max_date.date(),?max_y/100)
plt.annotate(text,?xy=(max_date,?max_y),?fontsize=14,?\
?????????????xytext=(max_date+pd.Timedelta(days=10),?max_y+10),?\
?????????????arrowprops=dict(arrowstyle="->",?connectionstyle="arc3"),?\
?????????????xycoords='data')
plt.ylabel('票房/百萬美元',?fontdict={'size':14})
plt.plot(x,?y)
完成后這個圖片效果如下

圖三 2019年北美票房每日數(shù)據(jù)圖
三、結(jié)語
上面這個爬蟲程序比較簡單,并沒有用到數(shù)據(jù)庫、多線程等復(fù)雜技術(shù),我們更多地應(yīng)該從所得到的數(shù)據(jù)中來挖掘更多的價值,筆者接下來會從這些數(shù)據(jù)中來分析一下好萊塢電影行業(yè)過去一年的發(fā)展,屆時會分享給大家,敬請期待。
