
1. 项目概述与选题背景最近在带学生做《Python数据分析与可视化》的期末项目发现一个挺有意思的现象很多同学一提到数据分析脑子里蹦出来的不是“泰坦尼克号生存预测”就是“某电商平台销售分析”。不是说这些题目不好只是做的人太多了很难做出新意报告也容易流于表面。这次有个学生选了“安居客房源数据分析——以鹤岗为例”我一看就觉得这个选题有点东西它跳出了常规的数据集把分析工具用到了一个非常具体且带有社会讨论度的现实场景里。鹤岗这个城市大家应该都不陌生几年前因为“几万块一套房”的新闻火遍全网成了低房价的代名词。但热度过去之后这座城市的真实居住生态是怎样的现在的房价是触底反弹了还是继续探底什么样的房子在流通这些都是很有意思的问题。用Python去爬取安居客上鹤岗的房源数据然后进行清洗、分析和可视化本质上是在用数据透视一个城市的微观房地产市场。这个项目不仅涵盖了爬虫、数据处理、可视化这一套完整的数据分析流程更重要的是它要求分析者带着问题去审视数据从冰冷的数字里解读出温暖的生活图景和社会经济信号这对于学生理解数据分析的价值非常有帮助。2. 核心分析思路与框架设计拿到“鹤岗房源分析”这个题目第一步不是急着写代码而是先搭好分析框架。我们要明确最终的报告需要回答哪些核心问题。我指导学生梳理出了以下几个关键分析维度这构成了我们整个项目的骨架。2.1 分析维度拆解首先我们需要定义清楚要从哪些角度来“观察”鹤岗的房源市场。我总结了四个核心维度整体市场概览这是分析的起点。我们需要知道鹤岗在安居客上总共有多少在售房源这些房源的平均单价、总价分布在一个什么区间和大众的普遍认知“白菜价”相比数据呈现的真相是什么这个维度能快速建立对市场的基本认知。房源属性深度分析房子本身的特点决定了其价值。我们需要聚焦房源的固有属性包括户型分析几室几厅的户型最主流一室、两室、三室的比例如何这反映了市场的主力需求是单身公寓、刚需家庭还是改善型需求。面积与单价关系是不是房子越小单价越贵有没有“豪宅”大平层拉高均价通过散点图分析面积与单价的关系能看出市场的定价逻辑。楼层与建筑类型高层、小高层、多层无电梯、别墅的分布和价格差异。低楼层如1-3层是否因方便而溢价顶楼是否因可能漏雨而折价空间分布与区域价值鹤岗不同的区、街道甚至小区其房价可能有显著差异。我们需要通过地理可视化回答房价的热点区域在哪里是传统的市中心还是新兴的开发区有没有形成明显的价格梯度比如向阳区 vs. 工农区价差有多大某些热门小区是否形成了价格高地这背后可能是学区、物业、环境等因素在驱动。挂牌特征与市场动态这部分关注房源的“软信息”和市场状态。装修情况精装、简装、毛坯房的比例和价格差异。这能反映市场是偏向投资毛坯房还是即买即住的装修房。挂牌时长房源挂了多久还没卖出去挂牌时间分布可以侧面反映市场的流动性。是快速成交的市场还是许多房源有价无市标题与描述文本分析进阶利用简单的文本分析看看房东在标题里最常强调什么“急售”、“学区房”、“拎包入住”还是“价格可谈”这些关键词能揭示卖家的心态和市场情绪。2.2 技术路线图基于以上分析维度我们的技术实施路径就非常清晰了数据采集爬虫使用requests库模拟浏览器请求从安居客鹤岗页面爬取房源列表页和详情页数据。关键点在于解析HTML结构提取标题、总价、单价、面积、户型、楼层、区域、小区、装修、挂牌时间等字段。必须注意设置合理的请求间隔遵守robots.txt避免对目标网站造成压力。数据清洗与预处理Pandas这是最耗时但至关重要的一步。爬下来的原始数据往往很“脏”。处理缺失值与异常值比如有些房源可能缺失单价我们需要根据总价和面积反算对于面积异常小如10平米或异常大如1000平米的记录需要核查或剔除。格式统一将“85.2平米”这样的字符串转换为数值85.2将“3室2厅”拆分为“室数3”、“厅数2”两个数值列将“中层(共6层)”拆分为“所在楼层3”、“总楼层6”。特征工程创建新特征例如“房龄”如果数据中有建筑年代、“单价段”将单价划分为如1000, 1000-2000, 2000元/平米等区间、“区域板块”等。数据分析与可视化描述性统计与分布查看使用Pandas的describe()函数快速查看数值字段的统计概况。核心可视化分布图用于查看单价、总价、面积的分布直方图、核密度估计图。关系图分析面积vs单价散点图、户型分布饼图或条形图。对比图不同区域的平均单价对比条形图、不同装修状态的价格箱线图。地理空间图如果能获取到小区的大致经纬度可通过小区名称调用地图API批量获取需谨慎处理可以使用folium或pyecharts制作热力图或点图直观展示房价空间分布。时间序列图如果爬取了不同时间点的数据可以分析价格走势折线图。工具选择Matplotlib和Seaborn是基础且强大的组合Plotly可以制作交互式图表让报告更出彩。在期末项目中我通常建议学生先用好Seaborn因为它默认样式美观且语法相对简洁。3. 数据获取爬虫实战与伦理考量项目的第一步也是数据质量的基石就是获取数据。我们选择安居客作为数据源因为它是一个主流的房产信息平台在鹤岗这类城市也有足够的房源覆盖。但爬取这类网站技术之外更要讲究策略和伦理。3.1 爬虫策略设计安居客的页面结构对于爬虫来说比较典型但也设有一些反爬机制。我们的策略是“化整为零循序渐进”。列表页抓取首先分析鹤岗房源列表页的URL规律。通常URL会包含城市拼音、页码、筛选条件等参数。我们通过循环页码获取所有列表页。从列表页上我们可以提取到每个房源的基本信息卡片数据以及最重要的——详情页的链接。这里能拿到房源标题、总价、单价、面积、户型、区域、小区等核心信息。详情页补全列表页的信息往往不全如装修情况、楼层详情、挂牌时间、具体描述等需要进入每个房源的详情页抓取。这里的关键是构造一个稳定的详情页链接列表然后逐个请求。请求头与间隔设置这是绕过基础反爬的关键。必须在requests.get()中设置完整的headers特别是User-Agent要模拟真实浏览器。我通常会让学生准备几个不同的User-Agent轮换使用。import requests import time from fake_useragent import UserAgent ua UserAgent() headers { User-Agent: ua.random, Accept-Language: zh-CN,zh;q0.9, } # 示例抓取列表页 base_url https://anshan.anjuke.com/sale/ for page in range(1, 51): # 假设抓取50页 url f{base_url}p{page}/ try: response requests.get(url, headersheaders, timeout10) # 解析response.text提取数据... time.sleep(random.uniform(1, 3)) # 重要设置随机延迟 except Exception as e: print(f抓取第{page}页失败: {e}) continue注意time.sleep()是必须的道德和技术措施。过于频繁的请求会加重服务器负担可能导致你的IP被暂时封禁。随机延迟如1-3秒比固定延迟更模拟人类行为。3.2 数据解析与存储解析HTML我们使用BeautifulSoup或lxml。关键在于找到稳定的CSS选择器。安居客的页面结构可能会微调所以解析代码需要有一定的容错性。from bs4 import BeautifulSoup import pandas as pd def parse_list_page(html): soup BeautifulSoup(html, lxml) house_list [] # 假设每个房源项在某个div下需要实际查看网页结构确定 items soup.select(div.property-content) for item in items: try: title item.select_one(h3 a).text.strip() price_total item.select_one(.property-price-total).text.strip() price_unit item.select_one(.property-price-unit).text.strip() # ... 解析其他字段 house_list.append({ title: title, total_price: price_total, unit_price: price_unit, # ... }) except AttributeError as e: # 某个字段可能缺失记录日志或跳过 print(f解析房源项时出错跳过: {e}) continue return house_list所有数据抓取完毕后统一存入pandas DataFrame并保存为CSV文件方便后续分析。df pd.DataFrame(all_houses) df.to_csv(hegang_houses_raw.csv, indexFalse, encodingutf-8-sig)3.3 爬虫伦理与注意事项这是必须对学生强调的一点遵守robots.txt在爬取前访问https://www.anjuke.com/robots.txt查看网站是否允许爬虫抓取相关路径。即使技术上可行也应尊重网站的规则。控制请求速率如前所述设置延迟是基本素养。数据用途明确告知学生爬取的数据仅用于课程学习与学术研究不得用于商业用途或任何可能侵害他人权益的行为。识别反爬与应对如果遇到验证码或频繁返回空页面可能是触发了反爬。此时应首先检查请求头是否完备、延迟是否足够如果问题持续应考虑暂停或放弃大规模抓取或尝试使用更高级的模拟技术如Selenium但这在期末项目中通常不要求。我们的核心是数据分析数据量达到数百条足以支撑有意义的分析。4. 数据清洗从“脏数据”到“干净数据集”爬虫拿回来的数据我们称之为“原始数据”或“脏数据”。直接用它做分析结果很可能失真甚至错误。数据清洗是数据分析过程中最考验耐心和细心的环节往往占据整个项目50%以上的时间。下面以我们爬取的鹤岗房源数据为例一步步进行清洗。4.1 处理缺失值与异常值首先加载数据并查看概况import pandas as pd import numpy as np df pd.read_csv(hegang_houses_raw.csv) print(df.info()) # 查看各字段非空数量、类型 print(df.describe()) # 查看数值字段的统计分布缺失值处理关键字段缺失对于“总价”、“面积”这样的核心字段如果缺失这条记录基本就失去了分析价值通常直接删除df df.dropna(subset[total_price, area])。非关键字段缺失如“装修情况”、“楼层”如果缺失比例不高可以用众数或“未知”填充df[decoration].fillna(未知, inplaceTrue)。单价缺失这是一个常见情况。如果“总价”和“面积”都存在我们可以计算并填充单价df[unit_price] df.apply(lambda row: row[total_price] / row[area] if pd.isna(row[unit_price]) else row[unit_price], axis1)。异常值处理面积异常鹤岗的普通住宅面积一般在30-200平米之间。如果出现小于20平米可能是车位或录入错误或大于300平米可能是别墅或商业的记录需要重点核查或根据分析目标决定是否剔除。我们可以用分位数或标准差来识别Q1 df[area].quantile(0.25) Q3 df[area].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR df df[(df[area] lower_bound) (df[area] upper_bound)]单价异常同样设定一个合理的单价范围例如根据常识鹤岗住宅单价可能在500-5000元/平米超出范围的值需要检查。4.2 格式统一与特征提取爬虫下来的数据很多是字符串需要转换成分析可用的格式。数值提取总价字符串可能是“58万”、“25.8万”。需要提取数字并统一为“万元”或“元”为单位。def extract_price(price_str): try: # 去除“万”、“元”等字符转换为浮点数 num float(re.search(r[\d\.], price_str).group()) if 万 in price_str: return num else: # 如果是“元”则除以10000 return num / 10000 except: return np.nan df[total_price_clean] df[total_price].apply(extract_price)面积处理“85.2平米”、“85.2㎡”。df[area_clean] df[area].str.replace(平米, ).str.replace(㎡, ).astype(float)文本字段拆分户型将“3室2厅1卫”拆分为三个独立的数值列。def split_layout(layout_str): # 使用正则表达式查找数字 rooms re.search(r(\d)室, layout_str) halls re.search(r(\d)厅, layout_str) baths re.search(r(\d)卫, layout_str) return pd.Series([ int(rooms.group(1)) if rooms else 0, int(halls.group(1)) if halls else 0, int(baths.group(1)) if baths else 0 ]) df[[rooms, halls, bathrooms]] df[layout].apply(split_layout)楼层将“中层(共6层)”拆分为“所在楼层”和“总楼层”。这里逻辑稍复杂需要处理“高/中/低层”、“顶层”、“地下室”等表述。def split_floor(floor_str): # 简化处理实际需要更复杂的逻辑 if 共 in floor_str: total int(re.search(r共(\d)层, floor_str).group(1)) # 简单假设“中层”为总楼层一半 current total // 2 return pd.Series([current, total]) return pd.Series([np.nan, np.nan]) df[[current_floor, total_floors]] df[floor_info].apply(split_floor)创建新特征单价段将清洗后的单价进行分箱便于分类分析。bins [0, 1000, 1500, 2000, 3000, df[unit_price_clean].max()] labels [1000, 1000-1500, 1500-2000, 2000-3000, 3000] df[price_segment] pd.cut(df[unit_price_clean], binsbins, labelslabels)房龄如果数据中有“建筑年代”可以计算大致房龄当前年份-建筑年代。清洗完成后保存一份干净的数据集df_clean df.copy() df_clean.to_csv(hegang_houses_clean.csv, indexFalse, encodingutf-8-sig)5. 数据分析与可视化实战有了干净的数据我们就可以开始有趣的探索了。可视化不仅是让报告好看的“面子工程”更是发现数据规律、讲述数据故事的“眼睛”。我们使用Seaborn和Matplotlib作为主力。5.1 整体市场概览价格与面积分布首先让我们对鹤岗的房源市场有一个宏观的认识。import seaborn as sns import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 解决中文显示问题 plt.rcParams[axes.unicode_minus] False # 1. 单价分布直方图 plt.figure(figsize(12, 5)) plt.subplot(1, 2, 1) sns.histplot(datadf_clean, xunit_price_clean, bins30, kdeTrue) plt.title(鹤岗房源单价分布) plt.xlabel(单价 (元/平米)) plt.ylabel(房源数量) # 2. 总价分布直方图 plt.subplot(1, 2, 2) sns.histplot(datadf_clean, xtotal_price_clean, bins30, kdeTrue) plt.title(鹤岗房源总价分布) plt.xlabel(总价 (万元)) plt.ylabel(房源数量) plt.tight_layout() plt.show()解读从单价分布图我们可能发现鹤岗的房源单价主要集中在1000-2500元/平米这个区间传说中的“几百元一平”可能只是极少数个案或特定类型的房产如偏远、老旧小区。总价分布图则能直观显示大部分房源的总价集中在10-30万元区间“几万一套”的房源占比多少一目了然。5.2 房源属性分析户型、面积与单价的关系接下来我们深入房源内部属性。# 3. 户型分布饼图或条形图 plt.figure(figsize(10, 6)) room_counts df_clean[rooms].value_counts().sort_index() room_counts.plot(kindbar) plt.title(鹤岗房源户型室数分布) plt.xlabel(室数) plt.ylabel(房源数量) plt.xticks(rotation0) for i, v in enumerate(room_counts): plt.text(i, v 5, str(v), hacenter) plt.show() # 4. 面积与单价散点图可加入户型颜色维度 plt.figure(figsize(10, 6)) scatter plt.scatter(df_clean[area_clean], df_clean[unit_price_clean], cdf_clean[rooms], cmapviridis, alpha0.6, s50) plt.colorbar(scatter, label室数) plt.title(房源面积与单价关系颜色代表室数) plt.xlabel(面积 (平米)) plt.ylabel(单价 (元/平米)) plt.grid(True, linestyle--, alpha0.5) plt.show()解读户型分布图能告诉我们市场供应主力是几居室。在鹤岗可能两室和三室是绝对主流这与家庭结构和小城市居住习惯有关。散点图则更加有趣我们可以观察是否存在“面积越小单价越高”的规律小户型单价溢价以及不同居室数的房源在面积-单价图上是否形成不同的集群。5.3 区域对比与空间洞察分析不同区域的房价差异是理解城市内部结构的关键。# 5. 各区域平均单价对比条形图 plt.figure(figsize(12, 6)) region_price df_clean.groupby(region)[unit_price_clean].mean().sort_values(ascendingFalse) sns.barplot(xregion_price.index, yregion_price.values) plt.title(鹤岗各区域平均单价对比) plt.xlabel(区域) plt.ylabel(平均单价 (元/平米)) plt.xticks(rotation45) for i, v in enumerate(region_price.values): plt.text(i, v 20, f{v:.0f}, hacenter) plt.tight_layout() plt.show() # 6. 装修情况对价格的影响箱线图 plt.figure(figsize(10, 6)) order [毛坯, 简装, 精装] # 按装修程度排序 sns.boxplot(datadf_clean, xdecoration, yunit_price_clean, orderorder) plt.title(不同装修状况房源单价分布) plt.xlabel(装修情况) plt.ylabel(单价 (元/平米)) plt.show()解读区域对比图能清晰展示鹤岗的“房价高地”和“洼地”。比如向阳区作为传统中心均价可能显著高于其他区。箱线图则能展示装修状况对房价的影响程度及其分布范围。精装房单价中位数和上四分位数通常高于简装和毛坯但箱体IQR可能也更宽说明精装标准差异大。5.4 进阶可视化交互与地理信息如果想让报告更出彩可以尝试交互式图表和地图。使用Plotly制作交互式图表Plotly图表可以缩放、悬停查看数据点详情。import plotly.express as px fig px.scatter(df_clean, xarea_clean, yunit_price_clean, colorrooms, hover_data[title, region, total_price_clean], title鹤岗房源面积-单价交互散点图) fig.show()地理空间可视化需经纬度数据如果通过小区名获取到了经纬度可以用folium制作热力图。import folium from folium.plugins import HeatMap # 假设df_clean中有lat和lng列 hegang_center [df_clean[lat].mean(), df_clean[lng].mean()] m folium.Map(locationhegang_center, zoom_start12) # 将单价作为权重生成热力图 heat_data [[row[lat], row[lng], row[unit_price_clean]] for index, row in df_clean.dropna(subset[lat, lng]).iterrows()] HeatMap(heat_data, radius15, blur10, max_zoom1).add_to(m) m.save(hegang_house_price_heatmap.html)注意批量获取经纬度涉及调用地图API如高德、百度有次数限制且需遵守其使用条款。在学术项目中可以手动查找少量重点小区作为示例不必追求全覆盖。6. 分析结论提炼与报告撰写心得数据分析的最终目的是产出有意义的结论。基于上述可视化结果我们可以提炼出一些关于鹤岗房源市场的核心洞察价格认知修正数据可能显示鹤岗整体房价确实处于低位但“白菜价”房源并非市场主流。核心区域、较新小区、精装修房源的单价仍能达到2000-3000元/平米总价在20-40万区间。这修正了网络传闻带来的极端化认知。市场结构特征供应以两室、三室的中小面积刚需户型为主符合资源型收缩城市的人口结构和需求特点。大户型四室及以上和别墅产品稀缺且单价不菲。区域分化明显城市内部存在清晰的价格梯度。向阳区、工农区等传统中心城区均价领先而东山、南山等区域则形成了价格洼地。这种分化与配套成熟度、学区资源密切相关。装修溢价有限相较于一二线城市鹤岗市场对装修的溢价接受度可能较低。毛坯与简装房源占据较大比例精装房的溢价空间并不夸张反映出市场更看重总价和地段。流动性观察通过挂牌时长如果数据中有的分布可以初步判断市场流动性。如果大量房源挂牌时间超过半年可能意味着市场交易并不活跃存在“有价无市”的情况。在撰写期末报告时我常提醒学生注意以下几点故事线重于罗列不要简单地把所有图表堆砌上去。报告应该有一条清晰的逻辑线例如“整体市场面貌 - 细分属性分析 - 空间格局解读 - 综合结论”。图表为观点服务每一张图都应该对应一个想要说明的结论并在图下方用一两句话点明核心发现。注明数据局限诚实说明数据的局限性例如数据仅来源于安居客一个平台、仅为某一时间点的截面数据、未能包含已成交价格等。这体现了分析的严谨性。代码与结果分离在报告正文中展示关键的分析结果和可视化图表将完整的、注释良好的Python代码作为附录。这样既保证了报告的可读性也展示了完整的工作流程。这个项目做下来学生收获的远不止是Pandas和Seaborn的语法。他们学会了如何从一个社会现象中定义分析问题如何系统地收集和处理真实世界中的“脏数据”如何通过可视化让数据“说话”并最终形成有依据的、接地气的结论。这才是数据分析课程最有价值的部分——将技术能力转化为解决实际问题的洞察力。