99999久久久久久亚洲,欧美人与禽猛交狂配,高清日韩av在线影院,一个人在线高清免费观看,啦啦啦在线视频免费观看www

熱線電話:13121318867

登錄
首頁(yè)精彩閱讀擺脫劇荒:教你用Python爬取豆瓣電影最新榜單
擺脫劇荒:教你用Python爬取豆瓣電影最新榜單
2019-09-02
收藏
擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

作者 | 吹牛Z

來(lái)源 | 數(shù)據(jù)不吹牛

【導(dǎo)讀】本文以豆瓣電影(非TOP250)為例,從數(shù)據(jù)爬取、清洗與分析三個(gè)維度入手,詳解和還原數(shù)據(jù)爬取到分析的全鏈路。閱讀全文大概需要5分鐘,想直接看結(jié)果或下載源碼+數(shù)據(jù)集的旁友可以空降到文末。

旁友,暑假,已經(jīng)過(guò)了一大半了。

這個(gè)遙遠(yuǎn)而炙熱的名詞,雖然和小Z這個(gè)上班狗已經(jīng)沒(méi)有任何關(guān)系,但在房間穿著褲衩,吹著空調(diào),吃著西瓜,看著電影,依然是假期最好的打開(kāi)方式?,F(xiàn)在褲衩、空調(diào)、西瓜都唾手可得,壓力全在電影這邊了。

關(guān)于電影推薦和排行,豆瓣是個(gè)好地方,只是電影TOP250排名實(shí)在是太經(jīng)典,經(jīng)典到有點(diǎn)老套了。

小Z想來(lái)點(diǎn)新花樣,于是按默認(rèn)的“評(píng)分最高”來(lái)排序,Emmm,結(jié)果好像比較小眾:

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

又按年代進(jìn)行篩選,發(fā)現(xiàn)返回的結(jié)果和預(yù)期差的更遠(yuǎn)了。

怎么辦捏?不如我們自己對(duì)豆瓣電影進(jìn)行更全面的爬取和分析,再DIY評(píng)分規(guī)則,結(jié)合電影上映年代做一個(gè)各年代TOP100電影排行榜。

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

數(shù)據(jù)爬取

1、網(wǎng)址規(guī)律探究

聽(tīng)說(shuō)看的人越多,評(píng)分越有說(shuō)服力,所以我們進(jìn)入導(dǎo)航頁(yè),選擇“標(biāo)記最多”。(雖然標(biāo)記的多并不完全等于看的多,但也差不多了)

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

要找到網(wǎng)址變化規(guī)律,常規(guī)的套路就是先右鍵“審查元素”,然后通過(guò)不斷的點(diǎn)擊“加載更多”刷新頁(yè)面的方式來(lái)找規(guī)律。

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

網(wǎng)址規(guī)律異常的簡(jiǎn)單,開(kāi)頭URL不變,每翻一頁(yè),start的數(shù)值增加20就OK了。

一頁(yè)是20部電影,開(kāi)頭我們立下的FLAG是要爬取9000部電影,也就是爬取450頁(yè)。

2、單頁(yè)解析+循環(huán)爬取

豆瓣灰常貼心,每一頁(yè)都是JSON格式存儲(chǔ)的規(guī)整數(shù)據(jù),爬取和清洗都省了不少事兒:

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

這里我們只需要偽裝一下headers里面的user-agent就可以愉快的爬取了:

headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'}

直接上單頁(yè)解析的代碼:

def parse_base_info(url,headers): html = requests.get(url,headers = headers) bs = json.loads(html.text) df = pd.DataFrame()for i in bs['data']: casts = i['casts'] #主演 cover = i['cover'] #海報(bào) directors = i['directors'] #導(dǎo)演 m_id = i['id'] #ID rate = i['rate'] #評(píng)分 star = i['star'] #標(biāo)記人數(shù) title = i['title'] #片名 url = i['url'] #網(wǎng)址 cache = pd.DataFrame({'主演':[casts],'海報(bào)':[cover],'導(dǎo)演':[directors],'ID':[m_id],'評(píng)分':[rate],'標(biāo)記':[star],'片名':[title],'網(wǎng)址':[url]}) df = pd.concat([df,cache])return df

然后我們寫一個(gè)循環(huán),構(gòu)造所需的450個(gè)基礎(chǔ)網(wǎng)址:

#你想爬取多少頁(yè),其實(shí)這里對(duì)應(yīng)著加載多少次def format_url(num): urls = [] base_url = 'https://movie.douban.com/j/new_search_subjects?sort=T&range=0,10&tags=%E7%94%B5%E5%BD%B1&start={}'for i in range(0,20 * num,20): url = base_url.format(i) urls.append(url)return urlsurls = format_url(450)

