漫爬蟲(chóng)全流程:從requests+xpath采集到可視化看板)
簡(jiǎn)介面向Python爬蟲(chóng)與數(shù)據(jù)分析初學(xué)者的B站動(dòng)漫數(shù)據(jù)項(xiàng)目以嗶哩嗶哩番劇為對(duì)象演示從總榜爬取動(dòng)漫列表、訪(fǎng)問(wèn)詳情頁(yè)抓取追番人數(shù)與評(píng)分再到數(shù)據(jù)清洗、統(tǒng)計(jì)分析與可視化的完整流程適合作為爬蟲(chóng)與數(shù)據(jù)分析方向的入門(mén)實(shí)戰(zhàn)參考。壓縮包共23個(gè)文件體積約3.58MB包含3個(gè)交互式筆記代碼文件、2份表格數(shù)據(jù)、10張可視化圖片、1份說(shuō)明文檔及少量配置類(lèi)文件覆蓋爬取、預(yù)處理、分析三條主要環(huán)節(jié)能直觀看到不同步驟的產(chǎn)出。目前已有2978人學(xué)習(xí)下載。借助這套資料讀者可同時(shí)獲得可運(yùn)行代碼、真實(shí)抓取數(shù)據(jù)和成圖結(jié)果結(jié)合文檔中對(duì)請(qǐng)求解析、字段抽取、數(shù)據(jù)清洗、圖表繪制等細(xì)節(jié)的介紹能較快理解一類(lèi)番劇數(shù)據(jù)項(xiàng)目的實(shí)現(xiàn)思路節(jié)省自行搭建和排錯(cuò)的時(shí)間。1. 先想清楚B站動(dòng)漫爬蟲(chóng)項(xiàng)目到底要抓哪些數(shù)據(jù)想給B站動(dòng)漫做數(shù)據(jù)分析第一個(gè)能落地的選題就是爬番劇列表標(biāo)題、評(píng)分、播放量、追番人數(shù)、平臺(tái)歸屬和動(dòng)漫類(lèi)型一個(gè)周末能出結(jié)果。別被“爬蟲(chóng)”兩個(gè)字嚇住這個(gè)項(xiàng)目的難點(diǎn)根本不在反爬而在數(shù)據(jù)清洗和口徑選擇——B站接口返回的score是一個(gè)嵌套字典play字段可能是字符串評(píng)分0的番劇到底算“沒(méi)開(kāi)分”還是“真爛”這些才是真正花時(shí)間的地方。適合三種人第一次寫(xiě)完整爬蟲(chóng)的新手、想把數(shù)據(jù)分析技能用在真實(shí)場(chǎng)景的學(xué)生、想觀察二次元內(nèi)容走勢(shì)的從業(yè)者。下面我把從請(qǐng)求到可視化的完整路徑拆給你照著跑晚上就能看到第一張B站動(dòng)漫數(shù)據(jù)圖表。2. 用requestsxpath抓B站動(dòng)漫數(shù)據(jù)請(qǐng)求、解析與翻頁(yè)參數(shù)2.1 為什么選requestsxpath而不是scrapy說(shuō)到爬蟲(chóng)很多人的第一反應(yīng)是scrapy但遇到這個(gè)項(xiàng)目我一般會(huì)勸新手先別上框架。scrapy的學(xué)習(xí)曲線(xiàn)體現(xiàn)在項(xiàng)目結(jié)構(gòu)、Item Pipeline、Middleware 這些概念上而你要抓的不過(guò)是B站動(dòng)漫索引這一個(gè)站點(diǎn)、幾十個(gè)字段、幾百條數(shù)據(jù)。用requests加xpath三步就能跑通發(fā)請(qǐng)求、拿響應(yīng)、解析字段。等量級(jí)大到要天天跑、要防封、要分布式采集時(shí)再換scrapy也不遲。方案適用場(chǎng)景上手成本主要坑點(diǎn)requests xpath小批量、單站點(diǎn)、字段結(jié)構(gòu)穩(wěn)定低重試和頻率得自己管scrapy大規(guī)模、多站點(diǎn)、需要長(zhǎng)期維護(hù)中框架概念多調(diào)試成本高playwright頁(yè)面要執(zhí)行JS才能渲染高內(nèi)存占用大運(yùn)行慢選擇邏輯很簡(jiǎn)單B站動(dòng)漫索引頁(yè)在服務(wù)端渲染了大部分列表內(nèi)容HTML里能直接拿到標(biāo)題和鏈接同時(shí)它的JSON接口也能返回格式化的字段。用requests就是把網(wǎng)絡(luò)爬蟲(chóng)原理里的“請(qǐng)求—響應(yīng)—解析”三步走完xpath負(fù)責(zé)從HTML靜態(tài)結(jié)構(gòu)里把節(jié)點(diǎn)摘出來(lái)。接口變動(dòng)時(shí)還能切到JSON方案兜底兩條路都不丟。2.2 抓取熱播番劇列表請(qǐng)求接口的完整代碼與參數(shù)說(shuō)明我先給出用JSON接口抓取番劇列表的完整寫(xiě)法。B站有一個(gè)半開(kāi)放的番劇索引接口返回的是結(jié)構(gòu)清晰的JSON比從HTML里用xpath摳文本要穩(wěn)。第一次請(qǐng)求時(shí)先把響應(yīng)打出來(lái)看結(jié)構(gòu)這步很重要后面所有清洗邏輯都建立在字段名正確的前提上。import requests from requests.adapters import HTTPAdapter import time session requests.Session() session.mount(https://, HTTPAdapter(max_retries2)) session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0 Safari/537.36, Referer: https://www.bilibili.com/anime/, Origin: https://www.bilibili.com, }) API_URL https://api.bilibili.com/pgc/season/index/result params { season_type: 1, # 1 代表番劇 type: sort, # 排序請(qǐng)求 sort_type: 2, # 2 按追番人數(shù)排序 page: 1, pagesize: 20, # 每頁(yè)返回條數(shù) } resp session.get(API_URL, paramsparams, timeout10) resp.raise_for_status() data resp.json() # 先看返回結(jié)構(gòu)確認(rèn)字段名再寫(xiě)后續(xù)邏輯 print(data[code], data[message]) print(data[data][list][0])這段代碼里有三個(gè)地方新手容易忽略。第一session.mount配合HTTPAdapter可以把超時(shí)重試交給底層處理不用自己寫(xiě)try except套循環(huán)第二User-Agent和Referer是B站服務(wù)端識(shí)別請(qǐng)求來(lái)源的重要依據(jù)少了Referer很容易拿到412或403響應(yīng)第三params里的season_type和sort_type決定你抓的是“番劇”還是“電影”是“按播放量排”還是“按追番數(shù)排”。先打印第一頁(yè)數(shù)據(jù)確認(rèn)score是字典還是數(shù)字確認(rèn)platform存在與否再往下寫(xiě)。2.3 翻頁(yè)、請(qǐng)求頭與超時(shí)重試穩(wěn)定采集三件套單頁(yè)數(shù)據(jù)撐不起分析翻頁(yè)才是采集的主體。翻頁(yè)時(shí)有個(gè)血淚經(jīng)驗(yàn)不要for page in range(1, 100)一口氣跑完控制請(qǐng)求頻率不是玄學(xué)是給服務(wù)端留響應(yīng)時(shí)間也是給自己留后退的余地。每請(qǐng)求一頁(yè)sleep 0.5到1秒看起來(lái)慢實(shí)際上20頁(yè)只需要幾十秒遠(yuǎn)比觸發(fā)風(fēng)控后換IP來(lái)得省事。def fetch_anime_page(page: int): params[page] page resp session.get(API_URL, paramsparams, timeout10) resp.raise_for_status() payload resp.json() if payload[code] ! 0: # 接口返回異常時(shí)先停下來(lái)看 message不要盲目重試 print(fpage {page} return code: {payload[code]}, msg: {payload[message]}) return [] items payload[data][list] rows [] for item in items: rows.append({ title: item.get(title), score: item.get(score), # 這里可能是 dict后面再拆 play: item.get(play), follow: item.get(follow), type_name: item.get(type_name), platform: item.get(platform), }) return rows all_rows [] for page in range(1, 11): # 先抓前10頁(yè)200條 rows fetch_anime_page(page) print(fpage {page}: got {len(rows)} rows) all_rows.extend(rows) time.sleep(1) # 請(qǐng)求間隔控制頻率代碼邏輯不復(fù)雜但有兩個(gè)設(shè)計(jì)取舍值得講。第一fetch函數(shù)內(nèi)部先判斷payload[code]code不等于0時(shí)直接返回空列表并打印message。B站索引接口在請(qǐng)求過(guò)密時(shí)會(huì)返回風(fēng)控提示把code打印出來(lái)能幫你區(qū)分“我方請(qǐng)求有問(wèn)題”和“服務(wù)端拒絕服務(wù)”避免在錯(cuò)誤方向上加戲。第二rows里統(tǒng)一用item.get()而不是item[]這樣字段缺失時(shí)返回None而不是拋KeyError后面pandas清洗會(huì)省很多事。如果你遇到接口完全不可用另一個(gè)常見(jiàn)做法是用requests抓HTML再用lxml的etree和xpath解析列表節(jié)點(diǎn)核心區(qū)別只是在解析層請(qǐng)求層的session邏輯可以原樣復(fù)用。3. 數(shù)據(jù)清洗與存儲(chǔ)把JSON響應(yīng)整理成能分析的DataFrame3.1 原始字段里哪些能用、哪些要拆開(kāi)用抓回來(lái)的原始數(shù)據(jù)是列表套字典的結(jié)構(gòu)直接塞進(jìn)DataFrame之前先看清每個(gè)字段的“真實(shí)身份”。我在第一次跑這個(gè)項(xiàng)目時(shí)犯過(guò)一個(gè)典型錯(cuò)誤天真地以為score是一列數(shù)字結(jié)果DataFrame里那一列全是字典對(duì)象排序直接翻車(chē)。B站接口里的score實(shí)際是嵌套結(jié)構(gòu)包含score和user_count兩個(gè)子字段必須提前拆開(kāi)。原始字段實(shí)際類(lèi)型分析用途清洗動(dòng)作titlestr番劇名稱(chēng)主鍵去重、去空格playint 或 str播放量轉(zhuǎn)數(shù)值處理非數(shù)字字符followint追番人數(shù)轉(zhuǎn)數(shù)值scoredict 或 int評(píng)分和評(píng)分人數(shù)拆出score子字段type_namestr動(dòng)漫類(lèi)型如“搞笑”“戰(zhàn)斗”分組聚合platformstr播出平臺(tái)統(tǒng)計(jì)平臺(tái)分布拆字段的操作其實(shí)在爬蟲(chóng)環(huán)節(jié)就能做。每一條item經(jīng)過(guò)清洗后只保留分析需要的維度不要貪多。尤其是platform這個(gè)字段部分番劇是B站獨(dú)播部分是多平臺(tái)聯(lián)播后面做類(lèi)型分析時(shí)它是個(gè)很容易被忽略的維度。數(shù)據(jù)是黑匣子你存進(jìn)去什么分析時(shí)就只能看到什么提前把字段拆干凈比事后補(bǔ)救便宜。3.2 空值、字符串?dāng)?shù)字和評(píng)分0pandas清洗三板斧數(shù)據(jù)落地到pandas后第一件事不是畫(huà)圖是看df.info()和df.isna().sum()。這兩個(gè)命令能告訴你哪些列有空值、哪些列類(lèi)型不對(duì)。play和follow是數(shù)字但如果接口在某些情況下返回了帶逗號(hào)的字符串排序時(shí)就會(huì)按字典序排1萬(wàn)播放會(huì)被排在2千后面。import pandas as pd df pd.DataFrame(all_rows) print(df.info()) # 字符串?dāng)?shù)字統(tǒng)一轉(zhuǎn)成數(shù)值轉(zhuǎn)不動(dòng)的置為 NaN df[play] pd.to_numeric(df[play], errorscoerce) df[follow] pd.to_numeric(df[follow], errorscoerce) # score 是字典的拆出真正分?jǐn)?shù)直接是數(shù)值的保留 def extract_score(value): if isinstance(value, dict): return value.get(score) return value df[score] df[score].apply(extract_score) df[score] pd.to_numeric(df[score], errorscoerce) # 去掉標(biāo)題為空的記錄保留數(shù)據(jù)完整的行用于評(píng)分分析 df_dedup df.drop_duplicates(subset[title]) df_dedup df_dedup.dropna(subset[title, play]) print(df_dedup.head()) print(df_dedup.isna().sum())這段清洗有兩個(gè)參數(shù)值得講清楚。pd.to_numeric里的errorscoerce作用是把無(wú)法解析的字符串置成NaN而不是拋異常這是處理臟數(shù)據(jù)的標(biāo)準(zhǔn)姿勢(shì)你永遠(yuǎn)不知道接口里會(huì)出現(xiàn)什么奇怪的垃圾值。score的提取用isinstance判斷類(lèi)型兼容“接口返回字典”和“已經(jīng)優(yōu)化成數(shù)值”兩種情況避免接口一變代碼就報(bào)廢。drop_duplicates和dropna的目的是保證后續(xù)排序、聚類(lèi)的數(shù)據(jù)干凈標(biāo)題去重按番劇名播放量缺失的行直接丟棄因?yàn)榕判蟹治隼锶辈シ帕烤蜎](méi)有意義。3.3 存CSV還是SQLite按你的分析目標(biāo)選清洗完的數(shù)據(jù)總要落盤(pán)。兩種選擇CSV適合一次性的分析流程打開(kāi)即用Excel也能看SQLite適合你后面要反復(fù)查、要做條件篩選的場(chǎng)景。如果你只是寫(xiě)完這篇就交差CSV就夠了如果你想在周末里反復(fù)調(diào)整分析邏輯SQLite的體驗(yàn)會(huì)好很多尤其數(shù)據(jù)量超過(guò)幾千條時(shí)SQL的聚合效率遠(yuǎn)超一遍遍跑pandas。# 方案一CSV注意編碼要用 utf-8-sig df_dedup.to_csv(bilibili_anime.csv, indexFalse, encodingutf-8-sig) # 方案二SQLite方便后續(xù) SQL 查詢(xún) import sqlite3 conn sqlite3.connect(bilibili_anime.db) df_dedup.to_sql(anime, conn, if_existsreplace, indexFalse) conn.close()這兩個(gè)參數(shù)是踩坑后才懂的to_csv的encodingutf-8-sig專(zhuān)門(mén)解決Excel打開(kāi)中文亂碼的問(wèn)題稍后避坑章節(jié)會(huì)細(xì)說(shuō)to_sql的if_existsreplace解決重復(fù)跑腳本時(shí)表已存在的問(wèn)題replace會(huì)直接重建表適合分析階段的反復(fù)迭代。至于列類(lèi)型pandas會(huì)自動(dòng)推斷int列存成INTEGERfloat列存成REAL不需要手工建表省一步是一步。4. 數(shù)據(jù)分析播放量、評(píng)分和類(lèi)型分布怎么算4.1 播放量TOP10與追番人數(shù)排行數(shù)據(jù)干凈了分析才有底氣。第一個(gè)必出的分析是播放量排行找頭部番劇。這里有個(gè)細(xì)節(jié)nlargest比sort_values().head()更直接它在底層做了優(yōu)化而且只對(duì)單列做排序不會(huì)打亂其他列的順序。top10_play df_dedup.nlargest(10, play)[[title, play, follow, score]].copy() print(top10_play.to_string(indexFalse)) top10_follow df_dedup.nlargest(10, follow)[[title, follow, play, score]].copy() print(top10_follow.to_string(indexFalse))排行表出來(lái)后第一個(gè)結(jié)論往往就藏在對(duì)比里。你會(huì)發(fā)現(xiàn)播放量最高的番劇不一定追番人數(shù)最多有的番劇是“路人盤(pán)大”點(diǎn)開(kāi)看一眼但不追番有的番劇是“粉絲粘性高”追番人數(shù)遠(yuǎn)超播放量。這兩個(gè)指標(biāo)一對(duì)比就能看出內(nèi)容的出圈屬性和粉絲屬性。分析時(shí)不要只看絕對(duì)數(shù)把play和follow都打印出來(lái)再算一列“播放追番比”這個(gè)比值是內(nèi)容運(yùn)營(yíng)里很常見(jiàn)的健康度指標(biāo)。4.2 評(píng)分分布、類(lèi)型結(jié)構(gòu)與數(shù)據(jù)口徑評(píng)分是這個(gè)項(xiàng)目里最容易翻車(chē)的維度。我的做法是先把評(píng)分切成區(qū)間看整體分布再按動(dòng)漫類(lèi)型分組看均值。pd.cut的bins參數(shù)決定著分的顆粒度B站番劇評(píng)分普遍偏高8分以下并不多見(jiàn)所以bins要按7.0到10.0的區(qū)間切切5分以下沒(méi)有實(shí)際區(qū)分度。# 評(píng)分分箱看分布 bins [0, 6, 7, 8, 9, 10] labels [0-6, 6-7, 7-8, 8-9, 9-10] df_dedup[score_bin] pd.cut(df_dedup[score], binsbins, labelslabels, rightFalse) score_dist df_dedup[score_bin].value_counts().sort_index() print(score_dist) # 按動(dòng)漫類(lèi)型統(tǒng)計(jì)播放量groupby 后 agg 一次算三個(gè)指標(biāo) type_stats ( df_dedup.groupby(type_name)[play] .agg([sum, mean, count]) .sort_values(sum, ascendingFalse) ) print(type_stats.head(10))這段分析有兩條邏輯要說(shuō)明。第一pd.cut的rightFalse表示區(qū)間左閉右開(kāi)8會(huì)進(jìn)到8-9而不是7-8這個(gè)參數(shù)不設(shè)的話(huà)默認(rèn)是右閉邊界數(shù)據(jù)會(huì)分到意外的桶里。第二groupby之后用agg一次算總和、均值、條數(shù)sum體現(xiàn)類(lèi)型總盤(pán)mean體現(xiàn)單部平均水平count體現(xiàn)作品數(shù)量三個(gè)數(shù)字并排看才能判斷一個(gè)類(lèi)型是“大而稀”還是“小而精”。至于波動(dòng)大的分析結(jié)論先確認(rèn)你的數(shù)據(jù)口徑是“當(dāng)前累計(jì)播放量”還是“某時(shí)間段增量”——B站公開(kāi)接口返回的是累計(jì)值說(shuō)“播放量”之前心里得有數(shù)。4.3 把分析結(jié)論收斂成可視化指標(biāo)清單分析做得再深最后都是為了輸出。我一般會(huì)在動(dòng)手畫(huà)圖前先列一張指標(biāo)和圖表類(lèi)型的對(duì)照表想清楚“誰(shuí)要看什么”。要回答的問(wèn)題核心指標(biāo)推薦圖表哪些番劇播放最猛play柱狀圖TOP10追番粘性和播放量是否一致play/follow比散點(diǎn)圖評(píng)分高的番劇集中在哪個(gè)類(lèi)型score均值 type_name箱線(xiàn)圖或柱狀圖B站獨(dú)播還是多平臺(tái)聯(lián)播占主流platform 占比餅圖這張表的價(jià)值在于讓你不盲目堆圖表。數(shù)據(jù)分析的方法不是圖越多越好而是每個(gè)圖都要對(duì)應(yīng)一個(gè)決策或一個(gè)判斷。播放量TOP10用柱狀圖平臺(tái)分布用餅圖評(píng)分分布用箱線(xiàn)圖。下一步畫(huà)圖時(shí)就有明確目標(biāo)了尤其是做可視化大屏?xí)r指標(biāo)清單能幫你擋住“再加個(gè)圖會(huì)顯得很厲害”的沖動(dòng)。5. 避坑B站爬蟲(chóng)里的403、字段變動(dòng)和編碼問(wèn)題排查5.1 請(qǐng)求被拒403UA、Referer與請(qǐng)求頻率現(xiàn)象前兩頁(yè)抓得好好的第三頁(yè)開(kāi)始返回403隨后連第一頁(yè)都打不開(kāi)。原因請(qǐng)求頻率太快加上請(qǐng)求頭里Referer缺失或錯(cuò)誤。B站服務(wù)端會(huì)把“無(wú)Referer的Python請(qǐng)求”和“高頻同IP請(qǐng)求”都識(shí)別為異常訪(fǎng)問(wèn)。解決給session配齊瀏覽器級(jí)的User-Agent和Referer每頁(yè)間隔加到1秒以上。請(qǐng)求節(jié)奏上做退避而不是硬碰硬遇到403先停30秒再繼續(xù)比一直重試有效。另外一個(gè)常用做法是把頁(yè)碼順序打亂尤其抓幾十頁(yè)時(shí)從中間頁(yè)開(kāi)始抓能降低連續(xù)翻頁(yè)的行為特征。5.2 xpath解析返回空列表先看響應(yīng)再排查現(xiàn)象寫(xiě)好的xpath表達(dá)式在瀏覽器里能定位到節(jié)點(diǎn)代碼里運(yùn)行卻一直返回空列表。原因B站的動(dòng)漫索引頁(yè)有些板塊走接口后在客戶(hù)端渲染HTML源碼里根本沒(méi)有你要的節(jié)點(diǎn)還有一種是class名帶了動(dòng)態(tài)變化的hash后綴每次請(qǐng)求都不一樣。解決遇到空列表第一件事不是改xpath而是打印resp.text前500個(gè)字符看響應(yīng)體里到底有什么。如果確定是接口渲染直接換JSON接口方案如果只是class動(dòng)態(tài)變化用xpath的contains函數(shù)模糊匹配比如//p[contains(class,score)]而不是寫(xiě)死完整class名。5.3 評(píng)分全是0或NaN取字段與類(lèi)型轉(zhuǎn)換的坑現(xiàn)象清洗完的DataFrame里score列要么是0要么是NaN沒(méi)有一個(gè)正常分?jǐn)?shù)。原因接口返回的score是字典直接當(dāng)成數(shù)值讀出來(lái)自然是錯(cuò)另外部分未開(kāi)分或評(píng)分人數(shù)太少的番劇接口給的score就是0。二者混在一起如果不處理分析時(shí)會(huì)把“沒(méi)評(píng)分”和“低分差評(píng)”混為一談。解決在爬蟲(chóng)解析層就用isinstance判斷字典就取出score子字段數(shù)值就原樣保留。分析時(shí)用布爾掩碼區(qū)分兩類(lèi)數(shù)據(jù)df[df[score] 0]做評(píng)分分析df[df[score].isna()]單獨(dú)看未開(kāi)分作品占比。這個(gè)區(qū)分直接影響結(jié)論的準(zhǔn)確性。5.4 CSV用Excel打開(kāi)亂碼編碼選utf-8-sig現(xiàn)象CSV文件用Notepad看正常用Excel打開(kāi)中文全是亂碼。原因Excel打開(kāi)CSV時(shí)默認(rèn)按GBK解碼而pandas默認(rèn)用utf-8寫(xiě)入編解碼不一致就亂了。解決寫(xiě)入?yún)?shù)改成encodingutf-8-sig。utf-8-sig會(huì)在文件開(kāi)頭寫(xiě)入BOM標(biāo)記Excel就能自動(dòng)識(shí)別為UTF-8編碼。這個(gè)問(wèn)題幾乎每個(gè)入門(mén)者都會(huì)遇到早改早省心。5.5 翻頁(yè)超過(guò)50頁(yè)就沒(méi)有數(shù)據(jù)接口頁(yè)數(shù)上限現(xiàn)象循環(huán)翻到第50頁(yè)左右返回的list為空但code還是0程序沒(méi)有報(bào)錯(cuò)。原因B站索引接口對(duì)單次排序條件下能翻的頁(yè)數(shù)有限制超過(guò)深度后返回空列表同時(shí)連續(xù)高頻翻頁(yè)也會(huì)觸發(fā)限流。解決不要執(zhí)著于“一頁(yè)頁(yè)翻到底”。要么增大pagesize到50減少請(qǐng)求次數(shù)要么換一個(gè)排序條件從另一個(gè)維度重新抓比如先按追番人數(shù)抓前50頁(yè)再按播放量抓前50頁(yè)。數(shù)據(jù)采集講究用不同路徑拼全景單一路徑是有邊界的。6. 用pyecharts做可視化落地從單圖到HTML看板6.1 匯報(bào)場(chǎng)景用柱狀圖起手pyecharts是ECharts的Python封裝畫(huà)完直接生成HTML文件瀏覽器就能打開(kāi)不需要搭建前端環(huán)境。對(duì)數(shù)據(jù)分析與可視化這個(gè)場(chǎng)景來(lái)說(shuō)這是最省事的落地方式。柱狀圖是匯報(bào)場(chǎng)景的默認(rèn)起手式因?yàn)榕判行畔⒌膫鬟_(dá)效率最高。from pyecharts.charts import Bar from pyecharts import options as opts top10 df_dedup.nlargest(10, play) bar Bar() bar.add_xaxis(top10[title].tolist()) bar.add_yaxis( 播放量, top10[play].tolist(), label_optsopts.LabelOpts(positionright), ) bar.set_global_opts( title_optsopts.TitleOpts(titleB站新番播放量TOP10), yaxis_optsopts.AxisOpts(name播放量), ) bar.set_series_opts(itemstyle_optsopts.ItemStyleOpts(color#fb7299)) bar.render(bilibili_anime_top10_play.html)有兩點(diǎn)細(xì)節(jié)我是吃過(guò)虧后養(yǎng)成的習(xí)慣。add_xaxis和add_yaxis都接收l(shuí)ist類(lèi)型pandas的Series要先tolist()不然圖表上會(huì)出現(xiàn)索引錯(cuò)位set_global_opts里的yaxis_opts用來(lái)改坐標(biāo)軸名稱(chēng)不要省后期看圖的同事不會(huì)知道你腦子里想的是播放量。顏色改成B站主色 #fb7299匯報(bào)時(shí)觀感會(huì)好很多。6.2 多圖合成一個(gè)HTML看板單張圖只能回答問(wèn)題的一個(gè)側(cè)面把柱狀圖、餅圖、表格拼進(jìn)同一個(gè)HTML文件就形成了最簡(jiǎn)版可視化大屏。pyecharts的Page組件可以串起多個(gè)圖表配套的SimplePageLayout會(huì)自動(dòng)做上下布局不需要寫(xiě)HTML模板。from pyecharts.charts import Bar, Pie, Page import pandas as pd platform_stats df_dedup[platform].value_counts() pie Pie() pie.add_xaxis(platform_stats.index.tolist()) pie.add_yaxis(平臺(tái)占比, platform_stats.values.tolist()) pie.set_global_opts(title_optsopts.TitleOpts(title播出平臺(tái)分布)) page Page(layoutPage.SimplePageLayout) page.add(bar, pie) page.render(bilibili_anime_dashboard.html)Page組件會(huì)把bar和pie兩個(gè)圖表對(duì)象依次渲染進(jìn)一個(gè)HTML文檔里layout參數(shù)控制排列方式。做這種組合看板時(shí)我的教訓(xùn)是圖表數(shù)量控制在三到四個(gè)以?xún)?nèi)指標(biāo)清單里沒(méi)有對(duì)應(yīng)分析結(jié)論的圖不要加。爬蟲(chóng)和數(shù)據(jù)清洗占掉了這個(gè)項(xiàng)目七成的時(shí)間可視化的價(jià)值在于讓分析結(jié)論一眼被看懂而不是把圖表堆成花架子。第一次跑通后你把pagesize改大、把season_type換成別的分類(lèi)整套流程可以原樣復(fù)用到B站電影和紀(jì)錄片板塊。數(shù)據(jù)分析需要學(xué)哪些最重要的不是工具數(shù)量而是從抓取到出結(jié)論這整條鏈路你都親手走過(guò)一遍后面換任何數(shù)據(jù)源都只是微調(diào)參數(shù)的事。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取