简介:这是一份面向毕业设计的Python网络爬虫实战资源,主题为二手房源数据采集与可视化分析,适用于高校计算机相关专业学生、爬虫初学者以及需要快速搭建同类系统的开发者。资源包内共157个文件,压缩后大小约40.03MB,结构上涵盖18个Python源码文件、18个CSV格式的房源数据、15个HTML可视化页面、11个JavaScript交互脚本、65张PNG分析图表,并附有答辩PPT文档。其中CSV数据包含UTF-8与ANSI等多种编码的原始数据及清洗后的版本,便于对比处理效果。目前已有693人学习下载。通过本包可获得从网页解析、数据清洗、存储到图表展示的完整实现思路,能帮助读者直观理解二手房价格、区域分布等维度的分析过程,同时提供可直接运行的代码和演示文稿,节省从零搭建系统的时间,也为毕业设计答辩提供了现成的展示材料。
1. 二手房爬虫项目:从采集到可视化的完整毕业设计
想在一个城市买二手房,最靠谱的做法不是跑线下门店,而是先把挂牌数据拉下来做一轮分析。这个 python 网络爬虫二手房源数据采集及可视化分析毕业设计,做的就是这件事:从某房产平台抓取二手房源挂牌信息,清洗后存成 CSV,再用图表回答“哪个区域贵、哪个户型多、面积和总价是什么关系”。适合正在做数据类毕业设计、或者想练手完整爬虫流程的开发者——它不是一个只跑通一页的 Demo,而是把“请求 → 解析 → 清洗 → 可视化”整条链路都串起来的项目,源码和 PPT 文档都在包里,拿到就能跑。
2. 项目结构梳理:六个 CSV 文件背后的设计意图
2.1 从原始到清洗:文件命名里的版本管理
解压后最先看到的是六个 CSV 文件,它们不是随机堆在一起的,而是数据流水线的三个阶段产物。ershoufang-origin-utf8.csv和ershoufang-origin-ansi.csv是原始数据,两个文件内容相同,区别只在编码——一个 UTF-8,一个 ANSI。这么做是有原因的:Windows 上的 Excel 直接打开 UTF-8 会乱码,ANSI 版本就是给这类场景准备的后悔药。
ershoufang.csv是主数据集,大约 20000 条记录,文件名里那个空格是原作者的命名习惯,读文件时注意别被坑。ershoufang - 20000.csv是它的备份副本,数据量完全一致。真正有价值的是最后两个文件:ershoufang-clean-utf8-v1.1.csv和ershoufang-clean-ansi-v1.1.csv,v1.1 意味着清洗逻辑迭代过至少两次,这两份才是拿来分析和可视化的最终输入。文件名作为版本号,是我见过比较规范的毕设习惯。
| 文件 | 定位 | 说明 |
|---|---|---|
| origin-utf8/ansi | 原始数据 | 未清洗,含缺失和脏字段 |
| ershoufang.csv | 主数据 | 20000 条,可直接读取 |
| clean-v1.1-utf8/ansi | 清洗结果 | 类型已转换,缺失已处理 |
2.2 技术栈选型:为什么不用 Scrapy 而用 requests
技术栈用的是 Python + requests + BeautifulSoup + pandas + pyecharts,这是国内数据类毕设里出现频率很高的组合。有人会问“爬虫为什么不上 Scrapy”,实际原因是:毕设的核心是“数据采集 + 分析”,不是“分布式爬虫”。用 requests 同步请求、BeautifulSoup 解析,代码更直白,逻辑一层层看得见,答辩的时候也更好讲清楚。
pandas 负责数据清洗和结构变换,可视化用的是 pyecharts,它能生成带交互效果的 HTML 图表,鼠标悬停能看到数值,比 matplotlib 静态图更适合放进论文和 PPT。整体链路是:requests 抓页面 → BeautifulSoup 提取字段 → pandas 清洗去重 → pyecharts 出图,每一步都有明确的输入输出,后期改任何一环都不影响其他模块。
2.3 数据字段拆解:爬下来到底有哪些字段
从清洗表 v1.1 的列名看,每条房源记录包含大约 10 个字段:小区名称、所在区域、户型室厅卫、建筑面积、朝向、装修情况、楼层、总价、单价,外加抓取时间。这些字段覆盖了二手房分析里最常用的维度,区域和总价可以做价格分布,户型和面积可以做供需结构,朝向和装修则是楼层之外的重要价值因子。
字段命名直接用了拼音,像xiaoqu、quyu、huxing、mianji、chaoxiang、zhuangxiu、louceng、zongjia、danjia。这在毕设里很常见——少一层中英映射就少一类错误,但也意味着如果你想把数据集迁移到自己的项目中,列名重命名这一步绕不过去。清洗版还把原本混在字符串里的总价拆成了数值列,这个细节会在后面第 4 章专门展开。
3. 爬虫核心模块拆解:一个请求从发出到入库的完整链路
3.1 请求构造:Header 伪装与 URL 参数规则
爬虫部分的第一步是向目标站点发起请求。拿到代码后第一个看的就是请求函数,它直接用 requests.get() 配合自定义 Header,把 User-Agent 伪装成浏览器,避免被服务器按默认 Python 请求头识别。代码里还写入了 Referer 字段——有一些站点的反爬逻辑会校验 Referer 是否来自自身域名,漏掉这一项容易返回 403。
def fetch_page(city_code, page_num): url = f"https://某平台.{city_code}.example.com/ershoufang/pg{page_num}/" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://某平台.example.com/", } resp = requests.get(url, headers=headers, timeout=10) resp.encoding = "utf-8" return resp.text这个函数接收城市代码和页码两个参数,拼出列表页 URL 并返回 HTML 字符串。timeout=10是防止某个页面长时间无响应把整个抓取流程卡死;resp.encoding手动指定 utf-8,是为了绕开某些站点页面里没有声明 charset 导致的中文乱码问题。这里的pg{pagenum}/是市面上多数列表页的分页规律,一页 30 条,20000 条数据对应 700 页左右。
3.2 页面解析:BeautifulSoup 提取目标字段
拿到 HTML 之后,解析逻辑是对每一个li标签做字段定位。代码里用的是soup.select()方法,按 class 选中每个房源卡片,然后从中继续提取小区名、区域、户型、面积、朝向等字段。这里踩过的坑是:CSS 选择器写得太死,站点一旦改版升级,class 名字一变,整个 parse 函数直接崩掉。
def parse_house_item(item): try: title = item.select_one(".title a").text.strip() house_info = item.select_one(".houseInfo").text.strip().split("|") total_price = item.select_one(".totalPrice span").text.strip() unit_price = item.select_one(".unitPrice span").text.strip() return { "title": title, "huxing": house_info[0], "mianji": house_info[1].replace("平米", ""), "chaoxiang": house_info[2], "zhuangxiu": house_info[3], "louceng": house_info[4], "zongjia": total_price, "danjia": unit_price, "crawl_time": time.strftime("%Y-%m-%d %H:%M:%S"), } except AttributeError: return None解析时对每个字段的缺失都做了异常兜底,AttributeError捕到就返回 None,由后续的清洗步骤统一处理。house_info是从|分隔的字符串里切出的列表——这是各平台房源标题下常见的结构化文本,切完后按位置索引,所以任何一项为空都会导致整个列表顺序变化,这种问题只有在后面清洗阶段才能暴露出来。
3.3 多页抓取与数据落盘
列表页循环抓取用的是for循环加range(1, total_pages + 1),每页抓完调用time.sleep(random.uniform(2, 5))做随机延时。20000 条数据算下来,硬跑也要半小时以上,这属于正常的采集节奏。代码把每页解析结果直接追加到一个 list,最后一次性用 pandas 转成 DataFrame 再to_csv()落盘。
def crawl_all(city_code, total_pages): all_data = [] for page in range(1, total_pages + 1): html = fetch_page(city_code, page) soup = BeautifulSoup(html, "lxml") items = soup.select(".sellListContent li") for item in items: parsed = parse_house_item(item) if parsed: all_data.append(parsed) time.sleep(random.uniform(2, 5)) df = pd.DataFrame(all_data) df.to_csv("ershoufang.csv", index=False, encoding="utf-8-sig")encoding="utf-8-sig"是核心细节——普通 utf-8 写入的 CSV 用 Excel 打开会乱码,utf-8-sig会写入 BOM 头,Excel 直接双击就能正确识别。这也是为什么后面清洗流程会多出 ansi 版本的另一个原因。每页抓完强制 sleep 2 到 5 秒,是为了避免请求频率过高触发站点限流,属于最基本的反反爬礼貌。
4. 数据清洗与可视化:从 20000 条脏数据到六张图表
4.1 清洗逻辑:类型转换与去重去脏
原始 CSV 里总价是字符串,带“万”字后缀,单价则是“元/平”格式夹着数字和单位。清洗的第一步是用正则把纯数字抽出来,再转成 float,这样后面才能做聚合运算。同时,同一套房子在列表页翻页时可能重复出现,所以按标题加面积的组合键做去重,避免统计时翻倍。
def clean_data(df): df["zongjia"] = df["zongjia"].str.replace("万", "").astype(float) df["danjia"] = df["danjia"].str.replace("元/平", "").str.replace(",", "").astype(int) df["mianji"] = pd.to_numeric(df["mianji"], errors="coerce") df = df.dropna(subset=["zongjia", "mianji"]) df = df.drop_duplicates(subset=["title", "mianji"], keep="first") df = df[df["zongjia"] > 0] df.to_csv("ershoufang-clean-v1.1.csv", index=False, encoding="utf-8-sig")errors="coerce"是 pandas 的容错开关,遇到无法转成数字的值不会抛异常,而是置为 NaN,随后的dropna()统一清掉。单价里可能混入“暂无”这类文本状态,也在这里一并处理。清洗完的 v1.1 文件和原始版本相比,行数大约会少掉 3% 到 5%,这部分差值在论文里可以作为“数据预处理效果”的量化证据。
4.2 可视化设计:区域均价、户型分布、面积价格关系
可视化部分用 pyecharts 生成四个核心图表:区域均价柱状图、户型占比饼图、总价散点图、单价热力图。区域均价图是按quyu分组对zongjia求均值,倒序排列后输出,能直接看出哪些区域挂牌价高;户型饼图用huxing分组算数量占比,三室永远是占比最大的主力户型。
散点图的 X 轴是建筑面积、Y 轴是总价,每个点是一套房源。这种图最能说明问题——绝大多数点落在一个斜向上的带状区域内,说明面积和总价整体线性相关,但总会有几个远离主趋势的离群点,在答辩时解释这些点往往能加分。热力图进一步把单价按区域和户型两个维度做透视,能回答“哪个区域的哪个户型最贵”这类跨维度问题,这是单独看柱状图发现不了的。
4.3 出图代码:把 pandas 结果交给 pyecharts
from pyecharts.charts import Bar, Pie, Scatter from pyecharts import options as opts region_price = df.groupby("quyu")["zongjia"].mean().sort_values() bar = ( Bar() .add_xaxis(region_price.index.tolist()) .add_yaxis("平均总价(万)", region_price.values.tolist()) .set_global_opts(title_opts=opts.TitleOpts(title="各区域二手房平均总价")) ) bar.render("region_price.html")Pie 和 Scatter 的写法完全同一套路,add_xaxis和add_yaxis分别传入类别和数值,最后render()生成独立 HTML。pyecharts 生成的图表是带交互效果的,鼠标移上去显示数值,图表右侧可以开关数据系列,这一点比 matplotlib 更适合展示在毕业答辩现场。如果想把所有图表整合到一个页面,还可以用Tab组件把四个图挂到一个 HTML 里,做成一个简单的数据看板。
5. 常见问题与排查:跑通这套代码会踩的五个坑
5.1 CSV 打开乱码:编码声明不一致
现象是把ershoufang.csv用 Excel 双击打开,中文全部变成乱码;或者用 pandas 读出来没问题,但用记事本打开能看到奇怪的符号。原因有两个,一是原始数据的页面本身是 UTF-8,但读的时候用了系统默认的 GBK 编码;二是to_csv()写文件时没加 BOM 头。
解决方法是写入时固定用encoding="utf-8-sig",读取时用encoding="utf-8",这样一半的乱码问题直接消失。如果某些第三方工具只认 ANSI,就用df.to_csv(path, encoding="gbk")再导一份。项目里同时保留 utf-8 和 ansi 两套文件就是这个思路。
5.2 爬取过程中途断掉:没有断点续抓
现象是程序跑到 300 多页的时候网络波动报错,重新运行又从第 1 页开始。原因是代码里没有做增量断点逻辑,所有数据都存在内存里,进程一死就全丢。常见做法是每抓 50 页就把当前 list 追加写一次 CSV,或者判断目标 CSV 已存在多少行,从对应页再往下抓。我一般在for循环里加一个page_counter % 50 == 0的分支,先落盘再继续,这样最多丢 50 页的数据。
5.3 页面字段被截断或换行符搅乱解析
现象是house_info字符串用|split 之后,某个字段里混入了\n或空格,导致数组长度对不上,解析出来错位。原因是 HTML 中某些房源标题带了换行标签,.text提取时把换行符也抓进来了。解决方法是 split 之前先re.sub(r"\s+", "", text)把所有不可见字符清掉,再按|切割。血泪经验是这种问题往往在清洗阶段才暴露,而报错信息完全定位不到爬虫代码,只能在造数据时多做一步处理。
5.4 平台反爬导致抓取数量不足 20000
现象是跑完全程只拿到 6000 多条数据,控制台没有任何报错。原因通常是请求频率过高,触发服务器的验证码或跳转中介页。解决方法是把 sleep 时间从固定 2 秒改成 3 到 6 秒随机,同时检查抓到的页面里是否还有房源li节点——如果连续 5 页解析出 0 条,就说明已经被拦截,需要停一下再继续。也可以在某次请求返回异常时打印状态码,403 就等 60 秒,这是我自己常用的降级策略。
5.5 房源数据存在隐性重复
现象是按title去重后数据量仍然偏多,同一个小区的同户型房子被算成两套。原因是中介会在同一套房源标题里更换描述词,比如改成“急售”“满五唯一”前缀。解决办法是去重键不要只用一个字段,用小区 + 面积 + 总价三个字段组合判断,或者把title去掉数字和形容词之后再做归一化去重。实际上去重是数据清洗里最花时间的一步,做好这一步的可视化结果才有说服力。
6. 二次开发:把这套爬虫扩成自己的毕设或产品原型
6.1 换成 Scrapy 架构:从单脚本到工程化
这套代码的核心逻辑全部在一个 Python 脚本里,如果要扩展成科研项目或者简历上的亮点,最值得做的就是迁到 Scrapy 框架。迁移的成本其实不高:fetch_page变成start_requests()方法,parse_house_item变成parse()回调,多页循环用scrapy.Request()的callback参数驱动,不用再手动管理 sleep。Scrapy 自带去重过滤和请求调度,20000 条数据跑下来比 requests 循环版稳定很多,还免费获得日志和统计报表。
import scrapy class ErshoufangSpider(scrapy.Spider): name = "ershoufang" start_urls = ["https://某平台.example.com/ershoufang/pg1/"] def parse(self, response): for li in response.css(".sellListContent li"): yield { "title": li.css(".title a::text").get(), "zongjia": li.css(".totalPrice span::text").get(), "danjia": li.css(".unitPrice span::text").get(), } next_page = response.xpath("//a[contains(text(),'下一页')]/@href").get() if next_page: yield scrapy.Request(response.urljoin(next_page))这段代码把原来的循环和 sleep 全部交给框架处理,实际抓取速度反而更快,因为 Scrapy 的下载器是异步的。挂在pipelines.py里的清理逻辑可以直接复用第 4 章的清洗函数,最后scrapy crawl ershoufang -o output.csv一条命令出结果。
6.2 增加地理编码:让可视化上地图
如果想让毕设跳出柱状图饼图的套路,可以给每个小区加经纬度,然后在地图上画气泡图。每一条房源记录里都有小区名和区域名,用地理编码服务转成坐标即可,转完再用 pyecharts 的Map类型做区域聚合热力图。地图可视化的效果远好于柱状图,论文配图也更有质感。
6.3 从描述统计走向预测模型
数据集里的面积、总价、单价、区域、朝向、楼层已经足够支持做一个简单的房价预测模型。建议用 sklearn 里的线性回归或随机森林,把区域和朝向做 one-hot 编码,面积和楼层做数值特征,预测目标设为总价。模型评估用 R 方和 MAE,分数不用太高,0.6 以上就能在论文里讲清楚“哪些特征对价格影响最大”这个分析结论。这是把爬虫项目从工具型升级成分析型的关键一步。
我在做完这套改造之后,最大的教训是:先确认数据质量再谈模型和可视化,原始数据里朝向字段有十几类写法,“南北”和“南 北”在去重时会被当成两个值,不处理干净,后面的图表和模型全是错的。从那以后我每次拿到爬虫数据,都会先info()和value_counts()检查一遍每个字段,确认没有脏值再往下走。希望帮到你。
本文还有配套的精品资源,点击获取