兩個(gè)湊一起,跑起來(lái):

result = pd.DataFrame()#看爬取了多少頁(yè)count = 1for url in urls:df = parse_base_info(url,headers = headers)result = pd.concat([result,df])time.sleep(random.random() + 2)print('I had crawled page of:%d' % count)count += 1

一個(gè)大號(hào)的功夫,包含電影ID、電影名稱、主演、導(dǎo)演、評(píng)分、標(biāo)記人數(shù)和具體網(wǎng)址的數(shù)據(jù)已經(jīng)爬好了:

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

下面,我們還想要批量訪問(wèn)每一部電影,拿到有關(guān)電影各星級(jí)評(píng)分占比等更豐富的信息,后續(xù)我們想結(jié)合評(píng)分分布來(lái)進(jìn)行排序。

3、單部電影詳情爬取

我們打開(kāi)單部電影的網(wǎng)址,取巧做法是直接右鍵,查看源代碼,看看我們想要的字段在不在源代碼中,畢竟,爬靜態(tài)的源代碼是最省力的。

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

電影名稱?在的!導(dǎo)演信息?在的!豆瓣評(píng)分?還是在的!一通CTRL+F搜索發(fā)現(xiàn),我們所有需要的字段,全部在源代碼中。那爬取起來(lái)就太簡(jiǎn)單了,這里我們用xpath來(lái)解析:

defparse_movie_info(url,headers = headers,ip = ''):if ip == '': html = requests.get(url,headers = headers)else: html = requests.get(url,headers = headers,proxies = ip) bs = etree.HTML(html.text)#片名 title = bs.xpath('//div[@id = "wrapper"]/div/h1/span')[0].text #上映時(shí)間 year = bs.xpath('//div[@id = "wrapper"]/div/h1/span')[1].text #電影類型 m_type = []for t in bs.xpath('//span[@property = "v:genre"]'): m_type.append(t.text) a = bs.xpath('//div[@id= "info"]')[0].xpath('string()')#片長(zhǎng) m_time =a[a.find('片長(zhǎng): ') + 4:a.find('分鐘\n')] #時(shí)長(zhǎng)#地區(qū) area = a[a.find('制片國(guó)家/地區(qū):') + 9:a.find('\n 語(yǔ)言')] #地區(qū)#評(píng)分人數(shù)try: people = bs.xpath('//a[@class = "rating_people"]/span')[0].text#評(píng)分分布 rating = {} rate_count = bs.xpath('//div[@class = "ratings-on-weight"]/div')for rate in rate_count: rating[rate.xpath('span/@title')[0]] = rate.xpath('span[@class = "rating_per"]')[0].textexcept: people = 'None' rating = {}#簡(jiǎn)介try: brief = bs.xpath('//span[@property = "v:summary"]')[0].text.strip('\n \\u3000\\u3000')except: brief = 'None'try: hot_comment = bs.xpath('//div[@id = "hot-comments"]/div/div/p/span')[0].textexcept: hot_comment = 'None' cache = pd.DataFrame({'片名':[title],'上映時(shí)間':[year],'電影類型':[m_type],'片長(zhǎng)':[m_time],'地區(qū)':[area],'評(píng)分人數(shù)':[people],'評(píng)分分布':[rating],'簡(jiǎn)介':[brief],'熱評(píng)':[hot_comment],'網(wǎng)址':[url]})return cache

第二步我們已經(jīng)拿到了9000部電影所有的網(wǎng)址,只需寫個(gè)循環(huán),批量訪問(wèn)就可以了。然鵝,盡管設(shè)置了訪問(wèn)時(shí)間間隔,爬取上千個(gè)頁(yè)面我們就會(huì)發(fā)現(xiàn),豆娘還是會(huì)把我們給BAN(禁)掉。

回憶一下,我們沒(méi)有登錄,不需要cookies驗(yàn)證,只是因?yàn)轭l繁的訪問(wèn)騷擾到了豆娘。那這個(gè)問(wèn)題還是比較好解決的,此處不留爺,換個(gè)IP就留爺。細(xì)心的朋友已經(jīng)發(fā)現(xiàn)了,上面針對(duì)單部電影的頁(yè)面解析,有一個(gè)默認(rèn)IP參數(shù),我們只需要在舊IP被禁后,傳入新的IP就可以了。

