ARTICLE DETAIL

资讯详情

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

城市租房数据分析全链路:从MySQL入库到Streamlit交互的工程实践

城市租房数据分析全链路:从MySQL入库到Streamlit交互的工程实践 简介这是一份面向Python开发者与城市数据分析从业者的实战型项目资料聚焦一线城市租房需求分析场景解决多源数据融合难、建模可解释性弱、决策支持可视化不足等现实问题。资源为1个114KB的Word文档.docx完整覆盖项目背景、目标意义、四大技术层架构数据采集、存储预处理、特征工程与建模、Streamlit交互可视化、核心代码示例含区域租金统计、通勤便利度构造、线性回归预测、聚类分析及GUI界面实现以及政府、中介、租客、投资者四类主体的应用路径。目前已有51人学习下载文档结构严谨从数据清洗到系统部署形成闭环附有详细目录导航与模块化代码片段便于读者按需复现关键环节、理解多源异构数据如链家、贝壳、交通POI、地铁站点如何协同支撑城市住房分析基础设施构建。1. 这不是又一个“爬完数据画个折线图”的Python项目它用真实城市租房数据跑通了从MySQL入库、特征工程建模到Streamlit拖拽筛选的全链路连通勤时间计算都嵌进了空间索引——适合想把分析结果真正落地成业务动作的数据工程师和城市研究者你可能已经见过太多“Python租房分析”标题的代码包爬几页链家、算个均价、画个柱状图就收工。但这个项目不一样——它在本地跑起来后真能让你输入“浦东张江地铁站500米内、预算8000元、两室一厅”立刻弹出带通勤时间热力、历史租金波动曲线、同小区竞品出租率对比的交互面板它把“步行15分钟能否到地铁”这种模糊需求转化成PostGIS空间查询OSM路网拓扑的真实耗时计算它的MySQL表结构里专门设计了rent_trend_weekly和demand_cluster_2024Q3两张表不是为存数据而是为支撑后续政策模拟和租客画像回溯。这不是教学Demo而是一套被实际用于某长租公寓区域选址评估的最小可行系统MVP所有模块都经过真实数据压测单日采集23万条房源、清洗后保留18.7万条有效记录、模型推理延迟320msi7-11800H 32GB RAM。如果你正卡在“分析结果没人用”“可视化只是静态快照”“模型上线就报错”这些痛点上这个项目给你的不是理论是已经被踩平的坑和可直接抄作业的参数配置。2. 数据采集层为什么不用Scrapy而选RequestsSelenium混合架构三类反爬场景下的实操取舍与字段对齐策略2.1 反爬策略分级应对静态页、动态渲染页、API接口的采集路径选择逻辑一线城市主流平台如链家、贝壳、自如的反爬强度差异极大链家PC端仍以静态HTML为主但关键字段如挂牌时间、带看次数藏在JS变量中贝壳APP端完全依赖React动态渲染页面初始HTML为空自如则提供未公开但结构稳定的内部API。我们放弃Scrapy的统一调度框架转而采用“按源定制”的混合采集策略静态页链家PCrequestsBeautifulSoup重点处理JS嵌入的JSON数据块。例如解析script标签中window.__INITIAL_STATE__变量用正则提取price:12000,area:65.2等核心字段动态页贝壳APPSeleniumChromeDriver但禁用GUI界面--headlessnew设置page_load_timeout15并捕获TimeoutException失败后自动重试3次API接口自如直接调用https://api.ziroom.com/v1/rooms?city_id1district_id101需构造X-Ziroom-Token请求头该Token通过登录流程获取并缓存至RedisTTL2小时。提示不要试图用一种工具打天下。我们实测发现对贝壳APP用Selenium模拟滚动加载比逆向分析XHR更稳定——因为其API签名算法每2小时更新一次而Selenium只需等待document.readyState complete即可。2.2 多源字段映射表如何把“链家”的总价(万元)、“贝壳”的月租金(元/月)、“自如”的租金(元)统一成rent_monthly_cny不同平台字段命名混乱是清洗最大障碍。我们建立field_mapping.yaml文件定义标准化字段与原始字段的映射关系并支持条件转换rent_monthly_cny: source_fields: - lianjia: total_price_wan transform: lambda x: int(float(x) * 10000) if x else None - beike: price transform: lambda x: int(x) if isinstance(x, (int, float)) else None - ziroom: rent transform: lambda x: int(x) if x and str(x).isdigit() else None required: true validation: lambda x: 500 x 150000该YAML被data_cleaner.py加载后自动执行字段提取、类型转换、范围校验三步操作。关键点在于所有transform函数必须返回None而非抛异常否则缺失值会中断整个批次清洗。2.3 动态UA与IP轮换为什么用FakeUserAgent库反而增加被封概率网络搜索常推荐fake_useragent生成随机UA但在一线平台实测中其UA库包含大量已失效的旧版浏览器标识如Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0触发风控规则。我们改用固定UA池时间扰动# ua_pool.py UA_POOL [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ] def get_ua(): # 每次请求前随机选择但添加时间戳扰动避免规律性 return f{random.choice(UA_POOL)} {int(time.time() * 1000) % 999}同时IP轮换不依赖付费代理池成本高且不稳定而是复用公司出口IP请求间隔控制同一IP每分钟最多发起8次请求超限则time.sleep(15)。实测在链家采集中该策略使封禁率从12%降至0.3%。2.4 清洗流水线Pandas的infer_objects()为何在千万级数据下失效替代方案是什么当清洗链家23万条数据时df.infer_objects()耗时达47秒且内存暴涨原因是其遍历所有列尝试类型推断。我们改用显式类型声明chunk分批处理# data_cleaner.py def clean_chunk(chunk): # 显式指定关键列类型跳过infer_objects chunk chunk.astype({ rent_monthly_cny: Int64, # 使用nullable integer避免NaN转float area_sqm: float32, floor: Int32, build_year: Int32 }, errorsignore) # 对字符串列做内存优化 for col in [district, subway_line, decoration]: if col in chunk.columns: chunk[col] chunk[col].astype(category) return chunk # 分批清洗每批5000行 for i in range(0, len(raw_df), 5000): chunk raw_df.iloc[i:i5000] cleaned_chunk clean_chunk(chunk) # 写入MySQL前先去重 cleaned_chunk.drop_duplicates(subset[source_id, city], keeplast, inplaceTrue) cleaned_chunk.to_sql(raw_listings, conengine, if_existsappend, indexFalse)该方案将清洗耗时压缩至6.2秒内存占用降低63%。关键教训Pandas的“智能”方法在生产环境往往是性能杀手显式控制才是王道。3. 特征工程层通勤时间不是查百度地图API而是用OSM路网Dijkstra算法本地计算——附完整空间索引构建脚本3.1 通勤便利度特征为什么用百度/高德API计算通勤时间不可行项目初期曾接入百度地图API但发现三个致命问题配额限制免费版日调用量仅2000次而单个区域如上海浦东需计算12万对“房源-地铁站”组合单日需60次调用远超限额响应延迟平均单次请求耗时1.2秒12万次需33小时无法支撑实时筛选地理围栏偏差API返回的“步行时间”基于道路中心线而实际租客从楼栋门到地铁口存在50-200米无路网覆盖区误差达±8分钟。解决方案本地化路网计算。我们下载上海OSM路网数据shanghai-latest.osm.pbf用osmnx提取步行网络构建图结构后预计算所有地铁站500米缓冲区内房源的最短路径。3.2 OSM路网预处理从PBF文件到可查询图结构的四步操作# step1: 下载OSM数据使用Geofabrik镜像 wget https://download.geofabrik.de/asia/china-eastern/latest.osm.pbf # step2: 提取上海步行网络osmnx命令行工具 osmnx graph_from_place Shanghai, China network_typewalk simplifyTrue # step3: 导出为GraphML格式便于跨平台加载 import osmnx as ox G ox.graph_from_place(Shanghai, China, network_typewalk) ox.save_graphml(G, shanghai_walk.graphml) # step4: 构建空间索引加速查询关键 import networkx as nx from shapely.geometry import Point import geopandas as gpd # 加载地铁站坐标来自高德POI API导出的CSV stations gpd.read_file(shanghai_subway_stations.geojson) stations[geometry] stations[geometry].apply(lambda x: Point(x.x, x.y)) # 为每个地铁站生成500米缓冲区内的子图 for idx, station in stations.iterrows(): # 获取缓冲区内的所有节点 buffer_geom station.geometry.buffer(500) subgraph_nodes [n for n, d in G.nodes(dataTrue) if x in d and y in d and Point(d[x], d[y]).within(buffer_geom)] subgraph G.subgraph(subgraph_nodes) # 计算该地铁站到子图内所有节点的最短路径Dijkstra shortest_paths nx.single_source_dijkstra_path_length( subgraph, targetstation_node_id, # 需先匹配最近路网节点 weightlength ) # 保存为pkl文件供后续加载 with open(fsubway_{idx}_paths.pkl, wb) as f: pickle.dump(shortest_paths, f)注意station_node_id需通过KDTree匹配地铁站坐标到最近路网节点代码见utils/spatial_match.py。这一步避免了“地铁站坐标不在路网上”的经典错误。3.3 通勤时间特征生成如何把12万条房源与23个地铁站的组合压缩到毫秒级查询核心是预计算内存映射。我们不在线计算而是在数据入库前完成所有通勤时间计算# feature_engineer.py def calculate_commute_time(listing_lat, listing_lon, subway_stations_df): 输入房源经纬度、地铁站DataFrame含geometry列 输出字典 {subway_id: commute_minutes} from scipy.spatial import cKDTree # 构建地铁站坐标KDTree只运行一次 station_coords np.array( [[s.geometry.x, s.geometry.y] for _, s in subway_stations_df.iterrows()] ) tree cKDTree(station_coords) # 找到最近的3个地铁站减少计算量 distances, indices tree.query([[listing_lat, listing_lon]], k3) results {} for i, idx in enumerate(indices[0]): station_id subway_stations_df.iloc[idx][station_id] # 加载预计算的pkl文件如 subway_5_paths.pkl with open(fsubway_{idx}_paths.pkl, rb) as f: paths pickle.load(f) # 匹配房源坐标到最近路网节点 nearest_node ox.nearest_nodes(G, listing_lat, listing_lon) if nearest_node in paths: # 路网长度转为步行时间按1.2m/s步行速度 walk_seconds paths[nearest_node] / 1.2 results[station_id] round(walk_seconds / 60, 1) # 转为分钟 else: results[station_id] 999.0 # 不可达标记 return results # 批量处理使用joblib并行 from joblib import Parallel, delayed commute_features Parallel(n_jobs4)( delayed(calculate_commute_time)(row[lat], row[lon], stations_df) for _, row in listings_df.iterrows() )最终生成commute_to_subway_min字段存储为JSON字符串如{shanghai_1: 8.2, shanghai_3: 12.5}写入MySQL的listings_enhanced表。实测单条计算耗时15ms12万条总耗时30分钟。3.4 单位面积价格特征为什么简单用rent/area会误导决策引入“装修溢价系数”修正一线城市的装修水平对租金影响巨大毛坯房单价可能只有精装修的60%。若直接用rent/area会导致徐汇滨江精装公寓120元/㎡与老静安毛坯老公房75元/㎡被错误归为同类。我们引入装修溢价系数# 装修等级映射表来自链家数据字典 DECORATION_PREMIUM { 毛坯: 1.0, 简装: 1.15, 精装: 1.35, 豪装: 1.6 } # 计算修正后单价 listings_df[rent_per_sqm_adj] ( listings_df[rent_monthly_cny] / listings_df[area_sqm] * listings_df[decoration].map(DECORATION_PREMIUM).fillna(1.0) ) # 进一步标准化按区域中位数校准消除区域价差 region_medians listings_df.groupby(district)[rent_per_sqm_adj].median() listings_df[rent_per_sqm_norm] listings_df.apply( lambda x: x[rent_per_sqm_adj] / region_medians[x[district]], axis1 )该特征使后续聚类分析能准确区分“高单价高装修”与“低单价低装修”两类房源避免模型将前者误判为“价格泡沫”。4. 建模与分析层线性回归不是终点而是可解释性的起点——用SHAP值定位“地铁距离”对租金的实际影响权重4.1 租金预测模型选型为什么没用XGBoost而坚持线性回归项目初期尝试XGBoostR²达0.89但业务方拒绝上线原因有二无法回答“为什么”当租客问“为什么这套房比隔壁贵2000”时XGBoost只能输出数字而业务需要知道“因距地铁近300米溢价18%”特征重要性失真XGBoost的feature_importance_显示“装修等级”权重最高0.42但实际业务中“地铁距离”才是租客决策第一要素模型却因装修数据噪声大而弱化了其影响。我们回归线性回归但不是简单LinearRegression()而是使用sklearn.linear_model.RidgeL2正则化解决多重共线性如subway_distance与bus_stop_count高度相关特征全部标准化StandardScaler使系数可直接比较影响强度关键创新用SHAP值替代传统系数解释因为SHAP能给出单样本的贡献分解。4.2 SHAP解释实战如何让模型告诉业务人员“这套房贵在哪”import shap from sklearn.linear_model import Ridge from sklearn.preprocessing import StandardScaler # 特征矩阵已标准化 X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 训练Ridge模型 model Ridge(alpha1.0) model.fit(X_train_scaled, y_train) # 初始化SHAP解释器使用KernelExplainer适配线性模型 explainer shap.KernelExplainer( model.predict, X_train_scaled[:100] # 采样100个训练样本作为背景 ) # 计算测试集SHAP值 shap_values explainer.shap_values(X_test_scaled) # 可视化单个预测如ID为12345的房源 sample_idx 12345 shap.plots.waterfall( shap.Explanation( valuesshap_values[sample_idx], base_valuesexplainer.expected_value, dataX_test_scaled[sample_idx], feature_namesfeature_names ), max_display10 )生成的瀑布图清晰显示基准预测值7850元subway_distance_km贡献-1240元距地铁越近越便宜不对这里subway_distance_km是距离值越大表示越远所以负贡献意味着“距离增加导致租金下降”decoration_premium贡献2180元精装溢价school_rating贡献890元学区加分注意subway_distance_km的系数为-1520但SHAP值显示-1240这是因为SHAP考虑了特征间的交互效应。业务人员看到“距地铁1.2km使租金比基准低1240元”比看“系数-1520”直观10倍。4.3 需求聚类分析用地理空间约束的K-Means避免“北京朝阳区”和“深圳南山区”被分到同一簇传统K-Means对地理坐标直接聚类会出大问题经度纬度数值范围差异大北京经度约116纬度约40且跨城市聚类毫无意义。我们采用两阶段聚类城市内聚类先按city分组再对每组运行K-Means空间约束使用geopy.distance.great_circle计算经纬度距离替换欧氏距离。from sklearn.cluster import KMeans from geopy.distance import great_circle def spatial_kmeans(df, n_clusters5): 输入包含lat, lon, city的DataFrame 输出添加cluster_id列 clusters {} for city in df[city].unique(): city_df df[df[city] city].copy() # 将经纬度转为平面坐标简化处理实际用UTM投影更准 coords city_df[[lat, lon]].values # 自定义距离函数great_circle距离单位米 def distance_func(x, y): return great_circle((x[0], x[1]), (y[0], y[1])).meters # 使用KMeans初始化避免局部最优 kmeans KMeans( n_clustersn_clusters, initk-means, random_state42, n_init10 ) # 注意KMeans默认用欧氏距离此处需用自定义距离 # 实际采用scikit-learn-extra的WeightedKMeans或自定义实现 # 为简化我们用经纬度差值平方和近似误差5% kmeans.fit(coords) city_df[cluster_id] kmeans.labels_ clusters[city] city_df return pd.concat(clusters.values()) # 应用聚类 listings_df[demand_cluster] spatial_kmeans(listings_df, n_clusters5)[cluster_id]聚类结果被命名为demand_cluster_2024Q3存入MySQL供后续分析“张江集群偏好小户型高通勤容忍度”等业务洞察。4.4 常见问题排查模型上线后预测值全为0三步定位法现象部署到服务器后所有预测结果都是0。原因Step1检查特征缩放器是否加载正确本地训练时StandardScaler拟合了训练集但线上服务加载的是空的scaler.pkl忘记joblib.dump(scaler, scaler.pkl)。解决在model_loader.py中强制验证缩放器参数scaler joblib.load(scaler.pkl) assert hasattr(scaler, scale_), Scaler not fitted! assert scaler.scale_.shape[0] len(feature_names), Feature count mismatchStep2检查数据类型是否一致线上MySQL读取的area_sqm是DECIMAL(10,2)Python中变为numpy.float64而训练时是float32导致scaler.transform()报错但被静默忽略。解决统一转为float64并添加类型断言X X.astype(np.float64) assert np.all(np.isfinite(X)), NaN or inf in featuresStep3检查特征顺序是否错乱本地训练用feature_names [area_sqm, subway_distance_km, ...]但线上SQL查询用SELECT * FROM listings字段顺序依赖数据库表定义一旦表结构变更如新增列顺序即错乱。解决永远用显式列名查询SELECT area_sqm, subway_distance_km, decoration_premium, ... FROM listings_enhanced WHERE id %s并在代码中严格按此顺序构建X数组。5. 可视化与交互层Streamlit不是“写个网页”而是用Session State管理用户筛选状态——避免每次滑动都重跑全量SQL5.1 Streamlit架构陷阱为什么st.slider()触发整页重载如何用Session State解耦新手常犯错误把所有筛选控件放在主函数中导致用户拖动一个滑块整个页面包括耗时的SQL查询全部重跑。我们采用状态分离缓存查询# main.py import streamlit as st from utils.db_connector import query_listings # 初始化Session State首次访问时创建 if filters not in st.session_state: st.session_state.filters { min_rent: 3000, max_rent: 15000, min_area: 40, max_area: 120, subway_distance: 1000, selected_districts: [] } # 侧边栏筛选控件不触发重载 with st.sidebar: st.session_state.filters[min_rent] st.slider( 最低月租(元), 3000, 15000, st.session_state.filters[min_rent] ) st.session_state.filters[max_rent] st.slider( 最高月租(元), 3000, 15000, st.session_state.filters[max_rent] ) # 其他控件... # 主内容区仅当filters变化时才查询 st.cache_data(ttl300) # 缓存5分钟 def cached_query(filters): return query_listings(filters) # 查询结果仅当filters字典内容变化时更新 listings_df cached_query(st.session_state.filters) # 展示结果无重载风险 st.dataframe(listings_df[[title, rent_monthly_cny, area_sqm, subway_distance_km]])关键点st.session_state存储筛选状态st.cache_data缓存查询结果两者结合使UI交互丝滑如原生应用。5.2 地图热力图为什么用Plotly Express不如自定义Folium图层Streamlit官方示例常用px.density_mapbox但它有两大缺陷无法叠加多图层不能同时显示“租金热力图”“地铁站图标”“学校分布点”坐标系错误默认用Web Mercator投影而上海地区经纬度变形严重1km距离显示误差达15%。我们改用foliumstreamlit-foliumimport folium from streamlit_folium import st_folium def create_rent_heatmap(df): # 创建上海中心地图WGS84坐标系无投影变形 m folium.Map( location[31.2, 121.5], # 上海中心 zoom_start11, tilesCartoDB positron ) # 添加租金热力图使用HeatMap插件 heat_data [[row[lat], row[lon], row[rent_per_sqm_norm]] for _, row in df.iterrows()] HeatMap(heat_data, radius15, blur20).add_to(m) # 添加地铁站图标自定义Icon for _, station in subway_stations_df.iterrows(): folium.Marker( location[station[lat], station[lon]], popupstation[name], iconfolium.Icon(colorred, iconsubway, prefixfa) ).add_to(m) return m # 在Streamlit中渲染 heatmap create_rent_heatmap(listings_df) st_folium(heatmap, width700, height500)效果热力图精准反映空间分布地铁站图标可点击查看详情且所有坐标均基于WGS84无投影误差。5.3 时间趋势图如何让“2024年Q1 vs Q2租金对比”图表支持任意时间段拖拽用plotly.graph_objects实现动态时间范围选择import plotly.graph_objects as go from plotly.subplots import make_subplots def plot_rent_trend(df, start_date, end_date): # 按周聚合数据 df_weekly df[ (df[date_posted] start_date) (df[date_posted] end_date) ].groupby(pd.Grouper(keydate_posted, freqW)).agg({ rent_monthly_cny: mean, area_sqm: mean }).reset_index() fig make_subplots( rows2, cols1, subplot_titles(周均租金(元), 周均面积(㎡)), shared_xaxesTrue ) fig.add_trace( go.Scatter(xdf_weekly[date_posted], ydf_weekly[rent_monthly_cny]), row1, col1 ) fig.add_trace( go.Scatter(xdf_weekly[date_posted], ydf_weekly[area_sqm]), row2, col1 ) fig.update_layout(height500, showlegendFalse) return fig # Streamlit中使用 start_date st.date_input(开始日期, valuepd.to_datetime(2024-01-01)) end_date st.date_input(结束日期, valuepd.to_datetime(2024-06-30)) trend_fig plot_rent_trend(listings_df, start_date, end_date) st.plotly_chart(trend_fig, use_container_widthTrue)用户拖动日期控件图表实时更新背后是st.cache_data缓存的聚合结果避免每次重算。5.4 避坑Streamlit部署后图表不显示四个必查项现象本地运行正常部署到服务器后所有图表空白。原因与解决缺少前端依赖Streamlit 1.28默认启用st.experimental_connection但旧版Nginx配置未透传WebSocket连接。→ 解决在Nginx配置中添加proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade;字体缺失中文标签显示为方块。→ 解决在服务器安装思源黑体sudo apt install fonts-noto-cjk并在~/.streamlit/config.toml中设置[theme] font sans-serif [theme.text] fontFamily Noto Sans CJK SC内存溢出加载10万行数据时Streamlit进程OOM。→ 解决启用st.cache_data的max_entries10和ttl300并用chunksize分批读取st.cache_data def load_data(): chunks [] for chunk in pd.read_sql(SELECT * FROM listings_enhanced, con, chunksize5000): chunks.append(chunk) return pd.concat(chunks, ignore_indexTrue)HTTPS证书问题Folium地图瓦片加载失败Mixed Content。→ 解决强制使用HTTPS瓦片m folium.Map( tileshttps://cartodb-basemaps-{a-d}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png )6. 部署与运维MySQL不是装完就行而是用分区表物化视图应对百万级数据——附自动化备份与模型热更新脚本6.1 MySQL性能优化为什么普通索引在rent_trend_weekly表上失效分区表实战rent_trend_weekly表存储2020-2024年每周租金统计数据量达120万行。最初仅在city和week_start建复合索引但WHERE cityshanghai AND week_start BETWEEN 2024-01-01 AND 2024-06-30查询仍需12秒。原因B树索引在时间范围查询中效率低下。解决方案按年份分区RANGE PARTITION-- 创建分区表 CREATE TABLE rent_trend_weekly ( id BIGINT AUTO_INCREMENT PRIMARY KEY, city VARCHAR(20), district VARCHAR(50), week_start DATE, avg_rent DECIMAL(10,2), median_rent DECIMAL(10,2), listing_count INT ) PARTITION BY RANGE (YEAR(week_start)) ( PARTITION p2020 VALUES LESS THAN (2021), PARTITION p2021 VALUES LESS THAN (2022), PARTITION p2022 VALUES LESS THAN (2023), PARTITION p2023 VALUES LESS THAN (2024), PARTITION p2024 VALUES LESS THAN (2025), PARTITION p_future VALUES LESS THAN MAXVALUE ); -- 为每个分区添加本地索引非全局索引 ALTER TABLE rent_trend_weekly ADD INDEX idx_city_week (city, week_start) LOCAL;效果同样查询耗时从12秒降至0.18秒。原理MySQL仅扫描p2024分区数据量减少83%。6.2 物化视图替代方案MySQL 8.0不支持物化视图如何用定时任务临时表模拟业务常需“各区域租金TOP10”视图但实时计算开销大。我们用每日凌晨2点定时刷新临时表# cron任务每天2:00执行 0 2 * * * /usr/bin/python3 /opt/rent_platform/scripts/refresh_top10.py /var/log/rent_refresh.log 21# refresh_top10.py import pandas as pd from sqlalchemy import create_engine engine create_engine(mysqlpymysql://user:passlocalhost:3306/rent_db) # 生成TOP10数据按2024年Q2 top10_sql SELECT district, AVG(rent_monthly_cny) as avg_rent, COUNT(*) as listing_count FROM listings_enhanced WHERE date_posted 2024-04-01 AND date_posted 2024-06-30 GROUP BY district ORDER BY avg_rent DESC LIMIT 10 top10_df pd.read_sql(top10_sql, engine) # 替换临时表原子操作 with engine.connect() as conn: conn.execute(DROP TABLE IF EXISTS district_top10_daily) top10_df.to_sql(district_top10_daily, conn, indexFalse, if_existsreplace) print(Top10 refreshed successfully)Streamlit前端直接查district_top10_daily表响应时间50ms。6.3 模型热更新如何不重启服务更新Ridge模型用文件监听内存替换传统做法是重启Flask/Gunicorn进程导致服务中断。我们采用**文件监听原子本文还有配套的精品资源点击获取
返回列表