安居客租房数据分析与可视化:从爬虫到PDF报告生成全流程 简介《安居客租房数据分析及可视化实验报告》是一份以深圳租房市场为案例的完整数据分析项目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, headersheaders, timeout10) 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(stripTrue) price price_tag.select_one(strong) if price: price price.get_text(stripTrue) else: price 0 # 提取“2室1厅 / 60平米 / 南向 / 中楼层”这类信息 info info_tag.get_text( , stripTrue) 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[租金], errorscoerce) df[面积] pd.to_numeric(df[面积], errorscoerce) # 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, indexFalse, encodingutf-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(租金中位数, ascendingFalse) print(area_stats.head(10)) # 导出为分组明细表 area_stats.to_csv(area_stats.csv, indexFalse, encodingutf-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[租金], bins50, color#4C72B0, alpha0.8, edgecolorwhite) 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[租金], s2, alpha0.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户型, axax) ax.set_title(不同户型的租金分布) ax.set_xlabel(户型) ax.set_ylabel(租金元/月) plt.tight_layout() plt.savefig(analysis_charts.png, dpi150)这段代码的每个子图都对应前文的分析维度。字体配置写在最前面SimHei 和 Microsoft YaHei 是 Windows 和 macOS 上最常见的两种中文字体双写是为了兼容不同环境。bins50控制直方图的柱子数量太少图形太粗太多又有毛刺。散点图把点大小调到s2并加透明度和避免点数量大时糊成一团。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_optsopts.TitleOpts(title区域租金中位数 Top10), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate30)) ) ) bar.render(area_rent_bar.html)pyecharts 默认生成带 js 依赖的 HTML 文件双击就能在浏览器打开。axislabel_opts里的rotate30是专门用于解决区域名称过长导致的重叠问题。如果报告要嵌入 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, pagesizeA4) width, height A4 # 标题 c.setFont(Helvetica-Bold, 18) c.drawString(50, height - 50, 安居客租房数据分析实验报告) # 插入图片 c.drawImage(analysis_charts.png, 50, height - 450, width500, height350) 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, pagesizeA4) styles getSampleStyleSheet() story [] story.append(Paragraph(安居客租房数据分析/b, styles[Title])) story.append(Spacer(1, 12)) # 图片 story.append(Image(analysis_charts.png, width500, height350)) story.append(Spacer(1, 12)) # 表格区域统计 table_data [area_stats.columns.tolist()] area_stats.head(8).values.tolist() table Table(table_data, repeatRows1) story.append(table) doc.build(story)repeatRows1让表格跨页时重复表头这是多页表格必不可少的一个参数忘记设置的表格跨页后会完全读不懂。platypus 对表格宽度不会自动适配如果发现表格溢出页面可以让Table包一层Table(table_data, colWidths...)手写列宽。PDF 中插入图片的分辨率由 matplotlib 的dpi决定保存时建议dpi200起步低于这个值打印出来会模糊。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()截取时用了不同的排序状态。这四个检查点做完这份实验报告就不仅仅是代码流程的输出物而是经得起检查的技术交付品。本文还有配套的精品资源点击获取