PS:代理IP如果展開(kāi)講篇幅太長(zhǎng),網(wǎng)上有許多免費(fèi)的IP代理(缺點(diǎn)是可用時(shí)間短,不穩(wěn)定)和付費(fèi)的IP代理(缺點(diǎn)是不免費(fèi))。另外,要強(qiáng)調(diào)一下這里我們傳入的IP長(zhǎng)這樣:{'https':'https://115.219.79.103:0000'}

movie_result = pd.DataFrame()ip = ''#這里構(gòu)建自己的IP池count2 = 1cw = 1for url,name in zip(result['網(wǎng)址'].values[6000:],result['片名'].values[6000:]):#for name,url in wrongs.items():try: cache = parse_movie_info(url,headers = headers,ip = ip) movie_result = pd.concat([movie_result,cache])#time.sleep(random.random()) print('我們爬取了第:%d部電影-------%s' % (count2,name)) count2 += 1except: print('滴滴滴滴滴,第{}次報(bào)錯(cuò)'.format(cw)) print('ip is:{}'.format(ip)) cw += 1 time.sleep(2)continue

電影頁(yè)面數(shù)據(jù)爬取結(jié)果如下:

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單
擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

數(shù)據(jù)清洗

1、基本信息表和電影內(nèi)容表合并

base_info表里面是我們批量抓取的電影基本信息,movie_info則是我們進(jìn)入每一部電影,獲取到的感興趣字段匯總,后面的分析是需要依賴兩張表進(jìn)行的,所以我們合并之:

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

2、電影年份數(shù)據(jù)清洗

我們發(fā)現(xiàn)之前爬取的上映時(shí)間數(shù)據(jù)不夠規(guī)整,前面都帶了一個(gè)“-”:

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

要把前面多余的符號(hào)去掉,但發(fā)現(xiàn)無(wú)論怎么用str.replace返回的都是Nan,原來(lái)這里pandas把所有數(shù)字默認(rèn)成負(fù)的,所以只需要把這一列所有數(shù)字乘-1即可:

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

3、評(píng)分分布規(guī)整

最終我們是希望能夠把電影整體評(píng)分(如某電影8.9分)和不同評(píng)分等級(jí)(5星的占比70%)結(jié)合起來(lái)分析的。而剛才爬取評(píng)分?jǐn)?shù)據(jù)的時(shí)候,為了偷懶,用的是一個(gè)字典把各評(píng)分等級(jí)和對(duì)應(yīng)的占比給包起來(lái)了,然鵝,pandas默認(rèn)把他當(dāng)成了字符串,不能直接當(dāng)做字典處理:

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

靈光一閃?這種字典形式的字符串,用JSON解析一下不就變字典了?HAVE A TRY:

結(jié)果,瘋狂報(bào)錯(cuò):

報(bào)錯(cuò)貌似在提示我們是最外圍的引號(hào)錯(cuò)誤導(dǎo)致了問(wèn)題,目前我們用的是雙引號(hào)("{'a':1}")難道只能用單引號(hào)('{'a':1}')?先試試吧:

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

報(bào)錯(cuò)解決了。接下來(lái),我們把字典形式的評(píng)分拆成多列,例如每個(gè)星級(jí)對(duì)應(yīng)一列,且百分比的格式變成數(shù)值型的,寫個(gè)循環(huán)函數(shù),用apply應(yīng)用一下即可:

#把單列字典的評(píng)分分布轉(zhuǎn)化成分開(kāi)的5列,且每一列是數(shù)值型的def get_rate(x,types):try:return float(x[types].strip('%'))except:passmovie_combine['5星'] = movie_combine['format_評(píng)分'].apply(get_rate,types = '力薦')movie_combine['4星'] = movie_combine['format_評(píng)分'].apply(get_rate,types = '推薦')movie_combine['3星'] = movie_combine['format_評(píng)分'].apply(get_rate,types = '還行')movie_combine['2星'] = movie_combine['format_評(píng)分'].apply(get_rate,types = '較差')movie_combine['1星'] = movie_combine['format_評(píng)分'].apply(get_rate,types = '很差')

現(xiàn)在我們的數(shù)據(jù)長(zhǎng)這樣的:

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

OK,清洗到此告一段落。

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

數(shù)據(jù)分析

大家還記得開(kāi)頭的FLAG嗎?我們要制作各年代TOP100電影排行榜。所以直接按照年代劃分電影,然后按照電影評(píng)分排個(gè)序不就完事了!

然鵝這聽(tīng)起來(lái)有點(diǎn)話糙理也糙。如果只按照電影的總的評(píng)分來(lái)排序,會(huì)忽視掉內(nèi)部評(píng)分細(xì)節(jié)的差異性,舉個(gè)例子,搏擊俱樂(lè)部:

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

總評(píng)分9.0分,打出5星好評(píng)的占比60.9%,4星的有30.5%。

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

