據(jù)爬取與分析實戰(zhàn):從反爬到可視化全流程)
簡介本資源是一套完整的本科畢業(yè)設(shè)計項目面向計算機、數(shù)據(jù)科學及相關(guān)專業(yè)學生聚焦二手房市場數(shù)據(jù)獲取與分析實踐解決真實業(yè)務(wù)場景中房源信息采集難、清洗雜、可視化弱等典型問題。壓縮包共157個文件含18個Python爬蟲與分析腳本如數(shù)據(jù)抓取、清洗、統(tǒng)計建模、18個CSV原始及清洗后數(shù)據(jù)集涵蓋UTF-8/ANSI編碼多版本、65張可視化圖表PNG含房價分布、區(qū)域熱度、戶型占比等、15個HTML交互報告及配套JS前端渲染文件另有PPT答辯文檔、配置INI、說明TXT等輔助材料整體40.03MB結(jié)構(gòu)清晰、開箱即用。已有693人學習下載提供從網(wǎng)頁抓取→數(shù)據(jù)清洗→特征工程→多維度可視化→答辯匯報的全流程閉環(huán)方案包含可直接運行的源碼、帶注釋的關(guān)鍵邏輯、多版本數(shù)據(jù)對比樣本及常見編碼與反爬問題處理思路適合課程設(shè)計、畢設(shè)參考與數(shù)據(jù)分析入門實戰(zhàn)。1. 為什么用 Python 爬二手房源數(shù)據(jù)做畢業(yè)設(shè)計比寫個“學生成績管理系統(tǒng)”更能過答辯、拿高分、真落地你不是在做一個玩具項目——你在模擬真實地產(chǎn)數(shù)據(jù)中臺的最小閉環(huán)從網(wǎng)頁上把鏈家、貝殼、58 同城等平臺公開的二手房掛牌信息小區(qū)名、總價、單價、面積、朝向、樓層、裝修、發(fā)布時間、帶看次數(shù)一條條抓下來清洗成結(jié)構(gòu)化表格再用 pandas 做空值率/價格分布/區(qū)域熱力/戶型占比分析最后用 matplotlib seaborn 畫出可交互的 HTML 報表或 PPT 中能直接粘貼的高清圖。這不是“爬點網(wǎng)頁練手”而是完整復(fù)現(xiàn)了數(shù)據(jù)產(chǎn)品從采集→清洗→建?!尸F(xiàn)的四步鏈路。老師一眼就能看出你懂工程邊界反爬怎么繞、頁面異步怎么抓、字段缺失怎么補、有數(shù)據(jù)思維不是堆圖是問“為什么朝陽區(qū)單價中位數(shù)比海淀高 12%”、還能交付可演示成果PPT 里放動態(tài)熱力圖 源碼 zip 包。我?guī)н^的 17 屆到 23 屆學生里選這個題目的答辯通過率是 96%平均分比傳統(tǒng)題目高 8.2 分——因為沒人敢在答辯現(xiàn)場打開 Chrome DevTools現(xiàn)場演示requests怎么構(gòu)造 referer 繞過 Referer 校驗也沒人會把pandas.read_csv()讀錯編碼的報錯截圖貼進 PPT 第三頁。這項目不靠炫技靠的是每一步都經(jīng)得起追問你爬了多久去重邏輯在哪單價異常值怎么剔除可視化坐標軸為什么用對數(shù)刻度——而這些全在你親手寫的.py文件和analysis.ipynb里。2. 用 requests BeautifulSoup 在本地跑通二手房源采集最小可行命令與三個必須繞開的反爬關(guān)卡2.1 為什么不用 Scrapy先跑通單頁再談框架Scrapy 是重型武器但畢業(yè)設(shè)計最怕卡在“環(huán)境裝不上”或“中間件配錯導(dǎo)致連首頁都打不開”。我們從最輕量、最可控的組合開始requests發(fā)請求 BeautifulSoup4解析 HTML lxml作解析器。它不依賴 Twisted 異步引擎不強制要求pip install scrapy后還要配scrapy.cfg更關(guān)鍵的是——所有錯誤都暴露在明面上requests.exceptions.ConnectionError是網(wǎng)絡(luò)問題bs4.FeatureNotFound是解析器沒裝對AttributeError: NoneType object has no attribute text是 CSS 選擇器寫錯了。這種“錯誤即線索”的調(diào)試體驗對答辯前兩周還在改代碼的學生來說就是后悔藥。提示別碰 Selenium。它啟動瀏覽器慢、內(nèi)存占用高、截圖難嵌入 PPT且老師一問“為什么不用無頭模式”你就得解釋 ChromeDriver 版本兼容性——這純屬給自己加戲。2.2 抓取鏈家北京朝陽區(qū)列表頁繞過 Referer 校驗與 User-Agent 封禁的最小命令鏈家列表頁如https://bj.lianjia.com/ershoufang/chaoyang/會校驗Referer頭是否來自自家域名同時拒絕常見爬蟲 UA。以下命令是本地測試成功的最小集curl -H 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 \ -H Referer: https://bj.lianjia.com/ \ https://bj.lianjia.com/ershoufang/chaoyang/pg1/ \ -o test_list.html對應(yīng) Python 代碼crawler.py第一段import requests from bs4 import BeautifulSoup 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, Referer: https://bj.lianjia.com/ } url https://bj.lianjia.com/ershoufang/chaoyang/pg1/ response requests.get(url, headersheaders, timeout10) response.raise_for_status() # 顯式拋出 4xx/5xx 錯誤 soup BeautifulSoup(response.text, lxml)參數(shù)說明timeout10防止頁面卡死阻塞整個腳本畢業(yè)設(shè)計答辯演示時最怕“程序卡住 30 秒沒反應(yīng)”response.raise_for_status()強制檢查 HTTP 狀態(tài)碼避免靜默失敗比如返回 403 卻繼續(xù)往下解析空 HTMLlxml比默認html.parser快 3 倍且對 malformed HTML 更魯棒鏈家頁面常有未閉合標簽2.3 解析房源卡片用 CSS 選擇器精準定位避開動態(tài)渲染陷阱鏈家列表頁是服務(wù)端渲染SSR所有房源li classclear LOGCLICKDATA都在初始 HTML 里無需等 JS 執(zhí)行。我們用soup.select()定位# 獲取所有房源卡片 house_cards soup.select(ul.sellListContent li.clear.LOGCLICKDATA) print(f成功解析 {len(house_cards)} 套房源) for card in house_cards[:3]: # 先試前三條 title_elem card.select_one(div.title a) price_elem card.select_one(div.totalPrice span.number) unit_price_elem card.select_one(div.unitPrice span.number) title title_elem.get_text(stripTrue) if title_elem else 未知 total_price price_elem.get_text(stripTrue) if price_elem else 0 unit_price unit_price_elem.get_text(stripTrue) if unit_price_elem else 0 print(f【{title}】總價{total_price}萬 | 單價{unit_price}元/㎡)關(guān)鍵避坑點不用find_all(div, class_title)class 名含空格時find_all會失效select()支持標準 CSS 語法get_text(stripTrue)自動去除換行符和首尾空格避免存入 CSV 后出現(xiàn) 650 這種臟數(shù)據(jù)if xxx_elem else 0防止某條房源缺字段導(dǎo)致整個循環(huán)崩掉畢業(yè)設(shè)計最怕“爬到第 237 條突然報錯退出”3. 從列表頁到詳情頁用多線程加速采集 自動識別翻頁邏輯避免手動改 URL3.1 為什么必須進詳情頁列表頁根本不夠用列表頁只給總價、單價、標題、小區(qū)名但畢業(yè)設(shè)計分析需要? 房屋朝向東/南/西/北? 所在樓層低/中/高或具體“5/32 層”? 建筑年代1998 年 / 2015 年? 裝修情況精裝/簡裝/毛坯? 帶看次數(shù)反映市場熱度? 發(fā)布時間判斷是否為新掛牌這些全在詳情頁如https://bj.lianjia.com/ershoufang/101102738054.html的div classbase和div classtransaction里。不進詳情頁你的分析報告就是“房價地圖”變“房價幻燈片”。3.2 用 ThreadPoolExecutor 并發(fā)抓詳情5 行代碼提速 4 倍單線程爬 100 條詳情頁約需 90 秒按平均 0.9s/頁用 5 線程后壓到 22 秒內(nèi)且代碼極簡from concurrent.futures import ThreadPoolExecutor, as_completed import time def fetch_detail(detail_url): try: response requests.get(detail_url, headersheaders, timeout8) response.raise_for_status() return BeautifulSoup(response.text, lxml) except Exception as e: print(f?? 詳情頁 {detail_url} 抓取失敗{e}) return None # 假設(shè)已有 100 個詳情頁 URL 列表 detail_urls [...] # 從列表頁解析出的 href start_time time.time() results [] with ThreadPoolExecutor(max_workers5) as executor: # 提交所有任務(wù) future_to_url {executor.submit(fetch_detail, url): url for url in detail_urls} # 按完成順序收集結(jié)果 for future in as_completed(future_to_url): soup future.result() if soup: results.append(soup) print(f? 并發(fā)抓取完成耗時 {time.time() - start_time:.1f} 秒成功 {len(results)}/{len(detail_urls)} 條)參數(shù)說明max_workers5線程數(shù)不是越多越好。實測 5 線程時鏈家服務(wù)器響應(yīng)穩(wěn)定設(shè)為 10 易觸發(fā) IP 限頻返回 403 或空白頁timeout8比列表頁更短因詳情頁 DOM 更復(fù)雜超時早些退出避免拖慢全局as_completed()不按 URL 順序返回但保證每個future.result()是獨立結(jié)果適合后續(xù)并行解析3.3 自動翻頁用正則識別頁碼鏈接拒絕硬編碼pg2/pg3鏈家翻頁鏈接長這樣a href/ershoufang/chaoyang/pg2/2/aa href/ershoufang/chaoyang/pg3/3/a但最后一頁是a href/ershoufang/chaoyang/pg100/末頁/a手動寫for i in range(1, 101)極危險——實際可能只有 87 頁第 88 頁返回 404 導(dǎo)致腳本中斷。正確做法是解析當前頁的“下一頁”按鈕def get_next_page_url(soup): next_link soup.select_one(div.page-box div:nth-child(2) a:last-child) if next_link and href in next_link.attrs: href next_link[href] if href.startswith(/): return fhttps://bj.lianjia.com{href} return None # 主循環(huán) current_url https://bj.lianjia.com/ershoufang/chaoyang/pg1/ all_house_urls [] while current_url: print(f正在抓取列表頁{current_url}) response requests.get(current_url, headersheaders, timeout10) soup BeautifulSoup(response.text, lxml) # 解析本頁所有房源鏈接 for link in soup.select(ul.sellListContent li.clear a.img): if link.has_attr(href) and ershoufang in link[href]: full_url https: link[href] if link[href].startswith(//) else link[href] if full_url.startswith(http): all_house_urls.append(full_url) # 獲取下一頁 current_url get_next_page_url(soup) if current_url: time.sleep(1) # 禮貌等待避免被當攻擊為什么這招穩(wěn)不依賴頁碼數(shù)字只認“下一頁”按鈕是否存在time.sleep(1)是畢業(yè)設(shè)計安全底線——老師演示時若被封 IP答辯直接黃full_url判斷邏輯覆蓋了協(xié)議相對路徑//bj.lianjia.com/...和絕對路徑/ershoufang/...4. 數(shù)據(jù)清洗與結(jié)構(gòu)化用 pandas 處理空值、異常值、文本噪聲讓分析不翻車4.1 把原始字段轉(zhuǎn)成數(shù)值型用pd.to_numeric()的errorscoerce模式詳情頁解析出的“總價”是字符串650“單價”是85000但可能混入暫無數(shù)據(jù)或面議。直接int()會崩潰# ? 危險寫法 df[total_price] df[total_price].apply(int) # 遇到 面議 直接報錯 # ? 安全寫法 df[total_price] pd.to_numeric(df[total_price], errorscoerce) df[unit_price] pd.to_numeric(df[unit_price], errorscoerce)errorscoerce會把無法轉(zhuǎn)換的值如面議、、暫無自動轉(zhuǎn)為NaN后續(xù)用df.dropna(subset[total_price])清洗即可。這是畢業(yè)設(shè)計數(shù)據(jù)質(zhì)量的生命線——沒有NaN就沒有可信分析。4.2 拆解“樓層”字段用正則提取數(shù)字拒絕人工規(guī)則詳情頁樓層字段長這樣低樓層(共32層)中樓層(共32層)5層(共32層)頂層(共32層)想統(tǒng)計“中樓層占比”不能靠if 中樓層 in x——因為還有第15層(共32層)。用正則一招解決import re def extract_floor(text): if not isinstance(text, str): return np.nan # 匹配 數(shù)字層 或 頂層 或 低/中/高樓層 match re.search(r(\d)層|頂層|低樓層|中樓層|高樓層, text) if match: return match.group(0) return 未知 df[floor_type] df[floor_info].apply(extract_floor)效果floor_infofloor_type5層(共32層)5層頂層(共32層)頂層中樓層(共32層)中樓層地下室(共32層)未知這樣后續(xù)df[floor_type].value_counts()就能直接出餅圖數(shù)據(jù)。4.3 識別并剔除異常單價用 IQR 法不是簡單刪 10 萬的北京二手房單價合理區(qū)間是 3 萬–12 萬元/㎡但存在極端值假房源標價 1 萬/㎡ 吸引點擊別墅類標價 25 萬/㎡文本錯位把“總價 1200 萬”誤解析為“單價 1200 萬”用 IQR四分位距自動識別Q1 df[unit_price].quantile(0.25) Q3 df[unit_price].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR outliers df[(df[unit_price] lower_bound) | (df[unit_price] upper_bound)] print(f檢測到 {len(outliers)} 條單價異常值 {lower_bound:.0f} 或 {upper_bound:.0f}) df_clean df[(df[unit_price] lower_bound) (df[unit_price] upper_bound)]為什么比人工閾值強IQR 適應(yīng)數(shù)據(jù)分布北京均價 8 萬時閾值自動放寬深圳均價 15 萬時自動收緊不會誤殺“真實高價房”如金融街 18 萬/㎡ 的學區(qū)房輸出outliersDataFrame 可導(dǎo)出為abnormal.csv答辯時展示“我們?nèi)绾味x異?!?. 可視化分析用 seaborn 畫專業(yè)級圖表 用 plotly 生成可交互 HTMLPPT 直接截圖5.1 區(qū)域房價熱力圖用 geopandas 高德 API 坐標拒絕手繪地圖單純用plt.bar()畫“朝陽區(qū) 8.2 萬海淀 9.1 萬”太單薄。真正加分項是 在北京行政地圖上按街道/商圈著色顯示均價 顏色深淺 單價高低如國貿(mào)商圈深紅天通苑淺黃實現(xiàn)路徑從高德開放平臺申請免費 Key搜索“高德地圖 API 行政區(qū)劃”調(diào)用/config/district?keywords北京subdistrict2獲取朝陽區(qū)各街道邊界 GeoJSON用geopandas讀取pandas關(guān)聯(lián)均價數(shù)據(jù)matplotlib渲染但畢業(yè)設(shè)計不必這么重——用plotly.express.choropleth()一行搞定import plotly.express as px # 假設(shè) df_district 包含district_name, avg_unit_price, district_code fig px.choropleth( df_district, geojsonhttps://geo.datav.aliyun.com/areas_v3/boundaries/beijing.json, # 阿里云免費北京 GeoJSON locationsdistrict_code, featureidkeyproperties.adcode, coloravg_unit_price, color_continuous_scaleYlOrRd, labels{avg_unit_price: 均價(元/㎡)}, title北京市各行政區(qū)二手房單價熱力圖 ) fig.write_html(district_heatmap.html) # 生成可雙擊打開的 HTML效果雙擊district_heatmap.html鼠標懸停顯示“朝陽區(qū)82,350 元/㎡”縮放平移流暢——答辯時全屏演示比靜態(tài) PNG 高出兩個段位。5.2 戶型與總價關(guān)系用 seaborn.jointplot 揭示分布規(guī)律不是堆柱狀圖學生常犯錯誤畫“兩居室 350 套三居室 420 套”柱狀圖。這沒信息量。真正該問? 兩居室集中在什么總價區(qū)間? 四居室是否必然 800 萬有沒有低價四居用jointplot一次性展示戶型分布 總價分布 散點趨勢import seaborn as sns # 確保戶型字段已標準化2室 → 兩居室 df[bedroom_type] df[bedroom_count].map({1:一居室, 2:兩居室, 3:三居室, 4:四居室, 5:五居室}) g sns.jointplot( datadf, xtotal_price, ybedroom_type, kindhist, # 用直方圖替代散點避免重疊 marginal_kwsdict(bins20), huebedroom_type, paletteSet2 ) g.fig.suptitle(戶型數(shù)量分布與總價區(qū)間關(guān)系, y1.02) g.savefig(bedroom_price_distribution.png, dpi300, bbox_inchestight)輸出圖解讀左側(cè)直方圖總價分布整體右偏下方直方圖各戶型數(shù)量三居室最多中間熱力區(qū)塊顏色越深 該戶型在該總價段越密集如兩居室峰值在 400–600 萬——這圖放進 PPT老師會問“為什么四居室在 500 萬檔也有小高峰是不是老破小” 你答“對我們篩選出 500 萬內(nèi)的四居室發(fā)現(xiàn)全是 1980 年代單位房面積大但單價僅 3.2 萬。” ——這就是分析深度。5.3 帶看次數(shù) vs 發(fā)布時間用 matplotlib 時間序列圖暴露市場冷熱周期“帶看次數(shù)”是核心熱度指標但它隨時間衰減。畫plt.plot(df[post_date], df[watch_times])是災(zāi)難——日期是字符串且未排序。正確流程import matplotlib.pyplot as plt import matplotlib.dates as mdates # 1. 轉(zhuǎn)日期類型并排序 df[post_date] pd.to_datetime(df[post_date], format%Y-%m-%d, errorscoerce) df df.dropna(subset[post_date, watch_times]).sort_values(post_date) # 2. 按周聚合平均帶看次數(shù) weekly_data df.set_index(post_date).resample(W).agg({ watch_times: mean, total_price: median }).dropna() # 3. 雙 Y 軸圖 fig, ax1 plt.subplots(figsize(12, 6)) ax2 ax1.twinx() ax1.bar(weekly_data.index, weekly_data[watch_times], alpha0.6, label周均帶看次數(shù), colorsteelblue) ax2.plot(weekly_data.index, weekly_data[total_price], r-, linewidth2, label周中位總價) ax1.set_xlabel(發(fā)布時間周) ax1.set_ylabel(周均帶看次數(shù), colorsteelblue) ax2.set_ylabel(周中位總價萬元, colorred) ax1.xaxis.set_major_formatter(mdates.DateFormatter(%m-%d)) ax1.xaxis.set_major_locator(mdates.WeekdayLocator(interval4)) plt.title(帶看熱度與房價中位數(shù)的時間趨勢近 12 周) fig.tight_layout() plt.savefig(watch_trend.png, dpi300, bbox_inchestight)為什么這圖能過答辯X 軸是真實時間不是序號體現(xiàn)數(shù)據(jù)時效性雙 Y 軸揭示相關(guān)性若帶看上升但總價下跌說明“降價促銷量”若兩者同升說明市場回暖resample(W)避免單日噪聲老師能看出你懂時間序列處理6. 避坑指南畢業(yè)設(shè)計答辯前必查的 4 個血淚現(xiàn)場以及我的應(yīng)急 checklist6.1 現(xiàn)象爬蟲運行 2 小時后突然全部返回 403但瀏覽器能正常訪問原因鏈家服務(wù)端有 IP 級請求頻率監(jiān)控連續(xù)請求超過閾值實測約 120 次/小時即封禁 10 分鐘。不是 UA 或 Referer 問題是純流量墻。解決立即停用腳本換手機熱點獲取新公網(wǎng) IP在代碼中加入指數(shù)退避time.sleep(random.uniform(1.5, 3.0))替代固定sleep(1)答辯演示時提前用requests.get()測試目標 URL 是否返回 200失敗則彈窗提示“網(wǎng)絡(luò)受限請切換熱點”6.2 現(xiàn)象CSV 文件里中文全變成亂碼Excel 打開是方塊原因pandas.to_csv()默認用utf-8編碼但 Windows Excel 默認用gbk打開。解決保存時強制指定encodingutf_8_sig帶 BOM 的 UTF-8Excel 能自動識別df.to_csv(cleaned_data.csv, encodingutf_8_sig, indexFalse)或者生成 Excel 文件答辯時更體面df.to_excel(cleaned_data.xlsx, indexFalse) # 無需額外編碼參數(shù)6.3 現(xiàn)象PPT 里插入的 PNG 圖表模糊放大后鋸齒明顯原因plt.savefig()默認 DPI 過低80打印或全屏演示時像素不足。解決所有savefig()必加dpi300參數(shù)plt.savefig(chart.png, dpi300, bbox_inchestight)更推薦 SVG 格式矢量圖無限縮放plt.savefig(chart.svg, bbox_inchestight) # PPT 2016 直接支持插入 SVG6.4 現(xiàn)象答辯現(xiàn)場演示時pip install -r requirements.txt報錯cv2安裝失敗原因opencv-python在某些學校機房的舊版 pip 或受限網(wǎng)絡(luò)下安裝極慢甚至失敗。解決終極方案把cv2從requirements.txt中刪除改用PILPillow做基礎(chǔ)圖像處理畢業(yè)設(shè)計可視化完全夠用備用方案提前下載 wheel 包pip download opencv-python --no-deps --platform win_amd64 --python-version 38 --abi cp38 --only-binary:all:將下載的.whl文件和pip install xxx.whl命令寫進答辯備注頁現(xiàn)場離線安裝。注意答辯前 48 小時務(wù)必執(zhí)行我的應(yīng)急 checklist在答辯用電腦上python crawler.py運行一次確認能生成raw_data.csvjupyter notebook analysis.ipynb運行全部 cells確認圖表能輸出 PNG/SVG用 PowerPoint 打開 PPT檢查所有圖片是否正常顯示尤其 SVG把source_code.zip、PPT.pptx、report.pdf三文件壓縮為final_submit.zip重命名姓名_學號_二手房分析.zip打印一份《答辯問答預(yù)判清單》含“反爬怎么做的”“異常值怎么定義”“熱力圖數(shù)據(jù)來源”三問答案放在手邊我?guī)н^ 23 屆一個學生他答辯前夜發(fā)現(xiàn)鏈家改版列表頁結(jié)構(gòu)全變。沒慌用 Chrome 開發(fā)者工具重新抓ul.sellListContent li30 分鐘重寫解析邏輯第二天照樣拿了優(yōu)秀。畢業(yè)設(shè)計不是考你寫得多完美是考你面對黑匣子時能不能用print()、type()、dir()三板斧把它撬開。這套方案我壓過 7 屆學生從沒人在答辯現(xiàn)場被問倒——因為所有“為什么”都藏在你親手寫的 300 行代碼注釋里。希望幫到你。本文還有配套的精品資源點擊獲取