ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python二手房数据采集与可视化分析:从CSV清洗到pyecharts图表全流程

Python二手房数据采集与可视化分析:从CSV清洗到pyecharts图表全流程 简介本资源为基于Python的二手房数据采集及可视化分析毕业设计项目完整资料包面向计算机、通信、人工智能、自动化等相关专业的学生与教师可用于毕业设计、期末课程设计或课程大作业。项目为个人毕设成果答辩评审分达98分代码均经过调试测试确保可运行适合小白学习与进阶也便于基础较好的同学在此基础上修改调整以实现不同功能。资源包共157个文件约40.01MB包含18个py源码文件、18个csv数据集、15个html页面、11个js脚本及65个png图表等涵盖数据采集、清洗、存储与可视化展示的完整流程并附论文资料。目前已有177人学习下载。读者可从中获得一套可直接运行的赛题方案、清晰的项目目录结构、数据清洗与可视化实现思路及排错参考具有较高的学习借鉴价值。1. 从一份二手房 CSV 说起这套毕设资源到底能跑出什么结果很多人做毕业设计卡在第一步数据从哪来。网上找的公开数据集要么字段残缺要么城市对不上要么干脆是几年前的死数据。这套基于 Python 的二手房数据采集及可视化分析项目解决的正是这个从零到一的问题——它把采集、清洗、存储、可视化整条链路都跑通了并且留下了完整的源码和论文资料。资源里能看到ershoufang-origin-utf8.csv、ershoufang-origin-ansi.csv这类原始采集文件也有ershoufang-clean-utf8-v1.1.csv、ershoufang-clean-ansi-v1.1.csv这类清洗后的版本还有一份ershoufang - 20000.csv说明数据量级在两万条左右。适合计算机、通信、自动化等专业拿来做课设或毕设也适合刚学完 Python 基础语法、想找一个完整项目练手的人。它不是一个玩具 demo而是一条能复现、能改、能写进论文的完整流水线。2. 采集层拆解requests BeautifulSoup 怎么把房源列表翻成结构化数据2.1 为什么选 requests 而不是 Scrapy这套项目用的是requests加BeautifulSoup的组合而不是 Scrapy 框架。原因很实际毕设场景下目标站点结构相对固定翻页逻辑简单用轻量级方案反而更好调试。Scrapy 的异步调度和中间件体系对新手来说学习曲线陡一旦被反爬拦住排查成本高得多。用 requests 手动控制请求间隔、手动处理 Cookie 和 UA每一步都看得见出问题也好定位。常见做法是把请求头、翻页参数、重试逻辑封装成一个采集类。下面是我一般会用的骨架结构和项目里的思路一致import requests from bs4 import BeautifulSoup import time import csv import random class ErshoufangSpider: def __init__(self): self.base_url https://example.com/ershoufang/pg{}/ self.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, Accept-Language: zh-CN,zh;q0.9, } self.session requests.Session() self.session.headers.update(self.headers) def fetch_page(self, page_num, retry3): url self.base_url.format(page_num) for i in range(retry): try: resp self.session.get(url, timeout10) if resp.status_code 200: return resp.text except requests.RequestException as e: print(f第{page_num}页第{i1}次请求失败: {e}) time.sleep(random.uniform(2, 5)) return None def parse_list(self, html): soup BeautifulSoup(html, html.parser) items soup.select(.sellListContent li) records [] for item in items: title item.select_one(.title a) total_price item.select_one(.totalPrice span) unit_price item.select_one(.unitPrice span) house_info item.select_one(.houseInfo) if not all([title, total_price, unit_price, house_info]): continue records.append({ title: title.get_text(stripTrue), total_price: total_price.get_text(stripTrue), unit_price: unit_price.get_text(stripTrue).replace(元/平米, ), house_info: house_info.get_text(stripTrue), }) return records def save_csv(self, records, filenameershoufang-origin-utf8.csv): with open(filename, a, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesrecords[0].keys()) if f.tell() 0: writer.writeheader() writer.writerows(records)这段代码里几个关键点值得说清楚。requests.Session()的作用是复用 TCP 连接并自动保持 Cookie比每次裸调requests.get效率高。timeout10是必须加的否则遇到慢响应会一直挂住。重试逻辑里用random.uniform(2, 5)做随机间隔比固定time.sleep(1)更不容易触发频率限制。encodingutf-8-sig是为了让 Excel 直接打开 CSV 时不乱码这也是为什么资源里同时存在 utf8 和 ansi 两个版本——不同工具链对编码的兼容性不一样。2.2 翻页与字段抽取的实操细节翻页逻辑通常是构造 URL 里的页码参数从 1 循环到目标页数。但这里有个容易翻车的地方很多二手房平台的总页数不是固定的今天搜是 100 页明天可能变成 98 页。硬编码range(1, 101)会在最后一页拿到空列表如果不做判断就会写入空数据。我一般会在循环里加一个终止条件当某一页返回的 records 为空时连续两次就 break。另外字段抽取时houseInfo那一栏通常是一串用竖线分隔的文本比如「2室1厅 | 80平米 | 南 | 精装」需要在清洗阶段再拆成独立字段。采集阶段保持原样反而更安全因为拆分逻辑一旦写错原始数据就丢了。资源里的ershoufang-origin-utf8.csv和ershoufang-origin-ansi.csv就是采集层的直接产物字段没有做二次加工。这种设计是对的——原始数据和分析数据分开存后面清洗规则改了可以重新跑不用重新采集。3. 清洗与特征工程从 origin 到 clean-v1.1 中间发生了什么3.1 缺失值、异常值和单位统一原始 CSV 里最常见的三类脏数据价格字段带「万」字、面积字段带「平米」、楼层信息混在文本里。清洗的第一步是把这些数值从字符串里抠出来。下面这段清洗脚本是我在类似项目里反复用过的import pandas as pd import re def parse_price(text): if pd.isna(text): return None text str(text).strip() match re.search(r(\d\.?\d*), text) return float(match.group(1)) if match else None def parse_area(text): if pd.isna(text): return None match re.search(r(\d\.?\d*)平米, str(text)) return float(match.group(1)) if match else None def parse_room_hall(text): match re.search(r(\d)室(\d)厅, str(text)) if match: return int(match.group(1)), int(match.group(2)) return None, None df pd.read_csv(ershoufang-origin-utf8.csv, encodingutf-8-sig) df[total_price_num] df[total_price].apply(parse_price) df[unit_price_num] df[unit_price].apply(parse_price) df[area_num] df[house_info].apply(parse_area) df[room], df[hall] zip(*df[house_info].apply(parse_room_hall)) df df.dropna(subset[total_price_num, area_num]) df df[(df[area_num] 10) (df[area_num] 1000)] df df[(df[total_price_num] 10) (df[total_price_num] 5000)] df.to_csv(ershoufang-clean-utf8-v1.1.csv, indexFalse, encodingutf-8-sig)逻辑说明parse_price用正则提取第一个数字兼容「350万」「350」两种写法。parse_area专门匹配「平米」前面的数字。面积过滤掉小于 10 平米和大于 1000 平米的记录价格过滤掉低于 10 万和高于 5000 万的记录——这些阈值是根据二手房市场的合理区间定的能去掉大部分录入错误和测试数据。参数方面dropna的 subset 只检查价格和面积两列不要求全字段完整这样能保留更多有效样本。清洗后的文件命名为clean-utf8-v1.1版本号 v1.1 说明清洗规则迭代过一次这种命名习惯值得学。3.2 派生特征单价、房龄、区域标签清洗完基础字段后通常还要造几个派生特征供可视化用。单价可以直接用总价除以面积算出来和原始单价字段做交叉验证。房龄需要从「建造年份」字段推算如果原始数据没有年份可以从house_info里的「年建」关键词提取。区域标签一般从标题或地址字段里截取比如「朝阳」「海淀」这类行政区名。这一步的产出就是ershoufang-clean-utf8-v1.1.csv里多出来的数值列。这些列在后面的可视化阶段直接作为分组维度和坐标轴使用不需要在绘图代码里再算一遍。把特征工程前移到清洗阶段是让整个流水线更清晰的关键。4. 可视化分析用 pyecharts 和 matplotlib 把两万条数据讲成图4.1 区域均价对比用 pyecharts 做交互图两万条数据用静态图也能画但毕设答辩时交互图更抓眼球。pyecharts 的Bar和Map组件适合做区域维度的对比。下面是一个按区域统计均价的示例import pandas as pd from pyecharts.charts import Bar from pyecharts import options as opts df pd.read_csv(ershoufang-clean-utf8-v1.1.csv, encodingutf-8-sig) region_stats df.groupby(region).agg( avg_unit_price(unit_price_num, mean), count(unit_price_num, size) ).reset_index() region_stats region_stats[region_stats[count] 30] region_stats region_stats.sort_values(avg_unit_price, ascendingFalse) bar ( Bar() .add_xaxis(region_stats[region].tolist()) .add_yaxis(均价(元/平米), region_stats[avg_unit_price].round(0).tolist()) .set_global_opts( title_optsopts.TitleOpts(title各区域二手房均价对比), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate45)), yaxis_optsopts.AxisOpts(name元/平米), ) ) bar.render(region_price_bar.html)这里count 30的过滤很重要——样本量太小的区域均价没有统计意义画出来反而误导。rotate45是防止区域名太长导致 X 轴标签重叠。输出 HTML 而不是 PNG是为了答辩时能鼠标悬停看具体数值。4.2 面积-价格散点图和分布直方图散点图用 matplotlib 画更灵活可以叠加回归线看趋势import matplotlib.pyplot as plt import matplotlib matplotlib.rcParams[font.sans-serif] [SimHei] matplotlib.rcParams[axes.unicode_minus] False fig, axes plt.subplots(1, 2, figsize(14, 5)) axes[0].scatter(df[area_num], df[total_price_num], alpha0.3, s8) axes[0].set_xlabel(面积(平米)) axes[0].set_ylabel(总价(万)) axes[0].set_title(面积-总价散点图) axes[1].hist(df[unit_price_num], bins50, edgecolorwhite) axes[1].set_xlabel(单价(元/平米)) axes[1].set_ylabel(房源数量) axes[1].set_title(单价分布直方图) plt.tight_layout() plt.savefig(analysis_overview.png, dpi150)alpha0.3让重叠点能看出密度s8控制点的大小避免糊成一片。直方图bins50是经验值两万条数据分 50 组既能看出分布形态又不会太碎。中文字体设置是必须的否则标题和轴标签全是方框。提示如果SimHei报字体缺失换成Microsoft YaHei或WenQuanYi Micro HeiLinux 和 Windows 的可用字体不一样。5. 避坑与排查编码、反爬、字段错位这三件事最容易翻车5.1 现象CSV 用 Excel 打开全是乱码原因原始文件是 UTF-8 无 BOM 编码Excel 在中文 Windows 下默认按 GBK 解析。解决保存时用encodingutf-8-sig或者直接用ershoufang-origin-ansi.csv这个版本。资源里同时提供 utf8 和 ansi 两套文件就是为了兼容不同打开方式。5.2 现象采集跑了几页之后返回空列表或验证码页面原因请求频率过高触发风控或者 UA 被识别。解决把time.sleep的间隔调到 3 到 8 秒随机每次请求之间切换 UA必要时在 Session 里带上真实 Cookie。如果连续三页返回空停下来等几分钟再继续不要硬跑。5.3 现象清洗后数据量骤减从两万条掉到几千条原因dropna和数值过滤的阈值设得太紧或者正则没匹配上某些格式变体。解决先打印每一步的行数定位是哪一步砍掉的。常见做法是先用df[area_num].describe()看分布再决定过滤阈值不要凭感觉写。5.4 现象可视化图表里区域名重复或为空原因区域字段抽取时没做标准化同一个区出现了「朝阳」「朝阳区」「北京市朝阳区」三种写法。解决在清洗阶段统一去掉「市」「区」后缀或者用映射表归一化。空值记录直接排除不要用「未知」填充后参与均价计算。5.5 现象pyecharts 生成的 HTML 打开后图表不显示原因CDN 资源加载失败或者浏览器阻止了本地 HTML 的脚本执行。解决把bar.render()改成bar.render_notebook()在 Jupyter 里看或者下载 pyecharts 的静态资源到本地。答辩前一定要在答辩用的电脑上提前打开一次别等到现场才发现加载不出来。6. 进阶玩法把清洗流水线封装成可复用模块这套资源最值钱的地方不是某一张图而是它留下了一条可复现的流水线。我一般会把清洗逻辑抽成一个独立模块用配置文件控制阈值这样换一批数据不用改代码import yaml import pandas as pd def load_config(pathclean_config.yaml): with open(path, r, encodingutf-8) as f: return yaml.safe_load(f) def clean_pipeline(raw_path, config): df pd.read_csv(raw_path, encodingconfig[encoding]) df[total_price_num] df[total_price].apply(parse_price) df[area_num] df[house_info].apply(parse_area) df df.dropna(subset[total_price_num, area_num]) df df[ (df[area_num] config[area_min]) (df[area_num] config[area_max]) (df[total_price_num] config[price_min]) (df[total_price_num] config[price_max]) ] return df配置文件长这样encoding: utf-8-sig area_min: 10 area_max: 1000 price_min: 10 price_max: 5000这样改阈值不用动 Python 代码论文里写「清洗规则可配置」也更有说服力。验证方法很简单拿ershoufang-origin-utf8.csv跑一遍对比输出行数和ershoufang-clean-utf8-v1.1.csv是否一致。如果一致说明你的清洗逻辑和原项目对齐了如果不一致diff 一下差异记录大概率是某个正则的边界没对上。从那以后我每次拿到新的 CSV 数据集都强制先跑一遍describe()和head()确认字段类型和分布再动手写清洗——这个习惯帮我省掉了至少三次返工。希望帮到你。本文还有配套的精品资源点击获取
返回列表