同為9分佳作,給美麗心靈打出5星好評(píng)的有56.0%,和搏擊俱樂(lè)部相比少了4.9%,而4星的人數(shù)則高出了6%??梢圆回?fù)責(zé)任的做一個(gè)概括:兩部都是9分經(jīng)典,但觀眾給搏擊俱樂(lè)部的5星傾向要高于美麗心靈。

GET到這個(gè)點(diǎn),我們就可以對(duì)電影評(píng)分排序制定一個(gè)簡(jiǎn)單的規(guī)則:先按照總評(píng)分排序,然后再對(duì)比5星人數(shù)占比,如果一樣就對(duì)比4星,以此類推。這個(gè)評(píng)分排序邏輯用PYTHON做起來(lái)不要太簡(jiǎn)單,一行代碼就搞定:

#按照總評(píng)分,5星評(píng)分人數(shù)占比,4星占比,3星..依次類推movie_combine.sort_values(['評(píng)分','5星','4星','3星','2星','1星'],ascending = False,inplace = True)

但是仔細(xì)看排序結(jié)果,我們會(huì)發(fā)現(xiàn)這樣排序的一些小瑕疵,一些高分電影其實(shí)是比較小眾的,比如“劇院魅影:25周年紀(jì)念演出”和“悲慘世界:25周年紀(jì)念演唱會(huì)”等。

而我們想要找的,是人民群眾所喜聞樂(lè)見(jiàn)的電影排名,這里只有通過(guò)評(píng)分人數(shù)來(lái)代表人民的數(shù)量,我們先看一看所有電影的評(píng)分人數(shù)分布:

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

評(píng)分人數(shù)跨度極大,為了減少極值對(duì)于平均的影響,就讓中位數(shù)來(lái)衡量人民群眾是否喜聞樂(lè)見(jiàn),所以我們只留下大于中位數(shù)的評(píng)分。

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

接著,看看歷年電影數(shù)量分布情況:

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

直到2000年初,篩選后的電影年上映數(shù)才逼近200,更早時(shí)期的電影好像20年加起來(lái)還不到100部。為了讓結(jié)果更加直觀,我們來(lái)按年代統(tǒng)計(jì)電影的上映時(shí)間。這里涉及到給每部電影上映時(shí)間進(jìn)行歸類,有點(diǎn)棘手啊...

絞盡腦細(xì)胞,終于找到了一個(gè)比較討巧的辦法,先構(gòu)造年代標(biāo)簽,再借用cut函數(shù)按十年的間隔切分上映時(shí)間,最后把標(biāo)簽傳入?yún)?shù)。

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

得勒!數(shù)據(jù)直觀的反映出各年代上映量,20世紀(jì)80年代前真的是少得可憐??吹竭@里,不由想到我們最開(kāi)始立的那個(gè)“制作年代TOP100榜單”的FLAG,因?yàn)樵缙陔娪傲康呢毞Γ峭耆静蛔∧_的了。

不慌,一個(gè)優(yōu)秀的數(shù)據(jù)分析師,一定是本著具體問(wèn)題具體分析的精神來(lái)調(diào)整FLAG的:

基于年代上映量數(shù)據(jù),我們從20世紀(jì)30年代開(kāi)始制作排名;為了避免有些年代電影過(guò)少,優(yōu)化成各年代TOP 10%的電影推薦;同時(shí),為了避免近年電影過(guò)多,每個(gè)年代推薦的上限數(shù)不超過(guò)100部。

看到這三個(gè)條件,連一向自傲的潘大師(pandas)都不禁長(zhǎng)嘆了口氣。然鵝大師之所以是大師,就是因?yàn)樵谒劾餂](méi)有什么是不可能的。思考1分鐘后,確定了靈活篩選的套路:

final_rank = pd.DataFrame()for century,count in zip(century_f.index,century_f.values): f1 = movie_f2.loc[movie_f['年代'] == century,:] #1000部以下的,取TOP10% if count < 1000: return_num = int(count * 0.1) #1000部以上的,取前100部 else: return_num = 100 f2 = f1.iloc[:return_num,:] final_rank = pd.concat([final_rank,f2])

根據(jù)上一步構(gòu)造的century_f變量,結(jié)合每個(gè)年代上映電影量,不足1000部的篩選前10%,超過(guò)1000部的只篩選前100部,結(jié)果,就呼之而出了。

在附上代碼和榜單之前,我預(yù)感到大部分旁友是和我一樣懶的(不會(huì)仔細(xì)看榜單),所以先整理出各年代TOP5電影(有些年代不足TOP5),做一個(gè)精華版的歷史電影排行榜奉上:

