资讯详情

资讯详情

短租房源信息结构化:字段拆分、数据建模与Python解析实践

一条短租房源信息比如“碧桂园山湖城观澜1街1座29033室1厅1厨1卫2阳台3床1.8m1.5m1.2m可日租/周租/月租欢迎各位邻居咨询”看起来只是几行文字但在房东、平台和租客之间流动时它要承担很多角色房源唯一标识、展示信息、床型容量、租期模式、联系方式。非结构化文本一旦进入数据库或接口就会立刻暴露出问题很难查询、统计和自动计算。如果你正在做民宿短租系统、房源信息聚合平台或本地生活类应用迟早要处理这类文本到结构化数据的转换。这篇文章以这条真实文本为样例拆解如何把一条短租房源广告转成字段模型、数据库表、JSON 校验规则和可运行的解析脚本并讨论日租、周租、月租场景下最容易被忽略的工程细节。1. 原始房源文本里有价值但要先拆成可管理字段1.1 为什么必须做字段结构化先理解一个事实人是靠语义阅读文本的程序只能靠字段、索引和逻辑来理解数据。上面这条房源文本人一眼能看出是 3 室 1 厅 1 厨 1 卫 2 阳台有三张床分别接近 1.8 米、1.5 米和 1.2 米支持日租、周租、月租。但程序拿到这段字符串时如果只把它当作标题字段存进数据库后续所有业务都很难做。举个例子。租客想筛选“两居室以上”“有 1.8 米大床”“可以月租”的房源。如果原始文本只是被放进一个长文本字段最常见的做法是使用LIKE %月租%模糊查询结果可能包含“月租金”“月租价”等干扰项。更麻烦的是日租价格、周租价格、月租价格如果都写在同一段话里程序无法自动判断哪个价格对应哪个租期。结构化之后每个字段都变成独立可校验、可索引、可参与计算的数据。比如卧室数量是bedroom_count 3床型列表是[1.8m, 1.5m, 1.2m]租期模式是[day, week, month]。这样筛选条件可以写成 SQL价格可以通过租金策略表计算房态可以通过日历表排期。没有结构化的房源文本短期看只是维护成本高长期看会出现同一房源在不同平台展示不一致、价格计算错误、订单冲突等连锁问题。所以第一步不是急着写代码而是把一条文本里的信息拆成完整字段清单。1.2 字段拆分从文本提取的信息清单把“碧桂园山湖城观澜1街1座29033室1厅1厨1卫2阳台3床1.8m1.5m1.2m可日租/周租/月租欢迎各位邻居咨询。”按信息类别拆分可以得到下面这些字段。原始片段字段名字段类型示例值碧桂园山湖城project_namestring碧桂园山湖城观澜1街block_namestring观澜1街1座building_nostring1座2903room_nostring29033室1厅1厨1卫2阳台bedroom_count / living_room_count / kitchen_count / bathroom_count / balcony_countint3 / 1 / 1 / 1 / 21.8m1.5m1.2mbedsarray[{size_m:1.8, count:1}, ...]日租/周租/月租rental_modesarray[day, week, month]欢迎各位邻居咨询contact_notestring欢迎各位邻居咨询这里要区分“地址字段”和“房间字段”。project_name、block_name、building_no、room_no描述的是房源位置bedroom_count、living_room_count等描述的是户型。真实系统中地址部分通常要接入地址库做标准化不能只靠正则把“观澜1街”抽出来就算完成。room_no也要注意歧义。样例中的“2903”看起来是 29 层 03 号但在不同楼盘里它可能表示 2 栋 903 室也可能是 29 层 03 号。具体含义需要结合项目楼栋规则确认不要想当然把2903拆成29和03两个字段。1.3 文本里缺失的字段要补什么这条原始文本并没有包含短租系统需要的全部信息。拆字段时除了从文本中提取还要主动补上缺失项。至少缺少这些字段房源唯一 IDhouse_code用于关联订单、价格、日历和平台同步。价格日租价、周租价、月租价分别多少钱包含哪些费用。可住人数每张床对应可住几人客厅是否有沙发床。起租天数月租是否要求至少 30 天日租是否至少 1 天。押金和清洁费是否需要押金退房清洁费怎么算。联系人和联系方式由谁发布联系方式是什么。房源状态上架、下架、停用、审核中。创建时间和更新时间。这些字段在设计数据表时都要预留。价格、押金等内容缺失时可以先允许为空但不能因为文本没写就不设计对应表结构。否则后续接支付和订单时要再改表结构代价会更大。2. 数据模型要能表达“3室1厅1厨1卫2阳台”和“1.8m1.5m1.2m”2.1 设计房源基础表先把最核心的房源表设计出来。下面是一份 MySQL 风格的表结构用来承载样例文本中已经提取到的基础信息。CREATE TABLE house ( id BIGINT PRIMARY KEY AUTO_INCREMENT, house_code VARCHAR(64) NOT NULL UNIQUE, project_name VARCHAR(128), block_name VARCHAR(128), building_no VARCHAR(32), room_no VARCHAR(32), bedroom_count INT DEFAULT 0, living_room_count INT DEFAULT 0, kitchen_count INT DEFAULT 0, bathroom_count INT DEFAULT 0, balcony_count INT DEFAULT 0, max_guests INT, status VARCHAR(32) DEFAULT OFFLINE, source_text TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );这里有几个关键点。house_code是业务唯一键不应该让用户直接输入“碧桂园山湖城观澜1街1座2903”作为唯一键因为用户可能多写一个空格、少写一个“街”又或者换一种写法。建议由系统按规则生成比如BGYYHC-GL-01-2903并在录入时用地址库或人工确认。source_text字段很有必要。它保存原始文本方便以后排查解析问题。解析器改规则后可以回放历史文本对比新旧解析结果减少回归风险。max_guests没有在原始文本中出现但它是短租系统必须有的字段。它可以根据床型自动计算也可以由运营人员手动校正。注意床型可以决定理论最大人数但实际能住多少人还会受到沙发、儿童床等因素影响所以应该单独存储。2.2 床型和租期模式不能塞进一个字段床型不能放到house表的一列里。虽然可以存成1.8m1.5m1.2m但这样的字段无法参与统计。如果运营想统计全平台有大床房的房源数量或者计算平均床尺寸字符串字段处理起来非常痛苦。更合适的做法是建立子表或者使用兼容 JSON 的数据库字段。如果使用的是 MySQL 5.7 以上版本也可以用 JSON 类型但从扩展性和查询灵活性看独立子表更稳。CREATE TABLE house_bed ( id BIGINT PRIMARY KEY AUTO_INCREMENT, house_id BIGINT NOT NULL, bed_order INT NOT NULL, size_m DECIMAL(4, 2) NOT NULL, bed_count INT DEFAULT 1, bed_type VARCHAR(32) DEFAULT DOUBLE ); CREATE TABLE house_rental_mode ( house_id BIGINT NOT NULL, mode VARCHAR(16) NOT NULL, price DECIMAL(10, 2), currency VARCHAR(8) DEFAULT CNY, min_days INT, max_days INT, is_active BOOLEAN DEFAULT TRUE, effective_date DATE, expired_date DATE, PRIMARY KEY (house_id, mode) );house_bed表里bed_order用来表示床的顺序因为展示时通常要保持原始顺序1.8m、1.5m、1.2m。size_m用DECIMAL(4,2)不要用浮点类型存床尺寸和价格否则计算时容易出现精度问题。house_rental_mode表把日租、周租、月租拆成独立记录。这样做的原因是日租、周租、月租往往有不同的价格、折扣和起租天数。如果只在house表里放一个is_day_rental、is_week_rental、is_month_rental布尔字段后续就没办法表达“日租 300 元/晚月租 6000 元/月至少租 30 天”这类完整信息。2.3 用 JSON Schema 做数据校验在服务端接收前端提交的房源信息时可以使用 JSON Schema 做第一道校验。下面是一个针对本例的 JSON 表示。{ house: { house_code: BGYYHC-GL-01-2903, project_name: 碧桂园山湖城, block_name: 观澜1街, building_no: 1座, room_no: 2903, rooms: { bedroom: 3, living_room: 1, kitchen: 1, bathroom: 1, balcony: 2 }, beds: [ { size_m: 1.8, count: 1 }, { size_m: 1.5, count: 1 }, { size_m: 1.2, count: 1 } ], rental_modes: [day, week, month] } }JSON Schema 示例片段{ $schema: https://json-schema.org/draft/2020-12/schema, type: object, required: [house], properties: { house: { type: object, required: [house_code, rooms, beds, rental_modes], properties: { house_code: { type: string, pattern: ^[A-Z0-9\\-]$ }, rooms: { type: object, required: [bedroom, living_room, kitchen, bathroom, balcony], properties: { bedroom: {type: integer, minimum: 0}, living_room: {type: integer, minimum: 0}, kitchen: {type: integer, minimum: 0}, bathroom: {type: integer, minimum: 0}, balcony: {type: integer, minimum: 0} } }, beds: { type: array, minItems: 1, items: { type: object, required: [size_m, count], properties: { size_m: {type: number, minimum: 0.5, maximum: 3.0}, count: {type: integer, minimum: 1} } } }, rental_modes: { type: array, items: { type: string, enum: [day, week, month] } } } } } }bedroom这类数量字段设置minimum: 0比较合适因为有些单间公寓可能没有独立客厅和厨房。size_m的范围可以根据常见床型定义一般单人床 0.9 米、1.2 米双人床 1.5 米、1.8 米最大不会超过 2.2 米。如果出现超过 3 米的尺寸大概率是数据有问题。注意JSON Schema 只能做格式和范围校验不能保证业务正确。比如某条数据同时标记“可月租”但没有填写月租价格这类业务一致性需要由服务端代码继续检查。3. 用 Python 解析器把广告文本自动变成 JSON3.1 解析前的输入约定自动解析文本前先约定输入格式否则规则会越写越复杂。样例文本算是比较规整的因为房间和床位信息用数字、量词和分隔符表达。真实业务中还会出现“三室一厅”“1.8米大床1.5米床”这样的写法。一个可行的策略是发布入口尽量使用结构化表单只把“文本解析”作为辅助录入手段。辅助解析的价值在于减少录入工作量但结果要落到同一个校验逻辑里。下面先以这条样例文本作为输入raw_text 碧桂园山湖城观澜1街1座29033室1厅1厨1卫2阳台3床1.8m1.5m1.2m可日租/周租/月租欢迎各位邻居咨询。3.2 正则提取房间、床位、租期等关键信息这段 Python 代码演示如何从原始文本提取结构并不代表生产级方案但足够说明思路。import re import json def parse_house_text(text: str) - dict: result {} room_pattern re.compile( r(?Pbedroom\d)室 r(?Pliving_room\d)厅 r(?Pkitchen\d)厨 r(?Pbathroom\d)卫 r(?Pbalcony\d)阳台 ) m room_pattern.search(text) if m: result[rooms] {key: int(value) for key, value in m.groupdict().items()} else: result[rooms] None bed_sizes re.findall(r(\d(?:\.\d)?)m, text) result[beds] [{size_m: float(size), count: 1} for size in bed_sizes] rental_modes [] if 日租 in text: rental_modes.append(day) if 周租 in text: rental_modes.append(week) if 月租 in text: rental_modes.append(month) result[rental_modes] rental_modes project_match re.search(r([\u4e00-\u9fa5A-Za-z0-9]?)观澜, text) if project_match: result[project_name] project_match.group(1) room_match re.search(r(\d{3,4}), text) if room_match: result[room_no] room_match.group(1) return result if __name__ __main__: parsed parse_house_text(raw_text) print(json.dumps(parsed, ensure_asciiFalse, indent2))输出结果{ rooms: { bedroom: 3, living_room: 1, kitchen: 1, bathroom: 1, balcony: 2 }, beds: [ { size_m: 1.8, count: 1 }, { size_m: 1.5, count: 1 }, { size_m: 1.2, count: 1 } ], rental_modes: [ day, week, month ], project_name: 碧桂园山湖城, room_no: 2903 }正则re.findall(r(\d(?:\.\d)?)m, text)会把文本里所有带m的数字都找出来。这个简单写法在样例里能正确提取 1.8、1.5、1.2但如果出现“60m² 面积”这类信息就会误提取。生产环境的正则需要更严格的上下文判断比如只提取紧跟在“床”或“”附近的数值。这里把房间信息放在rooms对象里而不是拆成bedroom_count等顶层字段是为了 JSON 展示更清晰。落库时还是要把它们映射到house表的独立列。3.3 输出结果和边界情况处理解析器必须考虑边界情况。常见问题包括中文数字比如“三室一厅”当前正则匹配不到。分隔符不统一有人写1.8m1.5m1.2m有人写1.8m*2还有人写1.8米和1.5米。房号歧义2903可能是 29 层 03 号也可能是 2 栋 903 室。文本顺序变化有的文本把“可月租”放在开头有的放在中间正则只要定位关键词就不影响。处理办法不是把所有规则都写进一个巨大的正则而是先标准化输入再解析。比如在录入页面规定床尺寸之间用半角加号分隔房间数量用阿拉伯数字。对历史数据可以运行一个一次性清洗脚本把“三室”转成“3室”把“1.8米”转成“1.8m”。关键判断不要把解析器当成保证数据质量的唯一手段。解析器负责降低录入成本最终数据质量要靠字段校验、人工确认和定期抽样检查来兜底。4. 结构化之后才能做发布、计费和房态管理4.1 生成标准发布文案模板一旦数据变成结构化各个发布渠道的展示文案就可以统一生成不需要各平台分别手写。下面用 Python 生成一套比较标准的发布文案def build_publish_text(house: dict) - str: rooms house[rooms] beds .join( f{bed[size_m]}m for bed in house[beds] ) modes /.join(house[rental_modes]) text ( f{house[project_name]} {house.get(block_name, )} f{house.get(building_no, )}{house.get(room_no, )} f{rooms[bedroom]}室{rooms[living_room]}厅 f{rooms[kitchen]}厨{rooms[bathroom]}卫{rooms[balcony]}阳台 f{len(house[beds])}床{beds} f可{/.join(modes)}租欢迎咨询。 ) return text这样生成的文本可以保持一致性。但要注意不同平台的标题长度限制和展示规则不一样。有的平台要求标题不超过 50 字有的平台允许更宽松。模板化只是基础还要针对每个平台定义字段拼接规则和长度校验。4.2 日租/周租/月租的计费逻辑短租系统的计费不是简单地把日租价乘以天数。日租、周租、月租通常有不同的价格体系。比如日租价是 300 元/晚周租可能是 1800 元/周月租可能是 6000 元/月。如果只存日租价再按“月租价 日租价 × 30 × 折扣”计算遇到淡旺季、含水电费、不含清洁费等情况时就会非常难维护。推荐做法是直接在每个house_rental_mode记录中保存对应价格和约束。下面是计算接口的伪代码def calc_rental_price(house_id: int, mode: str, days: int) - float: # 从 house_rental_mode 表查询当前模式记录 # 校验 days 是否满足 min_days 和 max_days # day: 日租价 * days # week: 周租价 * (days // 7) 日租价 * (days % 7) # month: 月租价 * (days // 30) 日租价 * (days % 30) pass示例只做思路说明。真实项目里还要处理月份不固定问题28 天、29 天、30 天、31 天都不同。如果要精确计算月租可以按自然月区间处理比如从 2025-04-10 到 2025-05-09 算一个月而不是简单除以 30。4.3 用日历管理房态和订单冲突日租、周租、月租混合模式下订单占用的日期粒度不同。日租订单占用一天周租订单占用连续 7 天月租订单可能占用 30 天。如果不把订单展开到日期级别很容易出现两个订单在时间上重叠但系统没有发现。一个简单的房态表设计如下CREATE TABLE house_calendar ( house_id BIGINT NOT NULL, date DATE NOT NULL, status VARCHAR(16) NOT NULL, order_id BIGINT, PRIMARY KEY (house_id, date) );当新订单创建后相关日期写入house_calendar表状态为BOOKED。检查房源是否可订时只需要查询目标日期段内是否存在BOOKED记录。月租订单跨月时要把整个自然月对应的日期全部展开例如从 4 月 10 日到 5 月 9 日就写入 30 条日历记录。这个方案的优点是逻辑简单缺点是日租、周租、月租比例高的系统日历表数据量会增长。通常还需要配合索引和分区来优化查询。极端场景下可以用不展开的区间表加重叠判断但开发复杂度会更高。5. 短租系统最常见的三类问题要按链路排查5.1 床型信息解析错误现象录入“1.8m1.5m1.2m”系统只解析出[1.8, 5, 1.2]把1.5当中转文本吃掉了或者把1.8m1.5m理解成“1.8 米和 1.5 米”但最后的1.2m丢失。可能原因正则写得太宽松优先匹配到错误位置。输入分隔符不是统一的加号混用了全角加号、空格、/。床尺寸后面带有其他长度信息比如“1.8m×2m”被误拆。检查方式打印正则匹配到的中间结果。准备多组床型用例做单测1.8m1.5m1.2m、1.8米1.5米、1.8m*2。检查source_text是否保留了原始文本。处理建议统一录入规范床尺寸之间只用半角加号。解析后增加人工确认或在管理后台展示解析出的床型列表。对无法识别的文本不强行解析而是进入待人工处理队列。5.2 租期价格配置对不上现象租客选择“月租”系统按“日租价 × 30”计算或者“周租”跨月时按自然周分段计价结果和报价不一致。可能原因把租期模式当作布尔字段只存了“是否支持月租”没有存月租价格。计费函数里用固定 30 天代表一个月忽略了不同月份天数不同。价格配置已经有生效日期但查询时没有过滤effective_date和expired_date。检查方式查看house_rental_mode表中该房源的mode month记录。检查计费函数传入的起始日期和结束日期。打印订单快照看用户下单时看到的报价和最终订单金额是否一致。处理建议每个租期模式独立存价格。月租按自然月区间计算不按 30 天硬算。订单创建时保存价格快照避免后续改价影响历史订单。5.3 同一房源多个发布平台状态不一致现象运营在后台把房源设置为下架但外部 OTA 平台仍然展示可预订状态用户下单后才发现无法入住。可能原因平台只做了单向同步后台改状态后没有触发推送。同一个房源在外部平台有不同 ID没有建立映射关系。同步任务失败后没有重试和告警。检查方式查看house表的状态字段和外部平台的house_code。查看同步日志中的last_sync_time、sync_result、error_message。在外部平台搜索该房源确认实际状态。处理建议建立房源平台映射表保存platform_house_id。增加同步状态表记录每次同步任务的执行结果。对同步失败设置重试和告警避免静默失败。问题现象可能原因检查方式处理建议床型解析错误分隔符不统一、正则上下文不严打印正则中间结果增加单测统一输入规范解析后人工确认月租价格错误没有独立保存月租价格查看租金模式表价格按模式独立存储按自然月计算多渠道状态不一致同步失败且无重试机制查看同步日志和失败记录增加映射表、状态记录和失败告警6. 从学习环境到生产环境短租房源信息模型怎么落地6.1 学习环境如何快速跑通如果是学习或验证想法阶段不需要一上来就引入复杂框架。可以用 Python 加 SQLite 快速把流程跑通。python -m venv venv source venv/bin/activate pip install pyyaml然后创建schema.sql建立house、house_bed、house_rental_mode三张表再执行解析脚本把结构化 JSON 写入 SQLite。这样就能看到一条房源文本从字符串到表记录的全过程。学习环境的重点是验证解析规则和表结构是否合理不需要过度关注性能。但至少要做到多次运行解析脚本不会产生重复数据或者重复数据能被house_code唯一约束拦住。6.2 生产环境需要补的工程能力从学习环境到生产环境差距不只是换一个数据库。以下这些能力需要补齐地址库和地理编码把“碧桂园山湖城观澜1街1座2903”解析成标准地址和坐标或者至少与已有的楼盘字典对齐。房源唯一 ID 生成规则不要用用户输入文本做主键要用规则生成并加唯一约束。权限和审核房东、店长、客服、运营看到的操作按钮不同房源上架前需要审核。日志和监控记录解析失败率、同步失败率、订单冲突率出现异常时能及时告警。回滚和备份价格、房态、订单表需要保留变更历史方便追溯。缓存策略房源详情页不能每次都全表查需要缓存热点数据同时通过版本号或事件失效。异常重试外部平台同步、短信通知、支付回调都要有重试和幂等处理。6.3 从房源字段出发的可复用清单最后给一份发布前的可复用检查清单适合在短租或房源管理系统评审时直接使用。房源基本信息是否完整小区、楼栋、房号、户型、朝向、楼层。房间字段是否合法卧室、客厅、厨房、卫生间数量不能为负数不能超过常见上限。床型是否准确每张床的尺寸、数量、摆放位置是否清楚是否预留了沙发床。租期模式是否可计算日租、周租、月租是否都有独立价格、起租天数、生效时间。联系方式是否按平台规则处理是否隐藏手机号是否支持临时虚拟号。房源状态是否正确上架、下架、停用、审核中是否流转正常。多渠道同步状态是否正常每个平台的house_code映射是否存在最近一次同步是否成功。是否保留原始文本解析出问题时要能回溯到用户填写的原始内容。单条房源文本只是短租系统里的一个最小输入样例。真正的价值在于把非结构化文本转换成可计算、可追踪、可复用的数据模型让日期、床型、价格、房态这些关键信息不再依赖人工阅读。接下来可以继续扩展的方向包括自动识别中文数字、接入地址库、生成多平台适配文案、用日历服务处理订单冲突。如果从这条样例开始动手建议先用一个本地脚本把原始文本解析成 JSON再把 JSON 写入 SQLite最后再考虑完整后台。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →