
作者 | 吹牛Z
來源 | 數據不吹牛
【導讀】本文以豆瓣電影(非TOP250)為例,從數據爬取、清洗與分析三個維度入手,詳解和還原數據爬取到分析的全鏈路。閱讀全文大概需要5分鐘,想直接看結果或下載源碼+數據集的旁友可以空降到文末。
旁友,暑假,已經過了一大半了。
這個遙遠而炙熱的名詞,雖然和小Z這個上班狗已經沒有任何關系,但在房間穿著褲衩,吹著空調,吃著西瓜,看著電影,依然是假期最好的打開方式?,F在褲衩、空調、西瓜都唾手可得,壓力全在電影這邊了。
關于電影推薦和排行,豆瓣是個好地方,只是電影TOP250排名實在是太經典,經典到有點老套了。
小Z想來點新花樣,于是按默認的“評分最高”來排序,Emmm,結果好像比較小眾:
又按年代進行篩選,發現返回的結果和預期差的更遠了。
怎么辦捏?不如我們自己對豆瓣電影進行更全面的爬取和分析,再DIY評分規則,結合電影上映年代做一個各年代TOP100電影排行榜。
數據爬取
1、網址規律探究
聽說看的人越多,評分越有說服力,所以我們進入導航頁,選擇“標記最多”。(雖然標記的多并不完全等于看的多,但也差不多了)
要找到網址變化規律,常規的套路就是先右鍵“審查元素”,然后通過不斷的點擊“加載更多”刷新頁面的方式來找規律。
網址規律異常的簡單,開頭URL不變,每翻一頁,start的數值增加20就OK了。
一頁是20部電影,開頭我們立下的FLAG是要爬取9000部電影,也就是爬取450頁。
2、單頁解析+循環爬取
豆瓣灰常貼心,每一頁都是JSON格式存儲的規整數據,爬取和清洗都省了不少事兒:
這里我們只需要偽裝一下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'}
直接上單頁解析的代碼:
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'] #海報 directors = i['directors'] #導演 m_id = i['id'] #ID rate = i['rate'] #評分 star = i['star'] #標記人數 title = i['title'] #片名 url = i['url'] #網址 cache = pd.DataFrame({'主演':[casts],'海報':[cover],'導演':[directors],'ID':[m_id],'評分':[rate],'標記':[star],'片名':[title],'網址':[url]}) df = pd.concat([df,cache])return df
然后我們寫一個循環,構造所需的450個基礎網址:
#你想爬取多少頁,其實這里對應著加載多少次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)
兩個湊一起,跑起來:
result = pd.DataFrame()#看爬取了多少頁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
一個大號的功夫,包含電影ID、電影名稱、主演、導演、評分、標記人數和具體網址的數據已經爬好了:
下面,我們還想要批量訪問每一部電影,拿到有關電影各星級評分占比等更豐富的信息,后續我們想結合評分分布來進行排序。
3、單部電影詳情爬取
我們打開單部電影的網址,取巧做法是直接右鍵,查看源代碼,看看我們想要的字段在不在源代碼中,畢竟,爬靜態的源代碼是最省力的。
電影名稱?在的!導演信息?在的!豆瓣評分?還是在的!一通CTRL+F搜索發現,我們所有需要的字段,全部在源代碼中。那爬取起來就太簡單了,這里我們用xpath來解析:
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 #上映時間 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()')#片長 m_time =a[a.find('片長: ') + 4:a.find('分鐘\n')] #時長#地區 area = a[a.find('制片國家/地區:') + 9:a.find('\n 語言')] #地區#評分人數try: people = bs.xpath('//a[@class = "rating_people"]/span')[0].text#評分分布 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 = {}#簡介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],'上映時間':[year],'電影類型':[m_type],'片長':[m_time],'地區':[area],'評分人數':[people],'評分分布':[rating],'簡介':[brief],'熱評':[hot_comment],'網址':[url]})return cache
第二步我們已經拿到了9000部電影所有的網址,只需寫個循環,批量訪問就可以了。然鵝,盡管設置了訪問時間間隔,爬取上千個頁面我們就會發現,豆娘還是會把我們給BAN(禁)掉。
回憶一下,我們沒有登錄,不需要cookies驗證,只是因為頻繁的訪問騷擾到了豆娘。那這個問題還是比較好解決的,此處不留爺,換個IP就留爺。細心的朋友已經發現了,上面針對單部電影的頁面解析,有一個默認IP參數,我們只需要在舊IP被禁后,傳入新的IP就可以了。
PS:代理IP如果展開講篇幅太長,網上有許多免費的IP代理(缺點是可用時間短,不穩定)和付費的IP代理(缺點是不免費)。另外,要強調一下這里我們傳入的IP長這樣:{'https':'https://115.219.79.103:0000'}
movie_result = pd.DataFrame()ip = ''#這里構建自己的IP池count2 = 1cw = 1for url,name in zip(result['網址'].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('滴滴滴滴滴,第{}次報錯'.format(cw)) print('ip is:{}'.format(ip)) cw += 1 time.sleep(2)continue
電影頁面數據爬取結果如下:
1、基本信息表和電影內容表合并
base_info表里面是我們批量抓取的電影基本信息,movie_info則是我們進入每一部電影,獲取到的感興趣字段匯總,后面的分析是需要依賴兩張表進行的,所以我們合并之:
2、電影年份數據清洗
我們發現之前爬取的上映時間數據不夠規整,前面都帶了一個“-”:
要把前面多余的符號去掉,但發現無論怎么用str.replace返回的都是Nan,原來這里pandas把所有數字默認成負的,所以只需要把這一列所有數字乘-1即可:
3、評分分布規整
最終我們是希望能夠把電影整體評分(如某電影8.9分)和不同評分等級(5星的占比70%)結合起來分析的。而剛才爬取評分數據的時候,為了偷懶,用的是一個字典把各評分等級和對應的占比給包起來了,然鵝,pandas默認把他當成了字符串,不能直接當做字典處理:
靈光一閃?這種字典形式的字符串,用JSON解析一下不就變字典了?HAVE A TRY:
結果,瘋狂報錯:
報錯貌似在提示我們是最外圍的引號錯誤導致了問題,目前我們用的是雙引號("{'a':1}")難道只能用單引號('{'a':1}')?先試試吧:
報錯解決了。接下來,我們把字典形式的評分拆成多列,例如每個星級對應一列,且百分比的格式變成數值型的,寫個循環函數,用apply應用一下即可:
#把單列字典的評分分布轉化成分開的5列,且每一列是數值型的def get_rate(x,types):try:return float(x[types].strip('%'))except:passmovie_combine['5星'] = movie_combine['format_評分'].apply(get_rate,types = '力薦')movie_combine['4星'] = movie_combine['format_評分'].apply(get_rate,types = '推薦')movie_combine['3星'] = movie_combine['format_評分'].apply(get_rate,types = '還行')movie_combine['2星'] = movie_combine['format_評分'].apply(get_rate,types = '較差')movie_combine['1星'] = movie_combine['format_評分'].apply(get_rate,types = '很差')
現在我們的數據長這樣的:
OK,清洗到此告一段落。
數據分析
大家還記得開頭的FLAG嗎?我們要制作各年代TOP100電影排行榜。所以直接按照年代劃分電影,然后按照電影評分排個序不就完事了!
然鵝這聽起來有點話糙理也糙。如果只按照電影的總的評分來排序,會忽視掉內部評分細節的差異性,舉個例子,搏擊俱樂部:
總評分9.0分,打出5星好評的占比60.9%,4星的有30.5%。
同為9分佳作,給美麗心靈打出5星好評的有56.0%,和搏擊俱樂部相比少了4.9%,而4星的人數則高出了6%??梢圆回撠熑蔚淖鲆粋€概括:兩部都是9分經典,但觀眾給搏擊俱樂部的5星傾向要高于美麗心靈。
GET到這個點,我們就可以對電影評分排序制定一個簡單的規則:先按照總評分排序,然后再對比5星人數占比,如果一樣就對比4星,以此類推。這個評分排序邏輯用PYTHON做起來不要太簡單,一行代碼就搞定:
#按照總評分,5星評分人數占比,4星占比,3星..依次類推movie_combine.sort_values(['評分','5星','4星','3星','2星','1星'],ascending = False,inplace = True)
但是仔細看排序結果,我們會發現這樣排序的一些小瑕疵,一些高分電影其實是比較小眾的,比如“劇院魅影:25周年紀念演出”和“悲慘世界:25周年紀念演唱會”等。
而我們想要找的,是人民群眾所喜聞樂見的電影排名,這里只有通過評分人數來代表人民的數量,我們先看一看所有電影的評分人數分布:
評分人數跨度極大,為了減少極值對于平均的影響,就讓中位數來衡量人民群眾是否喜聞樂見,所以我們只留下大于中位數的評分。
接著,看看歷年電影數量分布情況:
直到2000年初,篩選后的電影年上映數才逼近200,更早時期的電影好像20年加起來還不到100部。為了讓結果更加直觀,我們來按年代統計電影的上映時間。這里涉及到給每部電影上映時間進行歸類,有點棘手啊...
絞盡腦細胞,終于找到了一個比較討巧的辦法,先構造年代標簽,再借用cut函數按十年的間隔切分上映時間,最后把標簽傳入參數。
得勒!數據直觀的反映出各年代上映量,20世紀80年代前真的是少得可憐??吹竭@里,不由想到我們最開始立的那個“制作年代TOP100榜單”的FLAG,因為早期電影量的貧乏,是完全站不住腳的了。
不慌,一個優秀的數據分析師,一定是本著具體問題具體分析的精神來調整FLAG的:
基于年代上映量數據,我們從20世紀30年代開始制作排名;為了避免有些年代電影過少,優化成各年代TOP 10%的電影推薦;同時,為了避免近年電影過多,每個年代推薦的上限數不超過100部。
看到這三個條件,連一向自傲的潘大師(pandas)都不禁長嘆了口氣。然鵝大師之所以是大師,就是因為在他眼里沒有什么是不可能的。思考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])
根據上一步構造的century_f變量,結合每個年代上映電影量,不足1000部的篩選前10%,超過1000部的只篩選前100部,結果,就呼之而出了。
在附上代碼和榜單之前,我預感到大部分旁友是和我一樣懶的(不會仔細看榜單),所以先整理出各年代TOP5電影(有些年代不足TOP5),做一個精華版的歷史電影排行榜奉上:
從峰回路轉、結尾讓人大呼牛逼的《控方證人》,到為無罪真理而辯的《十二怒漢》,再到家庭為重不怒自威的《教父》系列、重新詮釋希望和堅韌的《肖申克的救贖》以及將勵志提升到新高度的《阿甘正傳》(小Z閱片尚淺,榜單上只看過這些)。
每一部好的電影,都是一塊從高空墜落的石頭,它總能在人們的心湖上激起水花和漣漪,引起人們對生活、社會以及人性的思考。而爛片,就是從高空墜落的空礦泉水瓶,它墜勢洶洶,但最終只會浮在水面,讓看過的人心存芥蒂,感覺靈魂受到污染。
有了新的電影排名榜單,再也不用擔心劇荒了。
數據分析咨詢請掃描二維碼
若不方便掃碼,搜微信號:CDAshujufenxi
CDA數據分析師證書考試體系(更新于2025年05月22日)
2025-05-26解碼數據基因:從數字敏感度到邏輯思維 每當看到超市貨架上商品的排列變化,你是否會聯想到背后的銷售數據波動?三年前在零售行 ...
2025-05-23在本文中,我們將探討 AI 為何能夠加速數據分析、如何在每個步驟中實現數據分析自動化以及使用哪些工具。 數據分析中的AI是什么 ...
2025-05-20當數據遇見人生:我的第一個分析項目 記得三年前接手第一個數據分析項目時,我面對Excel里密密麻麻的銷售數據手足無措。那些跳動 ...
2025-05-20在數字化運營的時代,企業每天都在產生海量數據:用戶點擊行為、商品銷售記錄、廣告投放反饋…… 這些數據就像散落的拼圖,而相 ...
2025-05-19在當今數字化營銷時代,小紅書作為國內領先的社交電商平臺,其銷售數據蘊含著巨大的商業價值。通過對小紅書銷售數據的深入分析, ...
2025-05-16Excel作為最常用的數據分析工具,有沒有什么工具可以幫助我們快速地使用excel表格,只要輕松幾步甚至輸入幾項指令就能搞定呢? ...
2025-05-15數據,如同無形的燃料,驅動著現代社會的運轉。從全球互聯網用戶每天產生的2.5億TB數據,到制造業的傳感器、金融交易 ...
2025-05-15大數據是什么_數據分析師培訓 其實,現在的大數據指的并不僅僅是海量數據,更準確而言是對大數據分析的方法。傳統的數 ...
2025-05-14CDA持證人簡介: 萬木,CDA L1持證人,某電商中廠BI工程師 ,5年數據經驗1年BI內訓師,高級數據分析師,擁有豐富的行業經驗。 ...
2025-05-13CDA持證人簡介: 王明月 ,CDA 數據分析師二級持證人,2年數據產品工作經驗,管理學博士在讀。 學習入口:https://edu.cda.cn/g ...
2025-05-12CDA持證人簡介: 楊貞璽 ,CDA一級持證人,鄭州大學情報學碩士研究生,某上市公司數據分析師。 學習入口:https://edu.cda.cn/g ...
2025-05-09CDA持證人簡介 程靖 CDA會員大咖,暢銷書《小白學產品》作者,13年頂級互聯網公司產品經理相關經驗,曾在百度、美團、阿里等 ...
2025-05-07相信很多做數據分析的小伙伴,都接到過一些高階的數據分析需求,實現的過程需要用到一些數據獲取,數據清洗轉換,建模方法等,這 ...
2025-05-06以下的文章內容來源于劉靜老師的專欄,如果您想閱讀專欄《10大業務分析模型突破業務瓶頸》,點擊下方鏈接 https://edu.cda.cn/g ...
2025-04-30CDA持證人簡介: 邱立峰 CDA 數據分析師二級持證人,數字化轉型專家,數據治理專家,高級數據分析師,擁有豐富的行業經驗。 ...
2025-04-29CDA持證人簡介: 程靖 CDA會員大咖,暢銷書《小白學產品》作者,13年頂級互聯網公司產品經理相關經驗,曾在百度,美團,阿里等 ...
2025-04-28CDA持證人簡介: 居瑜 ,CDA一級持證人國企財務經理,13年財務管理運營經驗,在數據分析就業和實踐經驗方面有著豐富的積累和經 ...
2025-04-27數據分析在當今信息時代發揮著重要作用。單因素方差分析(One-Way ANOVA)是一種關鍵的統計方法,用于比較三個或更多獨立樣本組 ...
2025-04-25CDA持證人簡介: 居瑜 ,CDA一級持證人國企財務經理,13年財務管理運營經驗,在數據分析就業和實踐經驗方面有著豐富的積累和經 ...
2025-04-25