擺脫劇荒:教你用Python爬取豆瓣電影最新榜單

從峰回路轉(zhuǎn)、結(jié)尾讓人大呼牛逼的《控方證人》,到為無(wú)罪真理而辯的《十二怒漢》,再到家庭為重不怒自威的《教父》系列、重新詮釋希望和堅(jiān)韌的《肖申克的救贖》以及將勵(lì)志提升到新高度的《阿甘正傳》(小Z閱片尚淺,榜單上只看過(guò)這些)。

每一部好的電影,都是一塊從高空墜落的石頭,它總能在人們的心湖上激起水花和漣漪,引起人們對(duì)生活、社會(huì)以及人性的思考。而爛片,就是從高空墜落的空礦泉水瓶,它墜勢(shì)洶洶,但最終只會(huì)浮在水面,讓看過(guò)的人心存芥蒂,感覺(jué)靈魂受到污染。

有了新的電影排名榜單,再也不用擔(dān)心劇荒了。

數(shù)據(jù)分析咨詢請(qǐng)掃描二維碼

若不方便掃碼,搜微信號(hào):CDAshujufenxi

數(shù)據(jù)分析師資訊
更多

OK
客服在線
立即咨詢
客服在線
立即咨詢
') } function initGt() { var handler = function (captchaObj) { captchaObj.appendTo('#captcha'); captchaObj.onReady(function () { $("#wait").hide(); }).onSuccess(function(){ $('.getcheckcode').removeClass('dis'); $('.getcheckcode').trigger('click'); }); window.captchaObj = captchaObj; }; $('#captcha').show(); $.ajax({ url: "/login/gtstart?t=" + (new Date()).getTime(), // 加隨機(jī)數(shù)防止緩存 type: "get", dataType: "json", success: function (data) { $('#text').hide(); $('#wait').show(); // 調(diào)用 initGeetest 進(jìn)行初始化 // 參數(shù)1:配置參數(shù) // 參數(shù)2:回調(diào),回調(diào)的第一個(gè)參數(shù)驗(yàn)證碼對(duì)象,之后可以使用它調(diào)用相應(yīng)的接口 initGeetest({ // 以下 4 個(gè)配置參數(shù)為必須,不能缺少 gt: data.gt, challenge: data.challenge, offline: !data.success, // 表示用戶后臺(tái)檢測(cè)極驗(yàn)服務(wù)器是否宕機(jī) new_captcha: data.new_captcha, // 用于宕機(jī)時(shí)表示是新驗(yàn)證碼的宕機(jī) product: "float", // 產(chǎn)品形式,包括:float,popup width: "280px", https: true // 更多配置參數(shù)說(shuō)明請(qǐng)參見(jiàn):http://docs.geetest.com/install/client/web-front/ }, handler); } }); } function codeCutdown() { if(_wait == 0){ //倒計(jì)時(shí)完成 $(".getcheckcode").removeClass('dis').html("重新獲取"); }else{ $(".getcheckcode").addClass('dis').html("重新獲取("+_wait+"s)"); _wait--; setTimeout(function () { codeCutdown(); },1000); } } function inputValidate(ele,telInput) { var oInput = ele; var inputVal = oInput.val(); var oType = ele.attr('data-type'); var oEtag = $('#etag').val(); var oErr = oInput.closest('.form_box').next('.err_txt'); var empTxt = '請(qǐng)輸入'+oInput.attr('placeholder')+'!'; var errTxt = '請(qǐng)輸入正確的'+oInput.attr('placeholder')+'!'; var pattern; if(inputVal==""){ if(!telInput){ errFun(oErr,empTxt); } return false; }else { switch (oType){ case 'login_mobile': pattern = /^1[3456789]\d{9}$/; if(inputVal.length==11) { $.ajax({ url: '/login/checkmobile', type: "post", dataType: "json", data: { mobile: inputVal, etag: oEtag, page_ur: window.location.href, page_referer: document.referrer }, success: function (data) { } }); } break; case 'login_yzm': pattern = /^\d{6}$/; break; } if(oType=='login_mobile'){ } if(!!validateFun(pattern,inputVal)){ errFun(oErr,'') if(telInput){ $('.getcheckcode').removeClass('dis'); } }else { if(!telInput) { errFun(oErr, errTxt); }else { $('.getcheckcode').addClass('dis'); } return false; } } return true; } function errFun(obj,msg) { obj.html(msg); if(msg==''){ $('.login_submit').removeClass('dis'); }else { $('.login_submit').addClass('dis'); } } function validateFun(pat,val) { return pat.test(val); }