一豁陆、抓取網(wǎng)頁(yè)源代碼
import matplotlib as mpl
mpl.use('agg')
%matplotlib inline
import requests
import re
import pandas as pd
import time
import seaborn as sns
sns.set()
mpl.rcParams['font.sans-serif']=[u'SimHei']
mpl.rcParams['axes.unicode_minus']=False
requests
是一個(gè)強(qiáng)大的模塊扭吁,可以幫我們模擬絕大多數(shù)的瀏覽器網(wǎng)絡(luò)請(qǐng)求端圈,這次我們使用它的get
方法來(lái)獲取網(wǎng)頁(yè)的源代碼。
歡迎大家關(guān)注我的個(gè)人博客【數(shù)洞】 【備用站】
def get_one_page(url, headers):
'''
抓取單個(gè)網(wǎng)頁(yè)的源碼
'''
# 添加headers參數(shù)是為了偽裝成瀏覽器魁莉,避免被反爬蟲(chóng)策略封禁
response = requests.get(url, headers=headers)
# 200意味著成功的請(qǐng)求
if response.status_code == 200:
return response.content.decode('utf-8')
return None
通過(guò)觀察睬涧,我們可以看到貓眼電影TOP100頁(yè)面的url地址是http://maoyan.com/board/4?offset=0
,其中0可以替換成10旗唁、20畦浓、……、90逆皮。這是因?yàn)門(mén)OP100榜單分了十頁(yè)宅粥,每頁(yè)十部電影,這個(gè)可替換的數(shù)字參數(shù)相當(dāng)于每頁(yè)的電影的第一部的序號(hào)电谣。這里的編號(hào)跟Python中的編號(hào)規(guī)則一致秽梅,從0開(kāi)始抹蚀。
# 設(shè)置貓眼電影TOP100的url
# 為了方便,我們使用列表推導(dǎo)式來(lái)實(shí)現(xiàn)url的列舉
urls = ['http://maoyan.com/board/4?offset={0}'.format(i) for i in range(0, 100, 10)]
# 用header來(lái)假裝自己是瀏覽器企垦,這一部分可以通過(guò)瀏覽器的檢查功能來(lái)找到环壤,不清楚的可以百度搜索一下,非常簡(jiǎn)單钞诡。
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
}
# 先把所有網(wǎng)頁(yè)源碼爬下來(lái)
data = []
for url in urls:
tmp = get_one_page(url, headers=headers)
if not tmp == None:
data.append(tmp)
time.sleep(0.5)
# 我們查看一下爬取的網(wǎng)頁(yè)數(shù)量是否符合預(yù)期
print('{0} pages crawled'.format(len(data)))
10 pages crawled
十個(gè)網(wǎng)頁(yè)郑现,符合預(yù)期,那接下來(lái)我們就應(yīng)該解析數(shù)據(jù)了荧降。
二接箫、解析網(wǎng)頁(yè)數(shù)據(jù)
Python中存在許多網(wǎng)頁(yè)解析庫(kù),比如使用bs4
中的BeautifulSoup
朵诫、通過(guò)lxml
使用xpath
辛友、使用pymysql
,這些都是常用且好用的方案剪返。而這次废累,我們要自討苦吃,通過(guò)re
模塊脱盲,使用正則表達(dá)式的方法來(lái)解析數(shù)據(jù)邑滨。
關(guān)于正則表達(dá)式的語(yǔ)法和規(guī)則,可以自行百度钱反。為了簡(jiǎn)單易懂掖看,我們可以把不同數(shù)據(jù)的解析拆分開(kāi)來(lái),通過(guò)多個(gè)正則表達(dá)式的解析來(lái)實(shí)現(xiàn)各個(gè)字段的數(shù)據(jù)提取诈铛。但這種辦法有一些缺點(diǎn)乙各,比如網(wǎng)站的源碼中對(duì)于缺失數(shù)據(jù)的處理不合預(yù)期時(shí)墨礁,就可能導(dǎo)致某些字段出現(xiàn)缺失數(shù)據(jù)幢竹,這樣不同字段的數(shù)據(jù)列表長(zhǎng)度就產(chǎn)生了差異,我們就無(wú)法簡(jiǎn)單地進(jìn)行合并了恩静。
事實(shí)上焕毫,針對(duì)這種列表式展示內(nèi)容的網(wǎng)頁(yè),針對(duì)每個(gè)條目驶乾,它的不同字段都是放在一起的邑飒,前后順序一般也是固定的。因此级乐,假如我們將每個(gè)條目的所有字段一起解析疙咸,就可以方便地應(yīng)對(duì)字段缺失的問(wèn)題了。
不過(guò)貓眼電影TOP100的榜單應(yīng)該是有小編進(jìn)行手動(dòng)維護(hù)的风科,所以數(shù)據(jù)比較規(guī)整撒轮,暫時(shí)不用考慮這個(gè)問(wèn)題乞旦。
# 利用正則表達(dá)式,解析電影名题山、主演兰粉、排名、上映時(shí)間顶瞳、分?jǐn)?shù)數(shù)據(jù)
# 使用re.compile將各個(gè)正則表達(dá)式封裝成正則表達(dá)式對(duì)象玖姑,方便后邊解析使用。re.S參數(shù)是為了讓'.'能匹配空格慨菱。
actor_pattern = re.compile('<p\sclass="star">\s*(.*?)\s*</p>', re.S)
title_pattern = re.compile('class="name".*?movieId.*?>(.*?)</a></p>', re.S)
index_pattern = re.compile('<dd>.*?board-index.*?>(\d+)</i>', re.S)
time_pattern = re.compile('<p\sclass="releasetime">(.*?)</p>', re.S)
score_pattern = re.compile('<p\sclass="score"><i\sclass="integer">(\d+)\.</i><i\sclass="fraction">(\d+)</i></p>', re.S)
# 使用列表來(lái)存儲(chǔ)數(shù)據(jù)
indexes = []
actors = []
titles = []
release_times = []
scores = []
# 循環(huán)解析十個(gè)網(wǎng)頁(yè)焰络,將解析出來(lái)的數(shù)據(jù)附加在對(duì)應(yīng)的列表中
for page in data:
indexes.extend(re.findall(index_pattern, page))
titles.extend(re.findall(title_pattern, page))
actors.extend(re.findall(actor_pattern, page))
release_times.extend(re.findall(time_pattern, page))
scores.extend(re.findall(score_pattern, page))
# 清洗主演、上映時(shí)間符喝、上映國(guó)家或地區(qū)舔琅、評(píng)分?jǐn)?shù)據(jù)
actors = [i.strip('主演:') for i in actors]
# 可以看到,上映地區(qū)的數(shù)據(jù)在上映時(shí)間后邊的括號(hào)里洲劣,有很多電影上映時(shí)間后邊沒(méi)有括號(hào)了备蚓,通過(guò)觀察我們發(fā)現(xiàn)這些都是中國(guó)大陸上映的電影,
# 那我們就將這些默認(rèn)缺失的部分補(bǔ)充為'中國(guó)'
locs = [i.strip('上映時(shí)間:')[10:].strip('()') if len(i.strip('上映時(shí)間:')) > 10 else '中國(guó)' for i in release_times]
# 我們把字符串中‘上映時(shí)間:’這些沒(méi)用的去掉囱稽,然后取十位郊尝,也就是'YYYY-mm-dd'的長(zhǎng)度,事實(shí)上這一步我們也可以在正則表達(dá)式中解決战惊,
# 比如用'\d'匹配數(shù)字等流昏,詳細(xì)的大家可以自己嘗試,這樣還可以解決數(shù)據(jù)格式不符合預(yù)期的問(wèn)題吞获。
# 事實(shí)上電影天空之城的上映時(shí)間的格式還真的跟其他的不一樣况凉,不過(guò)此次我們不考慮這個(gè)問(wèn)題
release_times = [i.strip('上映時(shí)間:')[:10] for i in release_times]
# 網(wǎng)頁(yè)里邊將分?jǐn)?shù)的個(gè)位數(shù)與小數(shù)用了不同的格式,所以解析的時(shí)候我們分開(kāi)提取了它們各拷,因此需要處理一下
scores = [int(i) + int(j)/10 for i, j in scores]
pandas
是Python中數(shù)據(jù)分析的一個(gè)神器刁绒,它的很多功能和用法都借鑒了R
語(yǔ)言。
這里我們就使用DataFrame來(lái)存儲(chǔ)并分析數(shù)據(jù)烤黍。
# 生成DataFrame
df = pd.DataFrame({
'rank': indexes,
'title': titles,
'actor': actors,
'release_time': release_times,
'score': scores,
'location': locs
})
# 修改列名
df = df[['rank', 'title', 'actor', 'score', 'location', 'release_time']]
# 保存到本地csv文件中
df.to_csv('./maoyan_top100_movie.csv', index=False)
# 展示一下數(shù)據(jù)
df.head()
<div>
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style>
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>rank</th>
<th>title</th>
<th>actor</th>
<th>score</th>
<th>location</th>
<th>release_time</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>1</td>
<td>霸王別姬</td>
<td>張國(guó)榮,張豐毅,鞏俐</td>
<td>9.6</td>
<td>中國(guó)</td>
<td>1993-01-01</td>
</tr>
<tr>
<th>1</th>
<td>2</td>
<td>羅馬假日</td>
<td>格利高里·派克,奧黛麗·赫本,埃迪·艾伯特</td>
<td>9.1</td>
<td>美國(guó)</td>
<td>1953-09-02</td>
</tr>
<tr>
<th>2</th>
<td>3</td>
<td>肖申克的救贖</td>
<td>蒂姆·羅賓斯,摩根·弗里曼,鮑勃·岡頓</td>
<td>9.5</td>
<td>美國(guó)</td>
<td>1994-10-14</td>
</tr>
<tr>
<th>3</th>
<td>4</td>
<td>這個(gè)殺手不太冷</td>
<td>讓·雷諾,加里·奧德曼,娜塔莉·波特曼</td>
<td>9.5</td>
<td>法國(guó)</td>
<td>1994-09-14</td>
</tr>
<tr>
<th>4</th>
<td>5</td>
<td>教父</td>
<td>馬龍·白蘭度,阿爾·帕西諾,詹姆斯·肯恩</td>
<td>9.3</td>
<td>美國(guó)</td>
<td>1972-03-24</td>
</tr>
</tbody>
</table>
</div>
三知市、數(shù)據(jù)分析
1. 上映時(shí)間分布
首先,我們看一下貓眼TOP100電影都是什么年頭的速蕊。
# 我們的上映日期是以字符串存儲(chǔ)的嫂丙,需要將上映年份解析出來(lái)
df['上映年份'] = df['release_time'].map(lambda x: int(x[:4]))
df['上映年份'].value_counts()
2011 9
2010 7
2013 6
1993 5
2012 5
1994 5
2008 5
2006 4
1998 4
2003 4
2002 4
2001 3
2000 3
1997 3
1999 3
2004 3
1992 3
1965 2
2009 2
2014 2
1954 1
1966 1
1957 1
2017 1
1953 1
1974 1
1940 1
1972 1
1995 1
1975 1
1984 1
1987 1
1988 1
1989 1
1990 1
2015 1
2007 1
1939 1
Name: 上映年份, dtype: int64
雖然我們能看到有不少電影集中分散在千禧年之后的某幾年,比如2010-2013年就占了100部電影中的27部规哲,但是這樣數(shù)據(jù)看起來(lái)還是太過(guò)分散跟啤,我們可以考慮以5年為一個(gè)區(qū)間將數(shù)據(jù)分布集中起來(lái)。
df['上映年份區(qū)間'] = pd.cut(df['上映年份'], bins=[1938, 1980, 1990, 1995, 2000, 2005, 2010, 2015, 2018])
df['上映年份區(qū)間'].value_counts().sort_index().plot(kind='bar')
可以看到,年頭近一些的電影還是更符合當(dāng)代人的口味隅肥,那么我們看看最古老和最新的電影分別是什么关顷。
df.iloc[df['上映年份'].idxmin()]
rank 10
title 亂世佳人
actor 費(fèi)雯·麗,克拉克·蓋博,奧利維婭·德哈維蘭
score 9.1
location 美國(guó)
release_time 1939-12-15
上映年份 1939
上映年份區(qū)間 (1938, 1980]
Name: 9, dtype: object
df.iloc[df['上映年份'].idxmax()]
rank 100
title 英雄本色
actor 狄龍,張國(guó)榮,周潤(rùn)發(fā)
score 9.2
location 中國(guó)
release_time 2017-11-17
上映年份 2017
上映年份區(qū)間 (2015, 2018]
Name: 99, dtype: object
可以看到,最古老的電影是1939年上映的由費(fèi)雯·麗主演的《亂世佳人》武福,鼎鼎大名议双,名不虛傳。數(shù)洞更感興趣的捉片,是最新的電影《英雄本色》平痰,這部電影面世三十多年之后,經(jīng)過(guò)4K技術(shù)的修復(fù)伍纫,在國(guó)內(nèi)正式上映宗雇,著實(shí)賺了不少忠實(shí)粉絲的眼淚。小馬哥不是一個(gè)角色莹规,而是一個(gè)時(shí)代赔蒲。
2. 上映地區(qū)分布
看完了上映時(shí)間情況,我們?cè)倏纯瓷嫌车貐^(qū)的信息良漱。
df['location'].value_counts().plot(kind='bar')
可以看到舞虱,大陸片和美國(guó)片最受大家歡迎,日本韓國(guó)也有一定受眾母市,素有浪漫之風(fēng)的法國(guó)矾兜、意大利緊隨其后,中國(guó)香港排名第七有些出乎意料患久,看來(lái)當(dāng)年港片的輝煌已經(jīng)一去不復(fù)返了椅寺。
3. 分?jǐn)?shù)情況
我們來(lái)看看這TOP100電影評(píng)分的分布情況如何:
df.groupby('score')['title'].count().sort_index().plot(kind='bar')
df.score.hist(bins=5)
看來(lái)高分電影還是很稀有的,即使在TOP100中蒋失,評(píng)分在9.5以上的僅有5部返帕。另外分?jǐn)?shù)越高電影越少這一現(xiàn)象也符合預(yù)期。
那接下來(lái)呢篙挽,我們看看哪個(gè)地區(qū)的電影評(píng)分更高:
import matplotlib.pyplot as plt
plt.figure(figsize=(20,8))
sns.boxplot(x='location', y='score', data=df)
可以看到荆萤,香港和意大利雖然量少,但整體風(fēng)評(píng)更好嫉髓,韓國(guó)電影相對(duì)來(lái)說(shuō)評(píng)價(jià)較差观腊。
那我們?cè)倏纯茨男┠觐^的電影評(píng)價(jià)更好:
plt.figure(figsize=(20,8))
sns.boxplot(x='上映年份區(qū)間', y='score', data=df)
看起來(lái)1995-2000年的電影質(zhì)量相當(dāng)不錯(cuò)
接下來(lái)我們綜合上映地區(qū)和年份來(lái)看看分?jǐn)?shù)的情況:
df_heat = df.groupby(['上映年份區(qū)間', 'location'])['score'].mean().reset_index().pivot('上映年份區(qū)間', 'location', 'score')
cmap = sns.diverging_palette(220, 10, as_cmap=True)
sns.heatmap(df_heat, center=8.8, annot=True, cmap=cmap, linewidths=.5)
df_heat = df.groupby(['上映年份區(qū)間', 'location'])['title'].count().reset_index().pivot('上映年份區(qū)間', 'location', 'title')
cmap = sns.diverging_palette(220, 10, as_cmap=True)
sns.heatmap(df_heat, center=1, annot=True, cmap=cmap, linewidths=.5)
嗯……由于總共只有100部電影,分不到這么多區(qū)間里還是太過(guò)稀疏算行,數(shù)據(jù)量較大的時(shí)候基本上就不會(huì)出現(xiàn)這種情況了。數(shù)據(jù)這么稀疏的情況下苫耸,我們看到的結(jié)果可能不太具有代表性州邢,不過(guò)在此我們就不糾結(jié)這個(gè)問(wèn)題。
df['rank'] = df['rank'].map(int)
sns.lmplot(x='score', y='rank', data=df)
榜單排名和評(píng)分大致呈反比,即分?jǐn)?shù)越高量淌,排名越靠前骗村,符合預(yù)期。
4. 演員情況
我們使用collections
中的defaultdict
來(lái)統(tǒng)計(jì)每個(gè)演員入榜的電影數(shù):
from collections import defaultdict
actor_movie_cnt = defaultdict(int)
for index, row in df.iterrows():
for actor in row['actor'].split(','):
actor_movie_cnt[actor] += 1
sorted(actor_movie_cnt.items(), key=lambda x: x[1], reverse=True)[:10]
[('張國(guó)榮', 6),
('周星馳', 4),
('梁朝偉', 4),
('鞏俐', 3),
('阿爾·帕西諾', 3),
('莫文蔚', 3),
('克里斯蒂安·貝爾', 3),
('布拉德·皮特', 3),
('加里·奧德曼', 2),
('娜塔莉·波特曼', 2)]
前三名分別是哥哥呀枢、星爺以及小編心目中最帥的男人之一——梁朝偉胚股,這三位小編都非常喜歡。
到此為止裙秋,我們就完成了貓眼TOP100的抓取琅拌,也進(jìn)行了簡(jiǎn)單的描述統(tǒng)計(jì)分析,下次我們?cè)倏紤]下其他的網(wǎng)頁(yè)解析工具的使用摘刑。