简介:《安居客租房数据分析及可视化实验报告》是一份以深圳租房市场为案例的完整数据分析项目PDF,面向Python爬虫、数据处理与可视化初学者,尤其适合毕业设计或课程实训参考。资源包仅含1个PDF文件,大小约912KB,内容覆盖三大模块:数据获取环节对比单线程、多线程和Scrapy爬虫的优缺点;数据处理环节利用Power Query完成去重、字段拆分和数值化;可视化与建模环节使用Excel、Tableau绘制不同房屋类型、行政区和小区租金对比图,并构建多元线性回归模型,借助KNN原理剔除异常值、Lasso回归筛选变量。最终结论显示,房屋面积每增加1平方米租金平均上涨55元,距地铁站每增加100米租金平均下降6元,整租比合租平均贵453元。报告还提供了完整的变量说明和建模过程,已有2545人学习下载,可直接作为分析思路和代码实现的参考。
1. 从一份 PDF 实验报告说起:安居客租房数据的价值链路
如果你打开过安居客的租房频道,会发现同一个城市、同一个商圈,房源价格从几百到几万都有。抛开虚假房源和中介加价,这些数据本身就是一份极好的市场样本:可以计算真实租金分布、识别价格洼地、分析户型与面积的关系,甚至反向校验某个小区的挂牌价是否虚高。而“安居客租房数据分析及可视化实验报告.pdf”这类标题,代表的就是怎么把这套流程完整落地并输出为一份可归档、可汇报的成果。换句话说,这不是一篇讲爬虫的教程,而是从数据采集、清洗、分析到可视化、再到 PDF 导出的完整工程链。适合正在做数据分析课设、毕业设计,或者想用公开数据验证分析能力的人。整条链路都用 Python 就能跑通,不依赖任何收费工具,可复现性很强。
2. 数据准备:先把安居客房源列表页处理成可分析的结构化数据
2.1 字段设计:采集前先想清楚要分析什么
分析租房数据之前,最关键的不是代码而是字段。多数人第一次抓完数据才发现缺了朝向或楼层字段,回补成本很高。对照安居客租房列表页,通常值得保留的字段有:小区名称、所在区域、租金、面积、户型(几室几厅)、朝向、楼层、类型(整租/合租)、发布时间。这些字段足够支撑区域租金对比、户型和面积的关系分析、楼层溢价等常见维度。如果你要分析不同来源的价格差异,可以额外加一个字段叫source,写死为“安居客”,方便后续和其他平台数据合并。
字段设计确定后,采集本身并不复杂。安居客普通列表页没有特别强的签名校验,用 requests 加随机 User-Agent 就能拿到 HTML。下面以一个标准列表页为例:
import requests from bs4 import BeautifulSoup import pandas as pd import time headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } url = "https://sh.zu.anjuke.com/fangyuan/l1/" resp = requests.get(url, headers=headers, timeout=10) resp.encoding = "utf-8" soup = BeautifulSoup(resp.text, "html.parser") # 列表项 items = soup.select(".zu-itemmod") print(f"当前页解析到房源: {len(items)} 条")这段代码干了两件事:请求列表页 HTML,然后通过 BeautifulSoup 定位房源列表容器.zu-itemmod。注意编码要显式设置为utf-8,否则后续中文大概率乱码。这里只用了一个固定请求头,如果触发平台的风控,可以在请求前随机更换 UA 并且每次请求之间做 2 到 3 秒的延时。解析到列表之后,再从每个item里提取详情字段,比如租金、面积、朝向,这些都在特定的 CSS 类名下。
records = [] for item in items: title_tag = item.select_one(".items-name") info_tag = item.select_one(".zu-info") price_tag = item.select_one(".zu-side") if not title_tag or not price_tag: continue title = title_tag.get_text(strip=True) price = price_tag.select_one("strong") if price: price = price.get_text(strip=True) else: price = "0" # 提取“2室1厅 / 60平米 / 南向 / 中楼层”这类信息 info = info_tag.get_text(" ", strip=True) if info_tag else "" records.append({ "标题": title, "租金": price, "信息": info }) df = pd.DataFrame(records)提取逻辑的核心是先定位,再取文本,不匹配的字段直接跳过。`租金的取法是找强标签里的数字文本,面积、朝向、楼层都合并在一个信息字段里,后续清洗需再拆分。这样做的原因是不同区域的详情页 DOM 结构略有差异,一次性拆完全段字段容易在个别页面上报错。
2.2 用 pandas 完成去重、拆列与异常值过滤
采集后的 DataFrame 通常是脏的,典型问题包括:同样房源出现多页导致重复、面积带“平米”字样、租金含“元/月”、部分房源缺失面积或户型信息。处理顺序建议是先去重,再拆分,最后过滤异常值。
# 1. 去掉完全重复的房源 df = df.drop_duplicates(subset=["标题", "租金", "信息"]) # 2. 拆分字段 df["户型"] = df["信息"].str.extract(r"(\d室\d厅)") df["面积"] = df["信息"].str.extract(r"(\d+(\.\d+)?)平米") df["朝向"] = df["信息"].str.extract(r"([东南西北]+向)") df["楼层"] = df["信息"].str.extract(r"([低中高]楼层)") # 3. 类型转换 df["租金"] = pd.to_numeric(df["租金"], errors="coerce") df["面积"] = pd.to_numeric(df["面积"], errors="coerce") # 4. 过滤异常数据 df = df.dropna(subset=["租金", "面积"]) df = df[(df["租金"] > 300) & (df["租金"] < 100000)] df = df[(df["面积"] > 5) & (df["面积"] < 500)]正则里\d匹配数字,(\d+(\.\d+)?)是为了兼容面积的整数和小数两种写法。朝向的提取用字符组加“向”字,可以稳定解析“南向”“东南向”这类值。数值过滤的逻辑是:租金低于 300 的基本是合租单间或虚假价格,高于十万的通常是别墅或商业地产,面积同理。完成这一步后,字段干净、类型正确,后续分析才能直接用 groupby 和透视表。
数据分析项目里数据清洗的时间往往占一半以上。处理完的 DataFrame 建议立刻保存为中间文件:
df.to_csv("anjuke_cleaned.csv", index=False, encoding="utf-8-sig")使用utf-8-sig编码是为了让 Excel 直接打开不会出现中文乱码。后续分析统一从这个文件读入,避免重复爬取。清洗规则也可以沉淀成函数,多城市数据跑同一套逻辑,减少重复劳动。
3. 核心指标计算:从租金分布到区域差异的量化分析
3.1 用 describe 看整体数据形态,找离群点
拿到清洗后的数据,第一步不是画图,而是看统计特征。describe()会给出租金和面积的均值、标准差、四分位数,这些数字直接告诉你数据形态是否健康。比如租金均值远大于中位数,说明数据右偏,有少量极高价格房源拉高了平均值,这种情况下报告里应该优先展示中位数而不是平均值。
import pandas as pd df = pd.read_csv("anjuke_cleaned.csv") print(df[["租金", "面积"]].describe()) # 每平米单价 = 租金 / 面积 df["单价"] = (df["租金"] / df["面积"]).round(2) print(df["单价"].describe())描述性统计的结果可以直接作为实验报告的“数据概况”章节素材。单价比租金更能反映真实水平,因为 30 平的小户型租金 3000 和大户型租金 9000 没有可比性,但折算成单价就能在同一维度下比较。交叉分析里,单价是最常用的输出指标之一。
提示:如果
describe()中 75% 分位数和最大值差距过大,一定要单独看这些极端值的标题信息,很多是别墅、商铺混入了住宅房源,直接删除比保留更合理。
3.2 区域与租金的关系:用 groupby 找出价格梯队
区域分析是租房报告中最常见的板块。思路是对“区域”字段做分组,计算各区域的房源量、租金均值和中位数。房源的挂牌数量本身也有信息量:挂牌量大的区域通常是供应主力,均价与供应量结合可以判断市场热度。这张分组表就是后续可视化的数据源,第 4 章的柱状图和地图都依赖它。
area_stats = df.groupby("区域").agg( 房源量=("租金", "count"), 租金均值=("租金", "mean"), 租金中位数=("租金", "median"), 单价均值=("单价", "mean") ).reset_index() area_stats = area_stats.sort_values("租金中位数", ascending=False) print(area_stats.head(10)) # 导出为分组明细表 area_stats.to_csv("area_stats.csv", index=False, encoding="utf-8-sig")这里agg的写法是 pandas 2.x 推荐的命名聚合方式,列名直接写成中文,输出结果本身就是一份整洁的表格,不需要再做行列转置。排序建议用中位数而不是均值,前面说过均值受极端值影响大,中位数在报告里更有参考价值。得到这张表后,横向对比就能看出明显的价格梯队:市中心区域、成熟生活区、远郊区的差距一目了然。
3.3 户型、面积与单价的联动分析
户型对价格的影响不是线性的。一居室单价往往最高,因为面积小、地段普遍更好;三居及以上虽然总价高,但单价可能反而下降。要验证这个关系,可以做一个户型分组的透视表:
pivot = df.pivot_table( index="户型", values=["租金", "面积", "单价"], aggfunc={"租金": "median", "面积": "median", "单价": "median"} ).round(2) print(pivot)pivot_table比groupby更适合多指标并列的统计场景,直接以户型为索引,一次性输出三个指标的中位数。户型字段在前面清洗时已经拆成了“2室1厅”这样的字符串,pivot 会按字符串自动排序,看起来不够直观,可以按面积中位数排序。这个表主要回答两个问题:面积扩散是否合理、单价是否存在户型折价。
这里有个坑点:有些房源没有面积信息,但存在户型,清洗时如果用dropna会把这些行删掉,导致“户型-面积”的分析口径变小。正确做法是分两条路线处理——租金分析用全量数据,面积相关分析用非空子集。
3.4 楼层与朝向的量化判断
楼层和朝向这类字段在租房市场有隐性溢价。低楼层可能便宜,但采光和通风是劣势;高楼层视野好但爬楼梯费劲。朝南和朝北的价格差在租房市场可能没有买房市场那么极端,但数据能给出具体的差额。操作上就是把楼层和户型拼接成分组标签,做中位数对比:
df["楼层_tag"] = df["楼层"].fillna("未知") df["朝向_tag"] = df["朝向"].fillna("未知") floor_res = df.groupby("楼层_tag")["单价"].agg(["count", "median"]).round(2) print(floor_res)fillna("未知")是为了保留那些没有楼层/朝向信息的房源,避免分组时被静默丢弃。你会发现“未知”组的数量如果过多,说明解析正则漏掉了不少房源,需要回到第 2 章检查正则表达式。楼层分组的单价对比可以做成简单表格,也可以放到报告中作为“房源基本特征”小节的数据支撑。
4. 可视化落地:把 pandas 的统计结果转成可读图表
4.1 图表选型:什么数据配什么图
数据分析全套可视化不需要花哨,关键是选对图。我的常用映射关系是:区域租金对比用横向柱状图或箱线图,租金分布用直方图加核密度曲线,面积与租金的关系用散点图,楼层与单价对比用分组柱状图。如果页数允许,热度数据可以用 Geo 地图或大屏式仪表盘,但在 Python 生态里最稳妥的还是 matplotlib 和 pyecharts 的组合。
下面的代码设计了一套完整的图表输出方案,包含四个子图:
import matplotlib.pyplot as plt import matplotlib matplotlib.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"] matplotlib.rcParams["axes.unicode_minus"] = False fig, axes = plt.subplots(2, 2, figsize=(14, 10)) # 1. 租金分布直方图 ax = axes[0, 0] ax.hist(df["租金"], bins=50, color="#4C72B0", alpha=0.8, edgecolor="white") ax.set_title("租金分布直方图") ax.set_xlabel("租金(元/月)") ax.set_ylabel("房源数量") # 2. 区域租金中位数横向柱状图 ax = axes[0, 1] top10 = area_stats.head(10) ax.barh(top10["区域"], top10["租金中位数"], color="#DD8452") ax.set_title("区域租金中位数 Top10") ax.set_xlabel("租金中位数(元/月)") # 3. 面积与租金散点图 ax = axes[1, 0] ax.scatter(df["面积"], df["租金"], s=2, alpha=0.4, color="#55A868") ax.set_title("面积与租金散点图") ax.set_xlabel("面积(平米)") ax.set_ylabel("租金(元/月)") # 4. 户型和单价的分组箱线图 ax = axes[1, 1] df_pivot_box = df[df["户型"].notna()].head(500) df_pivot_box.boxplot(column="租金", by="户型", ax=ax) ax.set_title("不同户型的租金分布") ax.set_xlabel("户型") ax.set_ylabel("租金(元/月)") plt.tight_layout() plt.savefig("analysis_charts.png", dpi=150)这段代码的每个子图都对应前文的分析维度。字体配置写在最前面,SimHei 和 Microsoft YaHei 是 Windows 和 macOS 上最常见的两种中文字体,双写是为了兼容不同环境。bins=50控制直方图的柱子数量,太少图形太粗,太多又有毛刺。散点图把点大小调到s=2并加透明度和,避免点数量大时糊成一团。
boxplot是整个代码里最容易出问题的地方:如果某个户型的样本量太少,箱线会显示为一条线,看着像错误。可以在绘图前增加一个过滤条件,只保留样本量大于 20 的户型。箱线图的优势是能直观看到中位数、四分位距和异常值,比单纯柱状图信息密度大得多,报告中展示箱线图更有说服力。
4.2 用 pyecharts 做交互式可视化并导出 HTML
如果实验报告需要答辩展示,静态图不够有冲击力,可以把关键图表做成交互式版本。pyecharts 生成的图表支持鼠标悬浮查看数据、缩放、图表切换,而且不需要前端基础。下面的代码实现了区域租金对比和区域单价玫瑰图两个图:
from pyecharts.charts import Bar, Pie from pyecharts import options as opts # 柱状图:区域租金中位数 bar = ( Bar() .add_xaxis(area_stats["区域"].head(10).tolist()) .add_yaxis("租金中位数", area_stats["租金中位数"].head(10).round(0).tolist()) .set_global_opts( title_opts=opts.TitleOpts(title="区域租金中位数 Top10"), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=30)) ) ) bar.render("area_rent_bar.html")pyecharts 默认生成带 js 依赖的 HTML 文件,双击就能在浏览器打开。axislabel_opts里的rotate=30是专门用于解决区域名称过长导致的重叠问题。如果报告要嵌入 PDF,可以直接对 HTML 页面截图,或者用SnapshotSelenium组件把图表保存为 PNG 再插入报告。这里有个细节:pyecharts 柱状图数据是绕过的,也就是说 add_yaxis 传入的长度必须和 add_xaxis 一致,否则会报错,拼接时注意用head(10)同步截取。
4.3 大屏看板不是必须,但可以做一页过瘾的效果
数据分析项目做到一定程度,难免被“可视化大屏”的热词吸引。安居客租房数据做一张大屏其实是可行的:左上角放区域均价 Top10,中间放地图热力,右侧放户型占比环形图。但在实验报告里,大屏的投入产出比并不高。我的做法是:用 pyecharts 生成一个 HTML 看板文件作为附件或附录,主报告上放静态图,两者内容一致但形式互补。HTML 看板文件本来就是一页式导航,不需要额外开发框架。
5. PDF 报告自动生成:让图表和结论以 PDF 形式落地
5.1 用 reportlab 生成带图表的 PDF 报告
实验报告要求 PDF 成品,手动把图贴进 Word 再导出当然可以,但自动生成更符合数据分析项目的工程化形象。最直接的方案是用reportlab库,它可以把 pandas 结果表格和 matplotlib 生成的图片统一编排进 PDF 页面。下面是一个最小可用的生成逻辑:
from reportlab.lib.pagesizes import A4 from reportlab.pdfgen import canvas c = canvas.Canvas("安居客租房数据分析实验报告.pdf", pagesize=A4) width, height = A4 # 标题 c.setFont("Helvetica-Bold", 18) c.drawString(50, height - 50, "安居客租房数据分析实验报告") # 插入图片 c.drawImage("analysis_charts.png", 50, height - 450, width=500, height=350) c.showPage() c.save()reportlab 的drawImage接收图片路径和坐标,最麻烦的是坐标系——它的原点在左下角,而阅读习惯是从上往下。一种省事的方式是先计算height - 50这类绝对位置,多跑几次就能对齐。如果报告内容较多,可以使用platypus模块的SimpleDocTemplate,按段落、表格、图片的顺序组织内容,这部分逻辑清晰,适合做成通用模板,数据变了只换内容不必动布局。
5.2 PDF 报告内容自动排版:表格、图片和文字的组合
上面用 canvas 直接画是快速方案的写法,但真正的报告包含多个章节,需要自动换页,这里用 platypus 更合适。platypus 自带表格和图片容器,可以自动处理分页,不用手动计算坐标:
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, Table from reportlab.lib.styles import getSampleStyleSheet doc = SimpleDocTemplate("实验报告.pdf", pagesize=A4) styles = getSampleStyleSheet() story = [] story.append(Paragraph("安居客租房数据分析</b>", styles["Title"])) story.append(Spacer(1, 12)) # 图片 story.append(Image("analysis_charts.png", width=500, height=350)) story.append(Spacer(1, 12)) # 表格:区域统计 table_data = [area_stats.columns.tolist()] + area_stats.head(8).values.tolist() table = Table(table_data, repeatRows=1) story.append(table) doc.build(story)repeatRows=1让表格跨页时重复表头,这是多页表格必不可少的一个参数,忘记设置的表格跨页后会完全读不懂。platypus 对表格宽度不会自动适配,如果发现表格溢出页面,可以让Table包一层Table(table_data, colWidths=...)手写列宽。PDF 中插入图片的分辨率由 matplotlib 的dpi决定,保存时建议dpi=200起步,低于这个值打印出来会模糊。
5.3 中文字体的嵌入方案
reportlab 默认字体是 Helvetica,不支持中文。直接 drawString 中文字符会呈现为方块,这是自动生成 PDF 最常见的坑。解决方式是指定一个支持中文的 TTF 字体文件,然后注册到 reportlab 的字体管理中:
from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont pdfmetrics.registerFont(TTFont("CNFont", "msyh.ttc")) # 微软雅黑 c.setFont("CNFont", 14)msyh.ttc是微软雅黑的字体文件名,通常在 Windows 的C:\Windows\Fonts目录下。如果换到 macOS,需要换成PingFang.ttc或STSong.ttf,路径和文件名因系统而异。写完这段代码后,可以再生成一个测试页面专门检查中文渲染是否正常,然后再跑完整版报告,否则整份 PDF 全是乱码,浪费输出时间。
5.4 报告验证的四个检查点
生成 PDF 后不要急着交,按下面四个顺序过一遍:第一,打开 PDF 检查中文字体渲染,重点看标题和表格表头;第二,检查图片与文字是否重叠,特别关注跨页位置;第三,确认图片清晰度,放大到 200% 仍然可接受才算合格;第四,抽查表格数据与原始 pandas 输出是否一致,以防head()截取时用了不同的排序状态。这四个检查点做完,这份实验报告就不仅仅是代码流程的输出物,而是经得起检查的技术交付品。
本文还有配套的精品资源,点击获取