尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Python爬虫实战:二手房数据采集清洗与可视化分析

发布时间:2026/9/11 20:20:16

资讯中心
01
ARTICLE

Python爬虫实战:二手房数据采集清洗与可视化分析

Python爬虫实战:二手房数据采集清洗与可视化分析
简介毕业设计项目以Python网络爬虫为主要技术手段围绕二手房交易平台进行房源数据采集并完成数据清洗、存储与可视化分析内容完整、流程清晰适合计算机相关专业学生作为毕业设计参考也适合想系统学习爬虫和数据可视化的开发者对照练习。资源包共157个文件压缩后约40MB文件结构清晰18个Python源码文件按功能划分爬虫采集、数据清洗、数据入库与可视化展示等模块18个CSV文件包含原始采集数据与清洗后的数据集方便对比验证15个HTML与11个JS组成前端可视化页面65张PNG图片保留了分析图表和运行效果截图另有PPT文档可直接用于毕业答辩准备。已有692人学习下载。整套资料完整呈现了从爬虫框架搭建、反爬处理、数据入库到图表展示的完整流程源码可直接运行或二次开发同时项目文件组织方式和注释也能为毕业论文中的模块设计、技术选型和结果展示提供直观参考。1. 从六个 CSV 文件反推爬虫的设计意图拿到这套毕业设计源码时最值得看的不是爬虫主程序而是压缩包里那六个 CSV 文件名。ershoufang-origin-utf8.csv 和 ansi 版本代表原始采集数据ershoufang-20000.csv 说明做了规模化采集clean-v1.1 系列则是清洗后的产出。一个 csv 文件命名的分层方式直接把「采集 → 清洗 → 分析」这条链路写在了明面上。对于准备做 Python 网络爬虫相关毕设或练手项目的人来说这套数据采集及可视化分析源码的价值在于它不仅有 requests 层面的爬虫实现还覆盖了数据清洗中编码处理、字段规整这些容易被忽视的环节最后用 pyecharts 完成可视化分析。全文会按「采集实现 → 清洗处理 → 可视化分析 → 并发优化 → 排错实战」的顺序拆开讲。2. 房源采集层requests 会话机制与反爬参数配置2.1 房源列表页的解析策略二手房源网站页面结构大同小异列表页通常一个 ul 容器下挂多个 li 节点每个 li 里包含房源标题、小区名、户型、面积、朝向、楼层、总价、单价等字段。用 lxml 配合 XPath 提取比 BeautifulSoup 更直接性能也更好适合批量抓取场景。import requests from lxml import etree import pandas as pd def parse_list_page(html_text): tree etree.HTML(html_text) items [] # 定位房源列表项 li_nodes tree.xpath(//ul[classsellListContent]/li) for li in li_nodes: title li.xpath(.//div[classtitle]/a/text()) # 位置信息在 flood 节点下 position li.xpath(.//div[classpositionInfo]//text()) # 总价与单价分别在不同 span 中 total_price li.xpath(.//div[classtotalPrice]/span/text()) unit_price li.xpath(.//div[classunitPrice]/span/text()) if title and total_price: items.append({ title: title[0].strip(), position: .join([p.strip() for p in position]), total_price: total_price[0].strip(), unit_price: unit_price[0].strip() if unit_price else }) return items代码说明xpath方法返回的是列表所以每个字段都做了空列表判断。positionInfo节点下文本分散在多个 span 里用join拼起来。这里抠出的字段后续会直接进入 DataFrame所以提前用字典结构封装。需要注意不同城市的链家房源列表页 class 名可能不一样sellListContent是北京站的写法切换城市时先手动打开页面检查 ul 的 class 属性值再改 XPath。2.2 请求头伪造与 session 复用反爬的第一道门槛是 User-Agent 检测。只伪装 UA 是不够的访问路径和请求频率也会被记录。推荐的组合是 requests.Session 配合完整的浏览器请求头并在两次请求之间加 sleep 控制频率。import time 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: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Referer: https://bj.lianjia.com/ } def fetch_list_pages(session, base_url, max_page100): all_items [] for page in range(1, max_page 1): url f{base_url}pg{page}/ try: resp session.get(url, headersHEADERS, timeout10) resp.raise_for_status() resp.encoding utf-8 parsed parse_list_page(resp.text) if not parsed: print(fpage {page} parse empty, stop.) break all_items.extend(parsed) time.sleep(1.5) # 控制采集频率 except Exception as e: print(fpage {page} error: {e}) continue return all_itemsSession 对象在这里重复使用 TCP 连接减少握手开销。timeout10防止某个页面阻塞整个循环raise_for_status()会在返回 4xx/5xx 时主动抛异常。sleep 1.5 秒是单机采集比较稳妥的节奏太快容易被封 IP太慢则 2 万条数据要跑很久。2.3 数据落地时的编码陷阱和解码细节采集到的数据写入 CSV 时编码问题直接决定后面能不能顺利读取。原项目的ershoufang-origin-utf8.csv和ershoufang-origin-ansi.csv两个文件就是同一个数据源在不同编码下的产物如果直接用 pandas 默认参数读取 ANSI 版本大概率出现乱码或 UnicodeDecodeError。# 优先用 utf-8 读取失败时回退到 gbk def safe_read_csv(path): for enc in [utf-8, gbk, gb18030]: try: df pd.read_csv(path, encodingenc) print(f{path} read with {enc}) return df except UnicodeDecodeError: continue raise ValueError(fcannot decode {path})Windows 环境下 pandas 的to_csv默认编码是utf-8引擎不同会有差异但 Excel 复制粘贴保存后经常会变成 ANSI即 GBK。gb18030是 GBK 的超集也能兼容繁体字做兜底编码很实用。3. 数据清洗从 2 万行原始记录到规整的 DataFrame3.1 字段拆分与单位换算的落地实现爬虫抓下来的字段往往是「北苑家园望春园 3室1厅 98.56平米 南北」这种复合字段不能直接用于分析。以 clean-v1.1 版本的数据为例清洗逻辑通常包含以下几类处理。import re def clean_house_data(df): # 拆分标题中的小区名和房源标签 df[community] df[title].str.extract(r^(.?)\s, expandFalse) df[layout] df[title].str.extract(r(\d室\d厅), expandFalse) # 面积统一转 float df[area] df[title].str.extract(r([\d.])平米, expandFalse).astype(float) # 总价转整数560万 - 560 df[total_price] df[total_price].str.replace(万, ).astype(int) # 单价转每平方米数值56000元/平 - 56000 df[unit_price] ( df[unit_price] .str.replace(元/平, ) .str.replace(,, ) .astype(int) ) return df几个关键点str.extract配合正则比str.split稳定因为字段内的空格并不规律面积可能带小数直接用 float 转换单价里的千分位逗号要先清掉。expandFalse返回 Series方便直接赋值给新列。这套逻辑对应到原始数据时要特别注意字符串前后的空格和全角字符比如室厅这种全角数字清洗前最好做一次unicodedata.normalize或直接映射转换。3.2 重复值判定和异常值过滤的边界条件主键缺失是常态。同一套房子可能在采集过程中被反复抓到如果只按标题去重一套房源改价后标题变化就会漏掉按 URL 去重更可靠但列表页解析时不一定保留了详情页链接。折中方案是对community layout area total_price求组合去重。def dedup_and_filter(df): key_cols [community, layout, area, total_price] df[tmp_key] df[key_cols].astype(str).agg(_.join, axis1) df df.drop_duplicates(subset[tmp_key], keepfirst) df df.drop(columns[tmp_key]) # 过滤单价明显偏低的记录可能有抵押或产权问题 df df[df[unit_price] 5000] # 过滤面积小于 10 平的异常数据 df df[df[area] 10] # 总价与单价的逻辑一致性校验总价*10000/面积 应约等于单价 df df[((df[total_price] * 10000 / df[area]) - df[unit_price]).abs() 3000] return df组合去重能容忍总价小幅变动但同一套房如果单价和面积几乎不变、总价变动超过 3000 元/平会被放行。这是刻意留出的宽松度——房价数据本身波动是正常的太严的校验反而会误删有效数据。3.3 清洗结果的版本管理原包里 clean 系列分 v1.1说明清洗逻辑迭代过至少一次。我一般会在清洗脚本里直接导出带版本号的文件而不是覆盖原表。df.to_csv(ershoufang-clean-utf8-v1.1.csv, indexFalse, encodingutf-8-sig) df.to_csv(ershoufang-clean-ansi-v1.1.csv, indexFalse, encodinggbk)utf-8-sig会在文件头写入 BOMExcel 打开时不会乱码这比纯 utf-8 更适合给非技术同学预览。两份文件共存的习惯能避免「清洗逻辑改到一半原始数据被污染」的尴尬。4. 可视化分析价格分布与楼层特征的图表参数详解4.1 聚合统计的 Pandas 操作序列可视化前先做数据透视分析「各区域房源数量与均价」「户型与总价的关系」「面积与单价的相关性」这三维度。Pandas 的groupby agg可以一次算出多个指标。def build_pivot(df): district_stat ( df.groupby(district) .agg( house_count(title, count), avg_total_price(total_price, mean), avg_unit_price(unit_price, mean), median_area(area, median) ) .reset_index() .sort_values(house_count, ascendingFalse) ) district_stat[avg_unit_price] district_stat[avg_unit_price].round(0) return district_statagg括号里每个字段名对应一个聚合函数round(0)去掉小数点避免图例数值过长。median_area比 mean 更能反映典型面积因为大面积豪宅会把均值拉高。如果数据里没有district字段需要用community反查行政区划常见的做法是维护一份小区名到行政区的映射字典或者用高德 API 的地理编码接口批量补全。4.2 pyecharts 图表参数与渲染配置pyecharts 是当前主流的 Python 可视化分析库链式调用写起来流畅生成的 HTML 可以直接交给答辩老师演示。from pyecharts.charts import Bar, Pie, Scatter from pyecharts import options as opts def draw_region_bar(district_stat): bar ( Bar(init_optsopts.InitOpts(width1000px, height600px)) .add_xaxis(district_stat[district].tolist()) .add_yaxis( 房源数量, district_stat[house_count].tolist(), category_gap40% ) .set_global_opts( title_optsopts.TitleOpts(title各区域二手房房源数量分布), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate30)), yaxis_optsopts.AxisOpts(name套数), toolbox_optsopts.ToolboxOpts(is_showTrue) ) ) return bar.render(region_bar.html)category_gap控制柱间距区域名过多时rotate30让 x 轴标签斜着显示避免文字重叠。ToolboxOpts开启后图表右上角会出现导出图片、查看数据等按钮演示时很加分。散点图用来观察面积和总价的关系opts.ScatterOpts里可以设置标记大小和颜色渐变对发现总价随面积增长的拐点很有帮助。4.3 地图可视化的数据适配如果数据覆盖多个城市或省份可以用 Map 图展示房价空间分布。地图组件需要国家或省市 GeoJSON 数据pyecharts 自带中国大陆地图但依赖echarts-china-provinces包。from pyecharts.charts import Map def draw_heat_map(district_stat): data_pair [ [row[district], round(row[avg_unit_price])] for _, row in district_stat.iterrows() ] map_chart ( Map() .add( 均价, data_pair, maptype北京, is_map_symbol_showFalse ) .set_global_opts( visualmap_optsopts.VisualMapOpts( min_20000, max_120000, is_piecewiseTrue, pos_left10% ) ) ) map_chart.render(price_map.html)is_map_symbol_showFalse隐藏非当前区域的散点标记is_piecewiseTrue把颜色梯度变为分段的图例范围指定 min 和 max 后颜色映射不会因为单个别墅区的高价而失真。5. 高并发采集线程池限速与断点续爬的工程化改造5.1 ThreadPoolExecutor 的并发参数选择串行采集 2 万条数据按每页 30 条、每条延时 1.5 秒来算大概需要 17 分钟。如果目标数据量翻倍到 5 万条以上串行方案的耗时就会让人焦虑。concurrent.futures.ThreadPoolExecutor是改造成本最低的并发方案。import concurrent.futures as cf def fetch_page_batch(session, base_url, page_list): results {} with cf.ThreadPoolExecutor(max_workers4) as executor: future_map { executor.submit(fetch_single_page, session, base_url, pg): pg for pg in page_list } for future in cf.as_completed(future_map): pg future_map[future] try: results[pg] future.result() except Exception as e: print(fpage {pg} failed: {e}) results[pg] [] return results def fetch_single_page(session, base_url, page): url f{base_url}pg{page}/ resp session.get(url, headersHEADERS, timeout10) resp.raise_for_status() resp.encoding utf-8 time.sleep(0.8) return parse_list_page(resp.text)max_workers4 是保守值。多数房源网站对单 IP 的并发连接数限制在 5-8超过后直接返回 503 或验证码页面。这里特别把 sleep 放在 fetch_single_page 内部而不是主循环里保证每个线程各自限速整体请求速率约等于 workers 数除以单页耗时比集中 sleep 更均匀。5.2 断点续爬时的进度记录方法采集过程中断后重新开始最怕的是不知道哪些页已经抓过。常规做法是把完成页码写入本地文件启动时读取。import os import json def load_progress(progress_fileprogress.json): if os.path.exists(progress_file): with open(progress_file, r) as f: return set(json.load(f)) return set() def save_progress(done_pages, progress_fileprogress.json): with open(progress_file, w) as f: json.dump(sorted(done_pages), f) def crawl_with_resume(base_url, total_pages200): done load_progress() todo [pg for pg in range(1, total_pages 1) if pg not in done] with cf.ThreadPoolExecutor(max_workers4) as executor: future_map { executor.submit(fetch_single_page, session, base_url, pg): pg for pg in todo } for future in cf.as_completed(future_map): pg future_map[future] try: page_items future.result() if page_items: save_page_items_to_csv(pg, page_items) done.add(pg) save_progress(done) except Exception: pass进度文件每完成一页就更新即使中途强制终止下次运行也能跳过已完成页面。这里用 set 去重转 list 后写入 JSON读取时再转回 set保证序列化兼容。6. 排错实战编码、反爬和解析失效的定位思路6.1 从乱码到定位编码问题的三步排查法遇到 CSV 乱码先别急着改代码。用chardet判断文件真实编码。pip install chardet python -c import chardet; dataopen(ershoufang-origin-ansi.csv,rb).read(5000); print(chardet.detect(data))chardet.detect的返回结果里encoding字段给出预测编码confidence表示置信度。如果显示 GB2312 或 GBK直接用对应编码读取即可。读取时抛出UnicodeDecodeError优先看报错提到的是哪个字节位置比如gbk codec cant decode byte 0xaf说明文件里混入了其他编码的字符这时要用errorsreplace先把坏字符替掉再定位具体行。6.2 解析规则失效的常见触发点爬虫跑一段时间后列表为空最常见的原因是 HTML 结构变了——网站在列表项里加了广告位、页面改版后 class 名重命名。这种问题靠肉眼排查很费劲建议用下面的对比脚本确认。# 将当前页面保存后与初始版本的 node 数量对比 from lxml import etree with open(current_page.html, r, encodingutf-8) as f: current etree.HTML(f.read()) li_count len(current.xpath(//ul[classsellListContent]/li)) print(fli count: {li_count})如果li_count为 0用浏览器开发者工具查看列表项的结构把 XPath 里的 class 替换成新的。另一个隐蔽原因是登录态过期点击页码跳转到登录页而不是列表页表现为所有字段为空却不报错此时检查响应 URL 是否变成了passport相关地址。6.3 采集频率和 IP 风险的控制技巧单机爬虫避免被封没有通用保险方案但有几条实践准则每个线程的发包间隔不低于 0.6 秒高峰期晚上 8-11 点访问流量大降低并发数采集一定量后主动休息 10-30 秒模拟用户阅读页面的节奏。如果网站已经弹出验证码停止当前任务等一段时间再恢复而不是硬着头皮重试。对于毕设场景2 万条数据量完全不需要分布式采集把延时调高、跑慢一点换取数据的稳定获取更划算。压缩包里那 20000 条 CSV 数据本身就是这个策略的产物——数据完整度比采集速度更影响后续分析的可信度。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。