據(jù)項(xiàng)目源碼:從采集清洗到可視化全鏈路實(shí)戰(zhàn))
簡介這份資源是面向計(jì)算機(jī)相關(guān)專業(yè)學(xué)生與項(xiàng)目實(shí)戰(zhàn)學(xué)習(xí)者的Python數(shù)據(jù)采集與可視化完整項(xiàng)目包以南京二手房為分析對(duì)象可用于課程設(shè)計(jì)、期末大作業(yè)或畢業(yè)設(shè)計(jì)參考。項(xiàng)目圍繞二手房數(shù)據(jù)的抓取、清洗與可視化展開包含原始數(shù)據(jù)、清洗后數(shù)據(jù)及多版本編碼文件便于對(duì)比數(shù)據(jù)預(yù)處理前后的差異。壓縮包共157個(gè)文件約40.02MB其中18個(gè)py腳本承載采集與繪圖邏輯18個(gè)csv與15個(gè)html、11個(gè)js構(gòu)成數(shù)據(jù)與可視化頁面另有65個(gè)png圖表、pptx匯報(bào)文件及ini、txt等配置說明結(jié)構(gòu)完整、層次清晰。目前已有618人學(xué)習(xí)下載。代碼經(jīng)過嚴(yán)格調(diào)試下載即用可直接運(yùn)行并復(fù)現(xiàn)分析流程同時(shí)配套PPT資料方便整理答辯思路與展示成果適合需要快速搭建項(xiàng)目框架、理解數(shù)據(jù)采集到可視化全鏈路的學(xué)習(xí)者參考借鑒。1. 南京二手房數(shù)據(jù)項(xiàng)目從 CSV 到可視化這套源碼能省你多少時(shí)間如果你正在做 Python 課程設(shè)計(jì)或期末大作業(yè)選題是「數(shù)據(jù)采集 可視化分析」大概率會(huì)遇到一個(gè)尷尬局面爬蟲代碼能跑但數(shù)據(jù)清洗完就不知道怎么出圖或者圖表能畫但數(shù)據(jù)字段對(duì)不上、中文亂碼、坐標(biāo)軸擠成一團(tuán)。這套南京二手房項(xiàng)目源碼核心價(jià)值就在于把「采集 → 清洗 → 存儲(chǔ) → 可視化」整條鏈路串起來了而且附帶了從原始 CSV 到清洗后 CSV 的完整中間產(chǎn)物你能清楚看到每一步數(shù)據(jù)長什么樣。項(xiàng)目面向計(jì)算機(jī)相關(guān)專業(yè)的課程設(shè)計(jì)和畢業(yè)設(shè)計(jì)場(chǎng)景代碼經(jīng)過調(diào)試下載后按依賴裝好就能運(yùn)行。它不是一個(gè)只貼幾行爬蟲的 demo而是包含多份數(shù)據(jù)文件、可視化腳本和 PPT 資料的完整交付物。適合兩類人一是需要快速搭出可演示系統(tǒng)的同學(xué)二是想拿真實(shí)數(shù)據(jù)練 pandas 和 matplotlib 的入門者。下面我按實(shí)際拆包順序把數(shù)據(jù)文件、清洗邏輯、可視化實(shí)現(xiàn)和常見翻車點(diǎn)逐個(gè)講透。2. 數(shù)據(jù)文件與采集鏈路ershoufang-origin 到 clean 的字段演變2.1 原始 CSV 的編碼差異與字段結(jié)構(gòu)拿到壓縮包后第一件事不是急著跑代碼而是先看清數(shù)據(jù)文件的命名規(guī)律。項(xiàng)目里出現(xiàn)了ershoufang-origin-utf8.csv、ershoufang-origin-ansi.csv、ershoufang-clean-utf8-v1.1.csv、ershoufang-clean-ansi-v1.1.csv以及ershoufang - 20000.csv、ershoufang.csv這幾類文件。命名里的origin表示采集后的原始數(shù)據(jù)clean表示清洗后的版本utf8和ansi是兩種編碼格式v1.1是清洗規(guī)則迭代后的版本號(hào)。原始數(shù)據(jù)通常包含這些字段小區(qū)名稱、戶型、面積、朝向、樓層、裝修情況、掛牌價(jià)格、單價(jià)、區(qū)域、經(jīng)緯度等。南京二手房數(shù)據(jù)有個(gè)特點(diǎn)鼓樓、玄武、建鄴這幾個(gè)區(qū)的單價(jià)跨度很大從兩萬多到七八萬都有做可視化時(shí)如果不做區(qū)域分組圖表會(huì)被極端值拉扁。ershoufang - 20000.csv這個(gè)文件名里的 20000 大概率是采集條數(shù)或價(jià)格篩選閾值實(shí)際使用時(shí)先head看一眼確認(rèn)。import pandas as pd # 先探測(cè)編碼不要直接指定 utf-8ansi 文件用 utf-8 讀會(huì)報(bào) UnicodeDecodeError raw_utf8 pd.read_csv(ershoufang-origin-utf8.csv, encodingutf-8) print(raw_utf8.shape) print(raw_utf8.columns.tolist()) print(raw_utf8.head(3)) # ansi 編碼在 Windows 下常見用 gbk 或 gb18030 讀取 raw_ansi pd.read_csv(ershoufang-origin-ansi.csv, encodinggb18030) print(raw_ansi.shape)這段代碼的關(guān)鍵點(diǎn)是編碼探測(cè)。很多同學(xué)拿到 CSV 直接pd.read_csv不寫 encoding在 Windows 上默認(rèn)走 cp936遇到 utf-8 文件就亂碼在 Linux 或 Mac 上默認(rèn) utf-8遇到 ansi 文件又報(bào)錯(cuò)。穩(wěn)妥做法是先讀前幾行試編碼或者用chardet檢測(cè)。shape和columns是必看項(xiàng)確認(rèn)行數(shù)和字段名跟預(yù)期一致避免后面清洗時(shí)字段對(duì)不上。2.2 采集腳本的請(qǐng)求構(gòu)造與翻頁邏輯項(xiàng)目源碼里的采集部分常見做法是用requests配合BeautifulSoup或lxml解析列表頁。南京二手房平臺(tái)如鏈家、安居客的列表頁 URL 通常帶page參數(shù)翻頁就是改這個(gè)參數(shù)。請(qǐng)求頭里必須帶User-Agent否則大概率返回 403 或空頁面。采集頻率要控制time.sleep(1)到time.sleep(3)是常見區(qū)間太快會(huì)被限流太慢兩萬條數(shù)據(jù)要跑很久。import requests from bs4 import BeautifulSoup import time import csv headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } def fetch_page(page_num): url fhttps://nj.example.com/ershoufang/pg{page_num}/ resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 if resp.status_code ! 200: print(fpage {page_num} status {resp.status_code}) return [] soup BeautifulSoup(resp.text, lxml) items soup.select(.sellListContent li) rows [] for item in items: title item.select_one(.title a) price item.select_one(.totalPrice span) unit item.select_one(.unitPrice span) rows.append({ title: title.get_text(stripTrue) if title else , total_price: price.get_text(stripTrue) if price else , unit_price: unit.get_text(stripTrue) if unit else }) return rows all_rows [] for pg in range(1, 101): rows fetch_page(pg) all_rows.extend(rows) print(fpage {pg} got {len(rows)} rows) time.sleep(2) with open(ershoufang.csv, w, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnames[title, total_price, unit_price]) writer.writeheader() writer.writerows(all_rows)這段采集代碼的邏輯說明headers里的User-Agent是必須項(xiàng)缺了會(huì)被識(shí)別為腳本請(qǐng)求timeout10防止某個(gè)請(qǐng)求卡死拖垮整個(gè)循環(huán)resp.encoding utf-8顯式指定編碼避免中文亂碼select選擇器要根據(jù)目標(biāo)頁面實(shí)際 DOM 結(jié)構(gòu)調(diào)整不同平臺(tái)類名不一樣。翻頁范圍range(1, 101)對(duì)應(yīng)一百頁每頁約二十條總量兩千條左右如果要湊兩萬條需要擴(kuò)大范圍或按區(qū)域拆分采集。time.sleep(2)是禮貌間隔實(shí)際跑的時(shí)候根據(jù)目標(biāo)站點(diǎn)響應(yīng)速度調(diào)整但不要低于 1 秒。提示采集前先手動(dòng)翻幾頁確認(rèn) URL 規(guī)律和 DOM 結(jié)構(gòu)不要直接套用示例選擇器。平臺(tái)改版后類名會(huì)變這是最常見的翻車原因。3. 數(shù)據(jù)清洗與字段處理從原始 CSV 到 clean-v1.1 的轉(zhuǎn)換規(guī)則3.1 價(jià)格、面積字段的數(shù)值化與異常值處理原始數(shù)據(jù)里的價(jià)格和面積通常是帶單位的字符串比如「320萬」「89.5平米」「3室2廳」。做可視化之前必須把這些轉(zhuǎn)成數(shù)值類型否則 matplotlib 沒法畫。清洗的核心步驟包括去掉單位、轉(zhuǎn) float、處理缺失值、剔除極端異常值。clean-v1.1這個(gè)版本號(hào)說明清洗規(guī)則至少迭代過一次常見改動(dòng)是增加了異常值過濾或修正了某個(gè)字段的解析邏輯。import pandas as pd import numpy as np df pd.read_csv(ershoufang-origin-utf8.csv, encodingutf-8) # 價(jià)格字段去掉「萬」字轉(zhuǎn) float df[total_price] df[total_price].astype(str).str.replace(萬, , regexFalse) df[total_price] pd.to_numeric(df[total_price], errorscoerce) # 面積字段去掉「平米」等后綴 df[area] df[area].astype(str).str.extract(r(\d\.?\d*)) df[area] pd.to_numeric(df[area], errorscoerce) # 單價(jià)字段去掉「元/平米」并轉(zhuǎn)數(shù)值 df[unit_price] df[unit_price].astype(str).str.replace(元/平米, , regexFalse) df[unit_price] pd.to_numeric(df[unit_price], errorscoerce) # 剔除面積或價(jià)格為空的行 df df.dropna(subset[total_price, area, unit_price]) # 用 IQR 方法過濾單價(jià)異常值 q1 df[unit_price].quantile(0.25) q3 df[unit_price].quantile(0.75) iqr q3 - q1 lower q1 - 1.5 * iqr upper q3 1.5 * iqr df df[(df[unit_price] lower) (df[unit_price] upper)] print(fafter clean: {df.shape}) df.to_csv(ershoufang-clean-utf8-v1.1.csv, indexFalse, encodingutf-8)邏輯說明str.replace去掉單位后pd.to_numeric配合errorscoerce把無法轉(zhuǎn)換的值變成 NaN再用dropna剔除。str.extract用正則從「89.5平米」里抽出數(shù)字部分比直接 replace 更穩(wěn)因?yàn)槊娣e字段的格式可能不統(tǒng)一。IQR 過濾是常見的異常值處理方式把超出 1.5 倍四分位距的數(shù)據(jù)去掉避免個(gè)別天價(jià)房源把圖表拉變形。參數(shù)lower和upper可以根據(jù)實(shí)際數(shù)據(jù)分布調(diào)整如果發(fā)現(xiàn)過濾后數(shù)據(jù)量驟減說明閾值太緊改成 3 倍 IQR 試試。3.2 區(qū)域字段標(biāo)準(zhǔn)化與戶型拆分南京的行政區(qū)劃字段在原始數(shù)據(jù)里可能有多種寫法比如「鼓樓」「鼓樓區(qū)」「南京鼓樓」混在一起。做區(qū)域維度的可視化之前需要統(tǒng)一成標(biāo)準(zhǔn)名稱。戶型字段「3室2廳」可以拆成「室數(shù)」和「廳數(shù)」兩個(gè)數(shù)值列方便后續(xù)做戶型分布統(tǒng)計(jì)。# 區(qū)域標(biāo)準(zhǔn)化去掉「區(qū)」「南京」等前后綴 df[district] df[district].astype(str).str.replace(南京, , regexFalse) df[district] df[district].str.replace(區(qū), , regexFalse) df[district] df[district].str.strip() # 戶型拆分從「3室2廳」提取室數(shù)和廳數(shù) df[room] df[layout].astype(str).str.extract(r(\d)室) df[hall] df[layout].astype(str).str.extract(r(\d)廳) df[room] pd.to_numeric(df[room], errorscoerce) df[hall] pd.to_numeric(df[hall], errorscoerce) # 按區(qū)域統(tǒng)計(jì)均價(jià) district_avg df.groupby(district)[unit_price].mean().sort_values(ascendingFalse) print(district_avg)區(qū)域標(biāo)準(zhǔn)化這步看起來簡單但實(shí)際數(shù)據(jù)里經(jīng)常有「江寧」「江寧區(qū)」「南京江寧區(qū)」三種寫法并存不統(tǒng)一的話 groupby 會(huì)分成三組統(tǒng)計(jì)結(jié)果就錯(cuò)了。戶型拆分用正則提取數(shù)字errorscoerce處理「未知」「暫無」這類非標(biāo)準(zhǔn)值。groupby后的mean()默認(rèn)跳過 NaN但如果某個(gè)區(qū)域全是 NaN結(jié)果里會(huì)出現(xiàn)空值需要額外處理。注意清洗后的文件同時(shí)保留了 utf8 和 ansi 兩個(gè)版本這是為了兼容不同操作系統(tǒng)。Windows 下用 Excel 打開 utf8 文件可能亂碼ansi 版本就是給這種情況準(zhǔn)備的。寫代碼時(shí)統(tǒng)一用 utf8 版本避免編碼問題。4. 可視化實(shí)現(xiàn)與圖表選型matplotlib 出圖的關(guān)鍵參數(shù)4.1 區(qū)域均價(jià)柱狀圖與橫坐標(biāo)擁擠處理區(qū)域均價(jià)對(duì)比是最常見的可視化需求用柱狀圖最直觀。但南京有十幾個(gè)區(qū)如果全畫上去橫坐標(biāo)標(biāo)簽會(huì)擠成一團(tuán)這是 Python 畫圖的高頻問題。解決辦法有三種旋轉(zhuǎn)標(biāo)簽、只畫 Top N、或者用橫向柱狀圖。我一般用橫向柱狀圖區(qū)域名放縱軸可讀性最好。import matplotlib.pyplot as plt import matplotlib # 設(shè)置中文字體Windows 用 SimHeiMac 用 Arial Unicode MS matplotlib.rcParams[font.sans-serif] [SimHei] matplotlib.rcParams[axes.unicode_minus] False district_avg df.groupby(district)[unit_price].mean().sort_values(ascendingTrue) fig, ax plt.subplots(figsize(10, 6)) bars ax.barh(district_avg.index, district_avg.values, color#4C72B0) ax.set_xlabel(均價(jià)元/平米) ax.set_title(南京各區(qū)二手房均價(jià)對(duì)比) # 在柱狀圖末端標(biāo)注數(shù)值 for bar in bars: width bar.get_width() ax.text(width, bar.get_y() bar.get_height()/2, f{width:.0f}, haleft, vacenter, fontsize9) plt.tight_layout() plt.savefig(district_avg.png, dpi150) plt.show()邏輯說明font.sans-serif設(shè)置中文字體不設(shè)的話中文會(huì)顯示成方塊這是新手最常遇到的玄學(xué)問題。axes.unicode_minus False解決負(fù)號(hào)顯示異常。barh是橫向柱狀圖區(qū)域名在縱軸不會(huì)擁擠。ax.text在每根柱子末端標(biāo)數(shù)值haleft讓文字左對(duì)齊到柱子末端。tight_layout自動(dòng)調(diào)整邊距防止標(biāo)簽被裁掉。dpi150保證保存的圖片清晰度夠用。4.2 價(jià)格分布直方圖與戶型餅圖除了區(qū)域?qū)Ρ葍r(jià)格分布和戶型占比也是課程設(shè)計(jì)里常要求的圖表。價(jià)格分布用直方圖看集中區(qū)間戶型用餅圖看占比。直方圖的bins參數(shù)決定柱子數(shù)量太少看不出分布細(xì)節(jié)太多又顯得雜亂一般設(shè) 20 到 30 之間。fig, axes plt.subplots(1, 2, figsize(14, 5)) # 價(jià)格分布直方圖 axes[0].hist(df[unit_price], bins25, color#55A868, edgecolorwhite) axes[0].set_xlabel(單價(jià)元/平米) axes[0].set_ylabel(房源數(shù)量) axes[0].set_title(南京二手房單價(jià)分布) # 戶型分布餅圖只取前 6 種戶型 layout_counts df[layout].value_counts().head(6) axes[1].pie(layout_counts.values, labelslayout_counts.index, autopct%1.1f%%, startangle90) axes[1].set_title(戶型占比分布) plt.tight_layout() plt.savefig(price_layout.png, dpi150) plt.show()直方圖的bins25是經(jīng)驗(yàn)值數(shù)據(jù)量在兩萬條左右時(shí)這個(gè)粒度比較合適。edgecolorwhite讓柱子之間有白色分隔線視覺上更清晰。餅圖的autopct顯示百分比startangle90讓第一塊從十二點(diǎn)方向開始符合閱讀習(xí)慣。head(6)只取前六種戶型避免餅圖切片太多看不清。提示如果橫坐標(biāo)標(biāo)簽仍然擁擠可以用plt.xticks(rotation45)旋轉(zhuǎn)或者用ax.xaxis.set_major_locator控制刻度密度。但橫向柱狀圖通常比旋轉(zhuǎn)標(biāo)簽更優(yōu)雅。5. 避坑與排查編碼、字段、圖表三類高頻問題5.1 中文亂碼現(xiàn)象是圖表中文變方塊原因是字體未設(shè)置現(xiàn)象matplotlib 畫出來的圖里標(biāo)題和軸標(biāo)簽的中文全部顯示成方塊或問號(hào)。原因matplotlib 默認(rèn)字體不含中文字形需要顯式指定支持中文的字體。解決在繪圖前設(shè)置matplotlib.rcParams[font.sans-serif]Windows 用SimHeiMac 用Arial Unicode MSLinux 用WenQuanYi Micro Hei。如果設(shè)置后仍不生效檢查字體是否真的安裝在系統(tǒng)里可以用matplotlib.font_manager.findfont確認(rèn)。5.2 CSV 讀取報(bào)錯(cuò)現(xiàn)象是 UnicodeDecodeError原因是編碼不匹配現(xiàn)象pd.read_csv報(bào)UnicodeDecodeError: utf-8 codec cant decode byte。原因文件實(shí)際編碼是 ansi/gbk但讀取時(shí)指定了 utf-8或者反過來。解決先用chardet.detect檢測(cè)文件編碼或者直接用encodinggb18030讀取 ansi 版本。gb18030 兼容 gbk 和 gb2312覆蓋面更廣。如果不想改代碼用文本編輯器把文件另存為 utf-8 也行。5.3 字段提取為空現(xiàn)象是正則匹配后整列 NaN原因是字段格式與預(yù)期不符現(xiàn)象用str.extract提取面積或戶型后整列都是 NaN。原因原始數(shù)據(jù)里該字段的格式跟正則不匹配比如面積寫的是「89.5㎡」而不是「89.5平米」或者戶型寫的是「三室兩廳」而不是「3室2廳」。解決先df[area].head(20)看實(shí)際格式再調(diào)整正則。中文數(shù)字和阿拉伯?dāng)?shù)字混用的情況需要額外做映射轉(zhuǎn)換。5.4 圖表保存空白現(xiàn)象是 savefig 出來的圖片是白圖原因是 show 和 savefig 順序問題現(xiàn)象plt.savefig保存的圖片打開后是空白但plt.show彈出的窗口正常。原因在某些后端下plt.show()會(huì)清空當(dāng)前 figure如果先 show 再 savefig保存的就是空?qǐng)D。解決先savefig再show或者用fig.savefig代替plt.savefig。這個(gè)坑在 Jupyter Notebook 里尤其常見。5.5 采集被封現(xiàn)象是請(qǐng)求返回 403 或驗(yàn)證碼頁面原因是請(qǐng)求頻率過高或缺少請(qǐng)求頭現(xiàn)象采集腳本跑了幾十頁后開始返回 403或者頁面內(nèi)容變成驗(yàn)證碼。原因請(qǐng)求頻率太高被目標(biāo)站點(diǎn)識(shí)別為異常流量或者User-Agent缺失被直接拒絕。解決增大time.sleep間隔到 3 到 5 秒補(bǔ)全headers里的User-Agent、Referer等字段必要時(shí)輪換 User-Agent。如果仍然被封考慮降低采集量或分時(shí)段采集。6. 進(jìn)階技巧用 pyecharts 做交互式可視化與 PPT 聯(lián)動(dòng)課程設(shè)計(jì)如果只交靜態(tài)圖演示效果會(huì)打折扣。這套項(xiàng)目附帶了 PPT 資料說明作者考慮了答辯場(chǎng)景。我的習(xí)慣是在 matplotlib 靜態(tài)圖之外再用 pyecharts 做一版交互式圖表答辯時(shí)鼠標(biāo)懸停能看具體數(shù)值觀感提升明顯。pyecharts 的安裝用pip install pyecharts生成的是 HTML 文件瀏覽器直接打開就能交互。from pyecharts.charts import Bar, Pie from pyecharts import options as opts # 區(qū)域均價(jià)交互柱狀圖 district_avg df.groupby(district)[unit_price].mean().sort_values(ascendingFalse).head(10) bar ( Bar() .add_xaxis(district_avg.index.tolist()) .add_yaxis(均價(jià), district_avg.values.round(0).tolist()) .set_global_opts( title_optsopts.TitleOpts(title南京各區(qū)二手房均價(jià) Top10), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate30)), yaxis_optsopts.AxisOpts(name元/平米) ) ) bar.render(district_avg_interactive.html)這段代碼的邏輯Bar()創(chuàng)建柱狀圖對(duì)象add_xaxis和add_yaxis分別設(shè)置橫軸和縱軸數(shù)據(jù)set_global_opts配置標(biāo)題和軸標(biāo)簽。rotate30旋轉(zhuǎn)橫軸標(biāo)簽防止擁擠跟 matplotlib 的思路一致但參數(shù)寫法不同。render輸出 HTML 文件可以直接嵌入 PPT 的超鏈接里答辯時(shí)點(diǎn)擊跳轉(zhuǎn)。pyecharts 和 matplotlib 的選型區(qū)別matplotlib 適合生成靜態(tài)圖片插入論文或 PPTpyecharts 適合現(xiàn)場(chǎng)演示交互。如果時(shí)間充裕兩個(gè)都做如果只選一個(gè)答辯場(chǎng)景優(yōu)先 pyecharts論文場(chǎng)景優(yōu)先 matplotlib。另外PPT 里的圖表建議用清洗后的clean-v1.1數(shù)據(jù)重新生成一遍保證數(shù)據(jù)口徑一致避免 PPT 里的數(shù)字和代碼跑出來的對(duì)不上。還有一個(gè)細(xì)節(jié)pyecharts 生成的 HTML 文件默認(rèn)引用在線 CDN 的 JS 庫如果答辯現(xiàn)場(chǎng)網(wǎng)絡(luò)不穩(wěn)定圖表可能加載不出來。解決辦法是用pyecharts的離線資源模式或者提前把 HTML 在本地瀏覽器打開確認(rèn)能正常渲染。我一般會(huì)在答辯前一天把所有 HTML 文件在目標(biāo)電腦上過一遍確認(rèn)字體和交互都正常。從那以后我每次交付可視化項(xiàng)目都會(huì)強(qiáng)制走一遍「換一臺(tái)電腦打開 HTML」的流程因?yàn)榫幋a、字體、CDN 這三個(gè)東西在不同機(jī)器上的表現(xiàn)真的不一樣。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取