ARTICLE DETAIL

资讯详情

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

南京二手房数据采集与可视化完整实践指南

南京二手房数据采集与可视化完整实践指南 简介本资源是一套面向本科毕业设计、课程设计与期末大作业的Python实战项目聚焦南京二手房市场数据采集与多维度可视化分析适合零基础入门到中阶实践的学习者。项目完整覆盖网络爬虫RequestsBeautifulSoup、数据清洗Pandas、地理信息处理Geopy高德API、交互式图表绘制PlotlyPyecharts及HTML报告生成全流程代码逐行注释清晰配套文档详述部署步骤与运行逻辑。压缩包含158个文件以18个核心Python脚本、18个结构化CSV数据集、65张可视化结果图PNG、15个可直接浏览的HTML分析报告为主辅以JS前端交互支持与INI配置文件整体40.02MB目录组织规范模块职责明确。目前已有159人学习下载开箱即用无需复杂环境配置可快速复现高分项目成果是提升数据分析工程能力与答辩展示效果的优质参考范例。1. 南京二手房数据采集可视化不是“爬完就跑”而是能交差、能答辩、能复现的完整闭环你是不是也经历过——花三天写了个爬虫跑出几百条数据一画图发现价格全是0字段名乱码成“某某小区”导出Excel打开是方块字改完编码又卡在反爬验证码弹窗没接口、Headers被轮换、IP被限频最后硬凑个折线图交上去导师问“南京主城区和远郊价格梯度怎么体现学区房溢价有没有统计显著性”当场哑火。这个项目不是“Python爬虫入门demo”它是一套从真实链家/贝壳页面结构出发、绕过基础反爬、清洗南京本地化字段如“玄武湖景”“南师附中本部划片”、用地理坐标做热力图、按行政区做箱线图对比、带置信区间标注的完整分析流程。代码里每个.py文件都带中文注释requirements.txt锁死版本README.md写明每步执行顺序和预期输出连ershoufang-clean-utf8-v1.1.csv这种命名都告诉你v1.1比v1.0多修了哪3个字段空值逻辑。适合正在赶毕业设计DDL、被导师要求“必须有原始数据来源说明”“图表要带误差棒”“清洗步骤可追溯”的本科生也适合想快速搭一个城市房价分析脚手架的课程设计者——它不教你Python语法但教你怎么让Python在真实业务场景里不翻车。2. 数据采集从链家南京站HTML结构解析到稳定抓取2万条房源的实操路径2.1 为什么选链家而不是贝壳三个硬约束决定技术选型这个项目没用Selenium模拟点击也没调用任何第三方API核心是纯RequestsBeautifulSoup静态解析原因很现实链家南京站nj.lianjia.com的列表页HTML结构稳定div classinfo clear包裹每条房源a classtitle href...含详情页URLspan classpriceInfo里价格数字独立成标签不像贝壳把价格藏在JS变量里反爬强度适中无滑动验证、无动态token仅需构造合理User-AgentReferer随机延时IP封禁阈值约300次/小时实测字段完整性高小区名、户型、面积、朝向、楼层、装修、挂牌时间、单价、总价、关注人数、带看次数全部在列表页DOM中可见无需跳转详情页——这是省下80%请求量的关键。提示项目里spider_lianjia_nj.py的get_page_html()函数中headers字典明确写了User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36这不是随便抄的而是用Chrome开发者工具Network面板抓包确认的真实UA。别用网上搜的“通用UA”链家会校验UA中的Chrome/版本号是否匹配当前主流版本。2.2 分页逻辑与URL构造避开“第100页后返回空数据”的坑链家南京二手房列表页URL格式为https://nj.lianjia.com/ershoufang/pg{page}/但实际有效页数远小于显示页码。项目采用“增量探测法”而非硬编码页数先请求pg1解析div classpage-box># spider_lianjia_nj.py 关键片段 def get_total_pages(session): 从第一页提取data-totalpage属性但需二次验证 url https://nj.lianjia.com/ershoufang/pg1/ resp session.get(url, headersheaders, timeout10) soup BeautifulSoup(resp.text, lxml) page_box soup.find(div, class_page-box) if page_box and page_box.get(data-totalpage): return int(page_box[data-totalpage]) return 100 # 保守 fallback def crawl_all_pages(session, max_pages150): all_data [] for page in range(1, max_pages 1): url fhttps://nj.lianjia.com/ershoufang/pg{page}/ try: resp session.get(url, headersheaders, timeout10) if resp.status_code ! 200: print(fPage {page} returned {resp.status_code}, stop crawling) break soup BeautifulSoup(resp.text, lxml) items soup.find_all(div, class_info clear) if len(items) 5: # 连续少于5条视为结束 print(fPage {page} has only {len(items)} items, stop) break # 解析items逻辑... except Exception as e: print(fError on page {page}: {e}) continue这段代码里len(items) 5是血泪经验链家在页码超出实际数据量时会返回一个只有2-3条“推荐房源”的空白页而非404。硬设range(1,120)会导致后半段抓一堆无效数据。2.3 字段提取与编码处理为什么ershoufang-origin-ansi.csv和ershoufang-origin-utf8.csv要并存原始HTML中小区名、区域名等中文字段在不同服务器响应头中可能声明为charsetgbk或charsetutf-8直接用resp.text会乱码。项目采用双保险策略先用resp.content二进制读取再用chardet.detect()自动识别编码对gbk编码强制用decode(gbk, errorsignore)同时保存两份原始文件ershoufang-origin-utf8.csv统一转UTF-8、ershoufang-origin-ansi.csv保留原始ANSI编码方便后续排查乱码源头。关键参数说明errorsignore跳过无法解码的字节避免程序中断但会丢失个别字符如某些楼盘名里的生僻字chardet库需单独安装pip install chardet项目requirements.txt已包含ershoufang-origin-ansi.csv的ANSI指Windows系统默认的GBK编码不是ISO-8859-1这点在文档Data_Cleaning_Guide.md里专门用表格对比了三种编码下“仙林”二字的十六进制值。3. 数据清洗从2万行脏数据到ershoufang-clean-utf8-v1.1.csv的7步标准化流程3.1 字段映射表南京本地化字段的语义对齐原始HTML中同一信息在不同房源DOM位置不一致比如“装修情况”可能在div classflood里写“精装”也可能在div classtag里标“精装修”。项目定义了标准字段映射表见config/field_mapping.json原始HTML位置提取规则标准字段名示例值div classhouseInfo内文本正则r(\d)室(\d)厅bedroom_count3div classpositionInfo内文本正则r([\u4e00-\u9fa5])区district玄武span classpriceInfo内span标签int(re.search(r(\d)万, text).group(1))total_price_wan320注意district字段提取时正则r([\u4e00-\u9fa5])区只匹配“XX区”不匹配“江宁开发区”这类因为项目明确限定分析范围为市辖区鼓楼、秦淮等6个江宁、浦口等虽属南京但按行政规划单列——这在答辩时是加分项体现数据边界意识。3.2 空值与异常值清洗三类南京特有脏数据的处理逻辑清洗脚本clean_data.py针对南京市场特性设计规则“满五唯一”字段缺失链家列表页不显示该政策标签但详情页才有。项目将所有缺失值统一设为False保守假设并在文档中注明“此字段需结合详情页补全当前版本暂未实现”面积单位混用部分房源写“89㎡”部分写“89平”统一转为float并保留小数点后1位单价异常值过滤南京二手房单价普遍在2-8万/㎡但爬到3条单价15万/㎡的数据疑似别墅或误标用IQR法剔除Q1 - 1.5*IQR到Q3 1.5*IQR之外的值设为NaN。# clean_data.py 片段南京单价IQR过滤 def filter_price_outliers(df, price_colunit_price_yuan_per_m2, threshold1.5): Q1 df[price_col].quantile(0.25) Q3 df[price_col].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - threshold * IQR upper_bound Q3 threshold * IQR # 南京实际业务中低于5000元/㎡多为车库或车位高于120000元/㎡多为误标 lower_bound max(lower_bound, 5000) upper_bound min(upper_bound, 120000) mask (df[price_col] lower_bound) | (df[price_col] upper_bound) df.loc[mask, price_col] np.nan return df这里max/min限界是关键——IQR本身会受极端值影响先用业务常识卡住上下限再算IQR避免“用异常值算出异常阈值”。3.3 地理坐标补全用高德API批量解析小区名到经纬度ershoufang-clean-utf8-v1.1.csv比v1.0多出lng经度、lat纬度两列靠geocode_amap.py调用高德Web服务API实现输入community_name如“金陵雅筑”输出{status:1,count:1,geocodes:[{province:江苏省,city:南京市,district:鼓楼区,location:118.765432,32.098765}]}失败重试单次请求超时设为5秒失败后间隔1秒重试最多3次频控高德免费版限QPS1脚本用time.sleep(1.1)硬控节奏2万条数据耗时约6小时。提示API Key需自行申请高德开放平台项目config/amap_config.py留了占位符YOUR_AMAP_KEY填入后即可运行。别用别人共享的Key会被限流。4. 可视化分析不只是plt.plot()而是带业务解释的南京房价图谱4.1 行政区房价箱线图为什么用seaborn.boxplot()而不是matplotlib.bar()单纯用柱状图展示各区均价如鼓楼5.2万/㎡、建邺4.8万/㎡会掩盖内部差异。南京二手房价格离散度极大鼓楼既有3万/㎡的老破小也有12万/㎡的学区豪宅。项目用箱线图sns.boxplot(xdistrict, yunit_price_yuan_per_m2)呈现四分位距箱体上下沿 Q1/Q3体现中间50%房源价格区间箱内横线 中位数比均值更能代表“典型价格”须线延伸至Q1-1.5IQR/Q31.5IQR超出须线的点为异常值如河西某江景大平层。# viz_district_analysis.py import seaborn as sns import matplotlib.pyplot as plt plt.figure(figsize(12, 6)) # 按南京行政区划顺序排序非字母序 district_order [玄武, 秦淮, 建邺, 鼓楼, 栖霞, 雨花台] ax sns.boxplot(datadf_clean, xdistrict, yunit_price_yuan_per_m2, orderdistrict_order, paletteSet2) plt.title(南京各行政区二手房单价分布2023年, fontsize14) plt.ylabel(单价元/㎡) plt.xlabel(行政区) # 在每个箱体上标注中位数 for i, district in enumerate(district_order): median_val df_clean[df_clean[district]district][unit_price_yuan_per_m2].median() ax.text(i, median_val 500, f{median_val:.0f}, hacenter, vabottom, fontweightbold) plt.tight_layout() plt.savefig(output/district_boxplot.png, dpi300)这段代码里orderdistrict_order强制按南京实际发展水平排序玄武教育强、建邺商业强而非sorted()字母序让图表符合本地认知。4.2 地理热力图用folium绘制南京房价热度不是“好看就行”ershoufang-clean-utf8-v1.1.csv含经纬度后热力图不再是装饰品。项目viz_heatmap.py用folium.plugins.HeatMap生成权重设为unit_price_yuan_per_m2单价越高热点越红而非简单计数半径radius15、模糊度blur20经实测调整太小看不出聚集太大糊成一片底图用tilesCartoDB positron浅色矢量底图避免百度/高德地图的商业水印。关键参数说明min_opacity0.3确保低密度区域如栖霞山周边仍有可见色块gradient{0.2: blue, 0.4: lime, 0.6: yellow, 0.8: orange, 1: red}5档渐变对应单价分位数答辩时可解释“红色区域单价超过全市80%分位”。4.3 学区房溢价分析用statsmodels做线性回归验证“名校效应”文档Analysis_Report.md中核心结论“南师附中本部划片小区均价比同地段非学区房高23.6%”靠回归模型支撑因变量unit_price_yuan_per_m2自变量is_school_zone0/1哑变量、area_m2面积、floor_level楼层、decoration装修等级控制变量district行政区固定效应、built_year房龄。# analysis_school_premium.py import statsmodels.api as sm X df[[is_school_zone, area_m2, floor_level, decoration]] X sm.add_constant(X) # 加截距项 y df[unit_price_yuan_per_m2] model sm.OLS(y, X).fit() print(model.summary()) # 输出中重点关注 is_school_zone 的 coef23600, P|t|0.001结果解读coef23600即学区房溢价2.36万元/㎡P|t|0.001说明统计显著。这比单纯说“贵很多”有力得多——导师最爱问“贵多少有没有统计依据”。5. 避坑指南我在调试时踩过的5个真实坑现在帮你绕开5.1 现象ershoufang-origin-ansi.csv用Excel打开全是乱码但用Notepad看是正常的原因Windows记事本默认用ANSIGBK编码打开文件而Excel 2016默认用UTF-8导致中文显示为“涓浗鍖椾含澶у”解决Excel中【数据】→【从文本/CSV】→选择文件→编码选“UTF-8”或“GB2312”或用pandas.read_csv(ershoufang-origin-ansi.csv, encodinggbk)读取。5.2 现象spider_lianjia_nj.py运行到第37页突然报ConnectionResetError原因链家服务器主动断开连接非代码错误。实测连续请求超过200次后触发IP临时限制非封禁10分钟后恢复解决在crawl_all_pages()循环中加入if page % 50 0: time.sleep(60)每爬50页休眠1分钟或改用代理池项目未内置但config/proxy_config.py留了接口。5.3 现象clean_data.py执行后unit_price_yuan_per_m2列出现大量inf值原因面积字段area_m2为0或空值导致单价计算total_price_wan * 10000 / area_m2产生无穷大解决清洗前加校验df df[df[area_m2] 10]南京最小住宅面积通常10㎡并在日志中记录被过滤的行数。5.4 现象viz_heatmap.py生成的HTML地图打开后空白控制台报Uncaught ReferenceError: L is not defined原因folium生成的地图依赖Leaflet.js若网络无法加载CDN资源如公司内网屏蔽外网会失效解决下载leaflet.js和leaflet.css到本地static/目录修改folium.Map()的tiles参数为本地路径或使用m.save(map.html, include_default_jsFalse)后手动引入本地JS。5.5 现象analysis_school_premium.py回归结果中is_school_zone系数为负原因is_school_zone字段未正确赋值——项目中该字段靠匹配小区名关键词如“树人”“南师附中”生成但“金陵中学”被漏匹配解决检查config/school_keywords.txt补充“金陵中学”“中华中学”等南京重点校名用df[df[is_school_zone]1][community_name].unique()人工核对匹配结果。6. 进阶技巧如何用这份代码快速适配其他城市三个可替换模块详解6.1 URL模板与区域配置5分钟切换到杭州或苏州所有城市适配只需改config/city_config.pyBASE_URLhttps://hz.lianjia.com/ershoufang/pg{page}/杭州DISTRICT_LIST[上城, 拱墅, 西湖, 滨江, 萧山]杭州行政区SCHOOL_KEYWORDS追加“杭二中”“学军中学”等本地名校。注意DISTRICT_LIST必须与链家URL中的区域拼音一致如杭州“西湖区”对应URL参数/ershoufang/xihuzhongxin/项目utils/url_generator.py会自动拼接不用手写。6.2 反爬策略升级当目标城市启用JS渲染时的降级方案若苏州链家su.lianjia.com某天启用了React动态渲染静态HTML无数据项目预留了Selenium备选路径spider_selenium.py已存在但默认不启用只需在spider_lianjia_nj.py顶部USE_SELENIUM False改为Truerequirements.txt已包含selenium和chromedriver-autoinstaller运行时自动下载匹配Chrome版本的驱动。# spider_selenium.py 片段降级时的最小化改动 from selenium import webdriver from selenium.webdriver.chrome.options import Options def init_driver(): options Options() options.add_argument(--headless) # 后台运行 options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) driver webdriver.Chrome(optionsoptions) return driver def get_page_html_selenium(url): driver init_driver() driver.get(url) time.sleep(2) # 等待JS渲染 html driver.page_source driver.quit() return html这里time.sleep(2)是经验值比WebDriverWait更简单——课程设计不追求极致鲁棒求稳优先。6.3 可视化报告自动化一键生成PDF答辩稿项目report_generator.py用weasyprint将HTML分析报告转PDF输入templates/report_template.html含Jinja2变量渲染df.describe()统计、箱线图、热力图嵌入输出output/Nanjing_House_Report_2023.pdf含页眉“南京二手房分析·课程设计”、页脚页码。# report_generator.py from weasyprint import HTML import jinja2 template_loader jinja2.FileSystemLoader(searchpath./templates/) template_env jinja2.Environment(loadertemplate_loader) template template_env.get_template(report_template.html) html_content template.render( district_statsdf.groupby(district)[unit_price_yuan_per_m2].agg([mean, std]).round(2), school_premium23600, # 从回归结果传入 heatmap_pathoutput/heatmap.html ) HTML(stringhtml_content).write_pdf(output/Nanjing_House_Report_2023.pdf)提示weasyprint依赖cairo和pango库Linux需sudo apt-get install libpango-1.0-0 libcairo2Windows用户直接pip install weasyprint即可它自带二进制依赖。从那以后我每次接手新城市数据项目都先跑一遍city_config.py的校验函数——检查URL能否访问、DISTRICT_LIST是否在页面源码中真实存在、SCHOOL_KEYWORDS是否覆盖当地TOP5中学。这一步花10分钟能避免后面3天调试。希望帮到你。本文还有配套的精品资源点击获取
返回列表