戰(zhàn):豆瓣Top250數(shù)據(jù)分析與可視化全流程)
簡介基于Python爬取豆瓣電影Top250并完成數(shù)據(jù)分析與可視化的完整項(xiàng)目面向計(jì)算機(jī)相關(guān)專業(yè)正在做課程大作業(yè)的學(xué)生以及需要實(shí)戰(zhàn)練習(xí)的Python學(xué)習(xí)者。資源共2000個(gè)文件以1830個(gè)Python源碼文件為主涵蓋爬蟲抓取、數(shù)據(jù)清洗、統(tǒng)計(jì)分析、可視化繪圖等核心模塊同時(shí)包含C擴(kuò)展、txt配置、HTML/CSS/JS前端展示文件、PDF文檔等輔助內(nèi)容整體壓縮包約80.74MB文件層級清晰查找定位方便。目前已有942人學(xué)習(xí)下載。項(xiàng)目經(jīng)導(dǎo)師指導(dǎo)并通過高分驗(yàn)收評審分98分全部源碼已本地編譯調(diào)試并穩(wěn)定運(yùn)行從豆瓣Top250頁面抓取、字段解析、數(shù)據(jù)清洗到統(tǒng)計(jì)分析與可視化出圖形成完整項(xiàng)目鏈路。附帶文檔說明還包括運(yùn)行環(huán)境配置、使用步驟、設(shè)計(jì)思路和常見問題排查能幫助讀者快速理解數(shù)據(jù)分析項(xiàng)目開發(fā)全流程借鑒代碼組織方式與調(diào)試技巧對完成課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)具有直接的參考價(jià)值。1. 為什么拿豆瓣Top250當(dāng)爬蟲和數(shù)據(jù)可視化練手項(xiàng)目數(shù)據(jù)規(guī)模與交付形態(tài)正好匹配拿“Python爬取豆瓣電影Top250數(shù)據(jù)分析與可視化”當(dāng)成一個(gè)完整項(xiàng)目來做最大的好處不是頁面難爬而是它剛好覆蓋了從業(yè)者日常最常用的一套鏈路請求 → 解析 → 清洗 → 分析 → 出圖。Top250只有250條數(shù)據(jù)單頁25條共10頁字段卻足夠豐富——排名、片名、導(dǎo)演、主演、年份、制片國家或地區(qū)、類型、評分、評價(jià)人數(shù)、引用語。這個(gè)規(guī)模意味著你不需要分布式爬蟲、不需要消息隊(duì)列一臺(tái)普通筆記本用requests加BeautifulSoup就能穩(wěn)定跑完但它又足夠讓你踩到真實(shí)項(xiàng)目里最常見的坑反爬校驗(yàn)、字段拆分、空值處理、中文字體亂碼。很多入門教程跑完就結(jié)束了但真正到交付環(huán)節(jié)才發(fā)現(xiàn)問題爬到的數(shù)據(jù)洗不干凈圖表畫出來沒有業(yè)務(wù)含義代碼改一處別處就崩。這篇筆記會(huì)把源碼和文檔說明里該有的模塊拆開講從單頁請求到翻頁循環(huán)從復(fù)合字段清洗到pandas指標(biāo)計(jì)算再到matplotlib與pyecharts的分工最后給出一套自檢和增量更新的驗(yàn)證方式。適合剛跑通Python基礎(chǔ)、想做一個(gè)完整數(shù)據(jù)分析與可視化實(shí)踐項(xiàng)目的人也適合已經(jīng)寫過爬蟲、但想把代碼從“能跑”提升到“能交付”的熟手。2. 請求與解析先用單頁跑通再把start參數(shù)變成翻頁循環(huán)2.1 請求頭、延時(shí)與緩存把“反爬”從玄學(xué)變成工程紀(jì)律豆瓣Top250的反爬嚴(yán)格程度在同類網(wǎng)站里算溫和的但不代表可以不帶腦子請求。很多人一上來就寫一個(gè)for循環(huán)連續(xù)請求10頁結(jié)果第3頁開始返回418或者一個(gè)“檢測到異常訪問”的提示頁。這里的核心問題不是網(wǎng)站有多強(qiáng)而是你的請求特征太像腳本了。我一般的做法是三個(gè)動(dòng)作同時(shí)做。第一固定一個(gè)完整的User-Agent和Accept-Language讓請求頭接近瀏覽器第二每頁請求之間加隨機(jī)延時(shí)把1秒到2秒的間隔打散第三把每次請求到的HTML緩存到本地目錄下次跑的時(shí)候優(yōu)先讀緩存。第三點(diǎn)很多人忽略但它其實(shí)是整個(gè)爬蟲實(shí)戰(zhàn)里最有用的“后悔藥”——豆瓣頁面的榜單變化很慢你完全不需要每次重新請求一遍緩存還能幫你定位解析問題頁面結(jié)構(gòu)變了和代碼寫錯(cuò)了是兩種完全不同的排錯(cuò)路徑。import os import time import random import requests HEADERS { User-Agent: ( Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ), Accept-Language: zh-CN,zh;q0.9,en;q0.8, } CACHE_DIR html_cache def get_page(start: int, use_cache: bool True) - str: os.makedirs(CACHE_DIR, exist_okTrue) cache_file os.path.join(CACHE_DIR, ftop250_{start}.html) if use_cache and os.path.exists(cache_file): with open(cache_file, encodingutf-8) as f: return f.read() url fhttps://movie.douban.com/top250?start{start}filter resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() html resp.text with open(cache_file, w, encodingutf-8) as f: f.write(html) time.sleep(random.uniform(1.0, 2.0)) return html邏輯說明get_page先是構(gòu)造帶start參數(shù)的URLstart0對應(yīng)第1頁start25對應(yīng)第2頁每頁25條。拿到響應(yīng)后先寫緩存再延時(shí)這樣即使后續(xù)解析代碼寫錯(cuò)了重新調(diào)試時(shí)也不會(huì)再消耗請求次數(shù)。參數(shù)說明timeout設(shè)10秒避免某個(gè)頁面卡住拖死整個(gè)腳本random.uniform(1.0, 2.0)把間隔控制在1到2秒之間這個(gè)頻率遠(yuǎn)低于豆瓣的風(fēng)控閾值足夠穩(wěn)定。不要為了趕時(shí)間把延時(shí)去掉這個(gè)項(xiàng)目的數(shù)據(jù)量決定了你省下的時(shí)間最多幾秒但觸發(fā)的風(fēng)控可能要等幾分鐘甚至更久。提示緩存目錄建議單獨(dú)建不要和代碼混在一起。文檔說明里把這一步寫清楚別人拿到源碼后第一眼就知道哪些文件是緩存、哪些是源碼。2.2 用BeautifulSoup提取Top250單頁字段選擇器、解析與翻頁參數(shù)拿到HTML后真正核心的解析邏輯其實(shí)固定在單頁里。Top250列表頁的結(jié)構(gòu)一直是ol.grid_view下的li每個(gè)li包含一部電影的完整信息標(biāo)題在.hd下的.title里評分在.star下的.rating_num引用語在.quote下的span而導(dǎo)演、主演、年份、地區(qū)、類型全部塞在.info .bd下面的p標(biāo)簽里。這個(gè)p標(biāo)簽內(nèi)部用br分隔成兩行是需要單獨(dú)處理的。from bs4 import BeautifulSoup import re def parse_page(html: str, start: int) - list[dict]: soup BeautifulSoup(html, lxml) items [] for li in soup.select(ol.grid_view li): rank int(li.select_one(.pic em).text) title_tag li.select_one(.hd .title) title title_tag.text.strip() if title_tag else rating_tag li.select_one(.star .rating_num) rating float(rating_tag.text) if rating_tag else 0.0 quote_tag li.select_one(.quote span) quote quote_tag.text.strip() if quote_tag else info_p li.select_one(.info .bd p) info_lines info_p.get_text( , stripTrue).split( ) # info_lines 大概是: # [導(dǎo)演: 某某 / 主演: 某某, 1994 / 美國 / 犯罪 劇情] person_line info_lines[0] if len(info_lines) 0 else meta_line info_lines[1] if len(info_lines) 1 else items.append({ rank: rank, title: title, rating: rating, quote: quote, person_line: person_line, meta_line: meta_line, }) return items邏輯說明先用select定位列表項(xiàng)再逐字段提取。這里的技巧是把導(dǎo)演主演單獨(dú)留成一個(gè)原始字符串不要急著拆因?yàn)椴煌瑮l目的主演人數(shù)不一樣拆早了容易丟失信息。meta_line同樣保留整行后面清洗階段再統(tǒng)一按“/”拆分。參數(shù)說明select_one在目標(biāo)不存在時(shí)返回None所以每個(gè)字段都做了兜底。rank用int轉(zhuǎn)換rating用float轉(zhuǎn)換萬一豆瓣調(diào)整了評分顯示精度這個(gè)轉(zhuǎn)換也不會(huì)崩。get_text( , stripTrue)的作用是把p標(biāo)簽里的br換成空格再拼接這樣兩行文本還能用split( )分開如果不加這個(gè)參數(shù)br會(huì)直接把文本拼成一坨后續(xù)清洗就沒法做了。3. 字段清洗與存儲(chǔ)復(fù)合字段拆分、去重與CSV落盤的落地規(guī)則3.1 年份、制片地區(qū)、類型混在一行用正則和split拆干凈爬到的數(shù)據(jù)里最臟的不是評分而是meta_line這一行文本。它長這樣“1994 / 美國 / 犯罪 劇情”但有三種變體非常容易讓人翻車年份后面帶括號(hào)備注比如“1993 / 中國大陸 / 中國香港”制片國家或地區(qū)本身帶斜杠比如“美國 / 英國”類型字段由兩個(gè)英文單詞組成比如“劇情 愛情”。如果無腦用split(/)然后取第0個(gè)元素當(dāng)年份就會(huì)把“1993中國大陸”整個(gè)當(dāng)成年份字符串。def clean_meta(meta_line: str): # 先用正則把年份摘出來年份一定是4位數(shù)字 year_match re.search(r(\d{4}), meta_line) year int(year_match.group(1)) if year_match else None # 去掉年份和可能帶括號(hào)的備注剩下的才是“地區(qū) / 類型” rest re.sub(r\d{4}.*?/, , meta_line) parts [p.strip() for p in rest.split(/)] country parts[0] if parts else genres [g.strip() for g in parts[1].split()] if len(parts) 1 else [] return year, country, genres邏輯說明先正則搜索4位連續(xù)數(shù)字作為年份把年份從原字符串里摘出去避免后續(xù)split被年份備注干擾。rest里可能還殘留“中國大陸 / 中國香港”這樣的多地區(qū)字段直接取第一個(gè)作為主要制片地區(qū)類型則用split()按空白切分因?yàn)槎拱甑念愋妥侄沃虚g是空格不是斜杠。參數(shù)說明re.search而不是re.findall因?yàn)橐徊侩娪爸挥幸粋€(gè)年份。re.sub的替換模式里\d{4}.*?/是非貪婪匹配作用是從年份開始一直去掉到第一個(gè)斜杠這樣即使年份后面跟著“(中國大陸)”也能一次清干凈。genres用split()而不是split( )既能處理單個(gè)空格也能處理多個(gè)空格。主演字段同樣需要單獨(dú)處理。person_line長這樣“導(dǎo)演: 弗蘭克·德拉邦特 Frank Darabont 主演: 蒂姆·羅賓斯 Tim Robbins / 摩根·弗里曼 Morgan Freeman”里面中英文混排演員之間用斜杠分隔。def clean_person(person_line: str): director actors [] if 導(dǎo)演: in person_line: director_part person_line.split(導(dǎo)演:)[1].split(主演:)[0] director director_part.strip() if 主演: in person_line: actors_part person_line.split(主演:)[1] # 只要前3位主演 actors [a.strip() for a in actors_part.split(/)[:3]] return director, actors邏輯說明先按“導(dǎo)演:”切出導(dǎo)演段再按“主演:”切出演員段。演員只取前3位因?yàn)門op250條目里主演最多能達(dá)到六七個(gè)取全了會(huì)讓后面的詞頻分析變得很碎。這里有一個(gè)隱藏坑有些老電影條目沒有“主演:”字樣只有導(dǎo)演所以兩個(gè)if都必須獨(dú)立判斷不能把邏輯寫成else。參數(shù)說明split(/)[:3]中的[:3]是硬編碼如果某天想分析完整演員陣容改成不帶切片即可。director的strip()不能省因?yàn)閷?dǎo)演字段后面可能帶著一個(gè)空格再拼接主演字段。3.2 從DataFrame到CSV編碼、去重與斷點(diǎn)續(xù)爬的落地寫法清洗完單個(gè)條目后把全部10頁數(shù)據(jù)匯總成DataFrame。這里要定一列統(tǒng)一字段順序rank、title、year、country、genres、director、actors、rating、quote其中actors和genres是列表存CSV前需要轉(zhuǎn)成字符串否則pandas會(huì)寫出帶引號(hào)的Python字面量。import pandas as pd def build_dataframe(all_items: list[dict]) - pd.DataFrame: cleaned [] for item in all_items: year, country, genres clean_meta(item[meta_line]) director, actors clean_person(item[person_line]) cleaned.append({ rank: item[rank], title: item[title], year: year, country: country, genres: /.join(genres), director: director, actors: /.join(actors), rating: item[rating], quote: item[quote], }) df pd.DataFrame(cleaned) df df.drop_duplicates(subset[rank]).sort_values(rank) df.to_csv(top250.csv, indexFalse, encodingutf-8-sig) return df邏輯說明清洗函數(shù)接收原始字段輸出規(guī)整的單條記錄。genres和actors用“/”重新拼回字符串是為了讓CSV每一列都是可讀文本而不是Python列表字面量。drop_duplicates按rank去重防止重復(fù)請求或斷點(diǎn)續(xù)爬時(shí)產(chǎn)生重復(fù)行。參數(shù)說明encodingutf-8-sig是給Excel用戶準(zhǔn)備的不加這個(gè)參數(shù)Excel打開CSV時(shí)中文會(huì)亂碼如果只給pandas自己讀普通utf-8就夠。sort_values(rank)保證輸出順序穩(wěn)定后續(xù)分析圖和榜單排名一致調(diào)試時(shí)方便對照原始網(wǎng)頁。注意初版代碼不要把清洗和分析混在同一個(gè)文件里。源碼至少要拆成crawler.py、clean.py、analysis.py三個(gè)模塊文檔說明里寫清楚運(yùn)行順序否則改一個(gè)字段格式就要連帶改畫圖代碼這是項(xiàng)目腐化最快的方式。4. 避坑與常見問題4條讓爬取、清洗與分析翻車的真實(shí)記錄4.1 請求返回418或解析結(jié)果為空列表現(xiàn)象、原因與排查順序現(xiàn)象代碼第一次跑通加了一個(gè)for循環(huán)請求10頁跑到第4頁突然打印出“418”狀態(tài)碼或者頁面正常返回但select(ol.grid_view li)一個(gè)元素都選不到。原因連續(xù)請求觸發(fā)了風(fēng)控豆瓣返回了一個(gè)驗(yàn)證頁面還有一種情況是你改了headers里的某個(gè)字段導(dǎo)致頁面返回的是默認(rèn)的靜態(tài)模板而不是電影列表。這兩個(gè)原因的表現(xiàn)形式完全一樣都是“沒有l(wèi)i”。解決先打印response.status_code和response.text的前200個(gè)字符確認(rèn)是418還是200。如果是418說明頻率太高把延時(shí)從random.uniform(1.0, 2.0)改成random.uniform(2.0, 4.0)如果返回200但沒有l(wèi)i檢查headers里是否漏了Accept-Language或者User-Agent被requests庫覆蓋成了默認(rèn)值。我踩過一次最蠢的坑在for循環(huán)外面定義的session循環(huán)里卻每次新建requests.get導(dǎo)致cookies沒有復(fù)用服務(wù)端把每次請求都當(dāng)成新連接風(fēng)控閾值更低。4.2 正則拆年份“吞掉”了數(shù)據(jù)爬到了卻洗丟了現(xiàn)象寫正則的時(shí)候想匹配年份和地區(qū)用了re.findall(r\d{4}, meta_line)發(fā)現(xiàn)《霸王別姬》的年份輸出是1993這沒問題但用同一個(gè)正則去匹配帶備注的條目比如“1993(中國香港) / 中國大陸”年份后面莫名多了一段字符串。原因正則\d{4}只匹配4位數(shù)字不匹配后面的括號(hào)備注但如果你在提取完后沒有把備注部分從原字符串里remove掉下一步split(/)時(shí)“1993(中國香港)”就成了一個(gè)整體。解決按代碼段的順序來先re.search提取年份再re.sub把從年份開始的這截內(nèi)容連同斜杠一起刪掉最后才做split。這個(gè)順序不能反過來——如果先split再提取年份遇到“美國 / 英國”這種地區(qū)本身帶斜杠的條目年份索引就全亂了。我見過有人用切片取第0個(gè)元素當(dāng)年份在Top250上大概90%的條目是對的剩下10%的條目錯(cuò)得毫無規(guī)律這種bug最可怕因?yàn)樗粫?huì)報(bào)錯(cuò)。4.3 清洗后明明250條分析時(shí)只剩248條空值溯源現(xiàn)象CSV里檢查行數(shù)是250但pandas讀進(jìn)來做groupby(year)之后年份分布的總和只有248。逐行排查發(fā)現(xiàn)是兩條評分缺失的條目被dropna帶走了。原因豆瓣Top250并不是每條都有“引用語”quote字段在解析時(shí)被賦了空字符串。清洗階段如果寫了df df.dropna()空字符串不會(huì)觸發(fā)dropna但如果你把空字符串fillna(pd.NA)再dropna就會(huì)把這兩條看似“不完整”的數(shù)據(jù)刪掉。解決分析腳本里只對參與計(jì)算的列做空值處理比如df.dropna(subset[rating, year])絕不對整張表做全字段dropna。更穩(wěn)的做法是清洗階段把quote的空字符串統(tǒng)一替換成“無”這樣無論是存CSV還是后續(xù)做詞云都不會(huì)影響主鏈路。4.4 matplotlib中文亂碼與坐標(biāo)軸重疊可視化最常見的兩座山現(xiàn)象畫評分分布圖標(biāo)題和坐標(biāo)軸文字全是方塊畫年份分布圖X軸年份標(biāo)簽擠成一團(tuán)什么都看不清。原因matplotlib默認(rèn)字體不含中文字符需要顯式指定字體年份從1939到2023跨度太大默認(rèn)刻度策略不會(huì)自動(dòng)旋轉(zhuǎn)標(biāo)簽。解決繪圖前先設(shè)置三行rcParams把字體切到SimHei并關(guān)閉unicode負(fù)號(hào)。如果運(yùn)行環(huán)境是Linux服務(wù)器SimHei不存在改成Noto Sans CJK SC。X軸標(biāo)簽在刻度超過10個(gè)時(shí)加plt.xticks(rotation45)這一步不是美化是數(shù)據(jù)可讀性的底線。下面的代碼塊是每次畫圖都要帶的配置import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False邏輯說明font.sans-serif必須放在繪圖代碼之前且不能放在函數(shù)內(nèi)部否則每次調(diào)用都需要重復(fù)設(shè)置。axes.unicode_minus控制負(fù)號(hào)顯示不設(shè)置的話坐標(biāo)軸負(fù)號(hào)會(huì)變成方塊這個(gè)問題在評分分布圖里特別明顯。5. 數(shù)據(jù)分析與可視化用pandas算出結(jié)論、用圖表把結(jié)論講清楚的完整鏈路5.1 用pandas算評分分布、年代分布與地區(qū)Top10代碼與參數(shù)數(shù)據(jù)清洗完成后的第一步不是畫圖而是先想清楚要回答哪幾個(gè)問題。Top250這個(gè)數(shù)據(jù)能支撐的結(jié)論大致有四類評分分布是否集中在9分以上、哪個(gè)十年貢獻(xiàn)的榜單影片最多、哪些國家或地區(qū)上榜最多、哪些導(dǎo)演多次上榜。每個(gè)問題對應(yīng)一個(gè)pandas操作代碼量不大但參數(shù)細(xì)節(jié)值得講。import pandas as pd import numpy as np df pd.read_csv(top250.csv) # 1. 評分分布按0.1分間隔做分桶統(tǒng)計(jì) df[rating_bin] pd.cut(df[rating], binsnp.arange(8.0, 10.0, 0.1)) rating_dist df[rating_bin].value_counts().sort_index() # 2. 年代分布按十年分桶 df[decade] (df[year] // 10) * 10 decade_dist df[decade].value_counts().sort_index() # 3. 地區(qū)分布把“美國 / 英國”拆成多行再計(jì)數(shù) df_country df.assign(country_listdf[country].str.split(/)).explode(country_list) country_top10 df_country[country_list].str.strip().value_counts().head(10) # 4. 導(dǎo)演上榜頻次 director_top10 df[director].value_counts().head(10)邏輯說明pd.cut用等距分箱把評分分成從8.0到9.9的區(qū)間這樣才能看出“9.0到9.1”和“9.1到9.2”是否有斷層。年代分桶用的是整除技巧year // 10 * 10把1994變成1990、2003變成2000天然形成十年粒度。地區(qū)分布里explode是關(guān)鍵原始數(shù)據(jù)里一部合拍片可能同時(shí)屬于美國和英國只有拆成兩行才能準(zhǔn)確統(tǒng)計(jì)地區(qū)占比。參數(shù)說明np.arange(8.0, 10.0, 0.1)生成的區(qū)間是左閉右開9.9分會(huì)被歸到9.9到10.0那一桶不會(huì)丟失。value_counts()默認(rèn)按頻次降序distinct排序時(shí)用sort_index()恢復(fù)區(qū)間順序否則畫出來的X軸是亂的。head(10)限制了輸出條目Top250里上榜國家或地區(qū)有幾十個(gè)只取前10畫圖避免條形圖X軸被擠爆。5.2 靜態(tài)圖先對齊結(jié)論交互圖再談美觀matplotlib與pyecharts的分工數(shù)據(jù)分析與可視化實(shí)踐里最常見的錯(cuò)誤是一上來就追美觀圖表樣式調(diào)了一下午結(jié)論沒驗(yàn)證。我的一般流程是先用matplotlib出四張靜態(tài)圖確認(rèn)數(shù)據(jù)和結(jié)論對得上再用pyecharts做成可交付的HTML看板。后者不改變分析結(jié)論只改變展示層。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(2, 2, figsize(14, 10)) # 左上評分分布直方圖 axes[0, 0].hist(df[rating], bins20, edgecolorwhite) axes[0, 0].set_title(Top250評分分布) # 右上年代分布柱狀圖 decade_dist.plot(kindbar, axaxes[0, 1]) axes[0, 1].set_title(上榜電影年代分布) # 左下地區(qū)Top10條形圖 country_top10.plot(kindbarh, axaxes[1, 0]) axes[1, 0].set_title(制片地區(qū)Top10) # 右下評分與評價(jià)人數(shù)散點(diǎn)圖 axes[1, 1].scatter(df[rating], df[rating_count]) axes[1, 1].set_xlabel(評分) axes[1, 1].set_ylabel(評價(jià)人數(shù)) plt.tight_layout() plt.savefig(top250_analysis.png, dpi150)邏輯說明四張圖分別對應(yīng)四個(gè)分析結(jié)論。評分分布直方圖能直接看出9.0到9.5是不是密集區(qū)年代柱狀圖用來觀察近幾年上榜數(shù)量是增還是減地區(qū)條形圖回答“哪個(gè)國家的電影最受榜單認(rèn)可”右下角的散點(diǎn)圖是把評分和評價(jià)人數(shù)放一起看高評分是否等于高熱度。matplotlib的核心價(jià)值是快和糙讓你在十分鐘內(nèi)確認(rèn)數(shù)據(jù)沒有異常。參數(shù)說明figsize(14, 10)控制總畫布尺寸四個(gè)子圖才不會(huì)擠在一起。dpi150是保存清晰度的下限低于這個(gè)值放大看會(huì)有鋸齒。tight_layout()會(huì)自動(dòng)調(diào)整子圖間距不調(diào)用的話標(biāo)題和坐標(biāo)軸會(huì)互相壓住。pyecharts的用法跟matplotlib完全不同。它生成的是HTML文件可以在瀏覽器里交互適合放進(jìn)交付文檔或可視化大屏。下面是用它畫地區(qū)Top10的完整代碼from pyecharts.charts import Bar from pyecharts import options as opts bar ( Bar() .add_xaxis(country_top10.index.tolist()) .add_yaxis(上榜數(shù)量, country_top10.values.tolist()) .set_global_opts( title_optsopts.TitleOpts(title豆瓣Top250制片地區(qū)分布), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate45)), ) ) bar.render(country_top10.html)邏輯說明pyecharts的鏈?zhǔn)秸{(diào)用把數(shù)據(jù)、坐標(biāo)軸和全局配置組合到一個(gè)對象里render輸出獨(dú)立HTML文件不需要本地起服務(wù)。rotate45是必須的Top10地區(qū)的名字比較長不旋轉(zhuǎn)會(huì)重疊。這個(gè)HTML可以直接發(fā)給任何人雙擊就能看hover有數(shù)值提示。pyecharts適合做交付但我不建議把全部圖表都交給它。散點(diǎn)圖和直方圖用pyecharts配置成本高matplotlib兩行就畫完要做可視化大屏?xí)r再把這幾個(gè)HTML用Page對象組合起來一個(gè)頁面放四五個(gè)圖切換tab就能看不同維度。順序很重要用matplotlib驗(yàn)證結(jié)論用pyecharts包裝展示不要反著來。6. 驗(yàn)證與進(jìn)階把一次性腳本變成可復(fù)用的數(shù)據(jù)管道6.1 用自檢腳本對全流程做斷言長度、唯一性、空值與范圍校驗(yàn)分析跑完不等于項(xiàng)目完工。我見過太多案例前一天清洗邏輯改了一個(gè)正則第二天重新生成圖表時(shí)少了5條數(shù)據(jù)沒人發(fā)現(xiàn)。給這個(gè)項(xiàng)目加一個(gè)自檢函數(shù)成本很低但能擋住大部分回歸問題。def validate(df: pd.DataFrame) - None: assert len(df) 250, f行數(shù)異常: {len(df)} assert df[rank].is_unique, rank列存在重復(fù) assert df[rating].between(8.0, 10.0).all(), 評分超出合理范圍 assert df[year].between(1930, 2030).all(), 年份超出合理范圍 missing df[[rating, year, title]].isna().sum().sum() assert missing 0, f關(guān)鍵字段存在空值: {missing} print(自檢通過: 250條數(shù)據(jù)完整字段無異常)邏輯說明自檢腳本只驗(yàn)證不修改任何一項(xiàng)失敗都會(huì)讓程序中斷并報(bào)出具體原因。行數(shù)校驗(yàn)是最基本的一道閘門防的是清洗階段誤刪數(shù)據(jù)rank唯一性校驗(yàn)防的是翻頁重復(fù)或去重失敗評分和年份區(qū)間校驗(yàn)防的是正則拆錯(cuò)導(dǎo)致數(shù)據(jù)漂移。參數(shù)說明assert后面的提示信息要具體寧可寫成“rank列存在重復(fù)”也不要只寫“數(shù)據(jù)異常”。between(8.0, 10.0)是左閉右閉豆瓣Top250的最低分是8.2不會(huì)誤傷。關(guān)鍵空值校驗(yàn)只查參與分析的列不查quote這類可有可無的字段這個(gè)設(shè)計(jì)跟第四章提到的空值坑呼應(yīng)上了。6.2 增量更新與自動(dòng)化從“跑一次”到“每周跑一次”如果這個(gè)項(xiàng)目只是跑一次出幾張圖那源碼組織成什么樣都無所謂但如果打算持續(xù)跟進(jìn)榜單變化就需要考慮增量更新。常見做法是給爬蟲腳本加一個(gè)force參數(shù)默認(rèn)讀緩存和已有CSV需要強(qiáng)制刷新時(shí)加上--force重新請求所有頁面并覆蓋CSV。我個(gè)人現(xiàn)在的習(xí)慣是清洗邏輯每改一次就順手把自檢函數(shù)跑一遍再?zèng)Q定要不要重新爬取。這個(gè)項(xiàng)目規(guī)模小不追求自動(dòng)化調(diào)度但把“自檢 緩存 參數(shù)化”這三個(gè)意識(shí)養(yǎng)成習(xí)慣后面接任何數(shù)據(jù)源都能復(fù)用這套骨架。我的項(xiàng)目文檔里會(huì)寫清楚三件事環(huán)境依賴怎么裝、腳本按什么順序運(yùn)行、哪些字段是清洗后新增的派生字段。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取