ARTICLE DETAIL

资讯详情

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

用Flask和SQLite从零搭建房源信息展示与发布系统

用Flask和SQLite从零搭建房源信息展示与发布系统 同样一套房源信息扔到业主群、朋友圈是纯文字过几天就刷没了整理成一个网页或者小系统不仅自己好维护别人看着也清楚。今天就用 Flask 加 SQLite带大家从零搭一个房源信息展示与发布小系统把房型、床型、租期、联系方式这些信息做成可浏览、可维护的页面。这套方案不依赖复杂前端框架纯 Python 后端起一个本地服务后续发布到服务器也容易适合做房产信息管理、二房东管理台账、个人房源展示这类场景。文章会覆盖从环境准备、数据库设计、Flask 路由编写、页面模板渲染到常见报错排查和上线注意点。代码都能直接复制运行你只需要一个能跑 Python 的电脑。1. 背景从一条租房信息到一个小系统平时我们在群里见到最多的房源信息大概是这样的碧桂园山湖城观澜1街1座29033室1厅1厨1卫2阳台3床1.8m1.5m1.2m可日租/周租/月租欢迎各位邻居咨询。这种文本信息的问题在于信息结构不统一房型、床型、租期混在一起无法筛选租客想找“带阳台月租房”得一条条翻修改价格或者添加房间信息时只能复制原文再改容易错发出去以后很快被其他消息顶掉想回看历史记录很麻烦。如果把这套房源信息做成一个页面效果就完全不一样了。租客打开页面能看到结构化的房型信息、床型尺寸、租期选项、联系方式房东自己维护数据时只需要在后台输入表单就能新增、修改、下架房源。本文会实现一个最小可用的“房源信息展示系统”技术栈只有三个FlaskPython 轻量级 Web 框架用来处理请求和页面跳转SQLitePython 自带的关系型数据库不需要单独安装服务Jinja2 模板Flask 默认集成的模板引擎用来渲染 HTML 页面。整个项目可以跑在本地 Windows、macOS 或 Linux 上也可以部署到云服务器。2. 环境准备与项目初始化2.1 检查 Python 环境Flask 是基于 Python 的 Web 框架先确认机器上已经安装了 Python 3。这里用命令行检查python --version如果你使用的是 macOS 或 Linux可能需要用python3python3 --version只要看到类似Python 3.8.x、Python 3.10.x、Python 3.11.x这样的输出就说明环境没问题。本文示例以 Python 3 环境为例重点演示配置思路具体小版本不影响运行。2.2 创建项目目录建议单独建一个项目文件夹避免和系统环境混在一起。例如在桌面或工作目录下新建mkdir house_info_system cd house_info_system后续所有代码文件都放在这个目录下。2.3 创建虚拟环境虚拟环境可以让 Flask 及其依赖只作用于当前项目不会污染系统级 Python。这一步在生产环境、多人协作场景下尤其重要。Windows 下创建并激活虚拟环境python -m venv venv venv\Scripts\activatemacOS 或 Linux 下python3 -m venv venv source venv/bin/activate激活后命令行提示符前面会出现(venv)表示当前正在使用虚拟环境。2.4 安装 Flask在虚拟环境中执行pip install flask安装完成后可以通过下面命令确认版本pip show flask不同时期安装到的 Flask 版本会有差异但本文用到的功能在 Flask 2.x 和 3.x 中都保持一致。如果你安装的是较老版本建议升级到当前稳定版本再运行示例pip install --upgrade flask2.5 项目结构规划为了让代码清晰这里采用一个非常小的分层方式house_info_system/ ├── app.py ├── init_db.py ├── templates/ │ ├── index.html │ ├── detail.html │ └── add_house.html ├── static/ │ └── style.css └── instance/ └── house.db说明app.pyFlask 主程序包含路由和页面逻辑init_db.py初始化数据库脚本建表并写入示例数据templates/Jinja2 模板文件static/静态文件如 CSS 样式instance/SQLite 数据库文件所在目录Flask 默认会创建这个目录。3. 核心知识拆解在写完整代码之前先花五分钟理解三个知识点后面看代码会轻松很多。3.1 Flask 最小应用长什么样Flask 的核心概念是“路由”也就是把 URL 地址映射到对应的 Python 函数上。先看一个最简单的例子from flask import Flask app Flask(__name__) app.route(/) def index(): return Hello, House System! if __name__ __main__: app.run(debugTrue)这里有几个关键点Flask(__name__)创建应用实例__name__告诉 Flask 当前模块在哪从而定位模板和静态文件app.route(/)是路由装饰器表示访问根路径/时执行下面的index()函数app.run(debugTrue)启动开发服务器debugTrue表示修改代码后自动重启并显示详细错误页面。开发环境用debugTrue很方便但是生产环境必须关闭否则会暴露敏感错误信息。3.2 SQLite 数据库表结构设计房源信息需要字段来承载结构化的数据。参照前面那条示例房源可以设计如下表字段名类型说明idINTEGER PRIMARY KEY AUTOINCREMENT主键自动增长titleTEXT房源标题addressTEXT详细地址house_typeTEXT房型比如 3室1厅1厨1卫2阳台bed_infoTEXT床型信息比如 1.8m1.5m1.2mprice_dayREAL日租价格price_weekREAL周租价格price_monthREAL月租价格has_balconyINTEGER是否有阳台1 表示有0 表示没有remarkTEXT备注或其他说明statusINTEGER状态1 表示可租0 表示已下架create_timeTEXT创建时间关于价格字段这里用REAL类型存储浮点数适合租金这种带小数的金额。如果对金额精度要求很高也可以改成INTEGER存储“分”在展示时再换算成“元”。实际项目里建议用整数分存储避免浮点误差。3.3 Jinja2 模板渲染Flask 默认从templates/目录加载 Jinja2 模板。模板文件里可以写 HTML也可以通过{{ 变量 }}插入 Python 传过来的数据通过{% for item in list %}做循环。例如ul {% for house in houses %} li{{ house.title }} - {{ house.address }}/li {% endfor %} /ul这个能力让页面能动态展示数据库中的每一条房源而不是写死静态文本。4. 完整实战房源信息展示系统下面我们逐步实现整个项目。先从数据库开始再写 Flask 主程序最后写页面模板。4.1 初始化数据库脚本先创建init_db.py用来建表并插入一条示例数据。文件路径house_info_system/init_db.pyimport sqlite3 import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) DB_PATH os.path.join(BASE_DIR, instance, house.db) def init_db(): if not os.path.exists(os.path.join(BASE_DIR, instance)): os.makedirs(os.path.join(BASE_DIR, instance)) conn sqlite3.connect(DB_PATH) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS house ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, address TEXT NOT NULL, house_type TEXT NOT NULL, bed_info TEXT NOT NULL, price_day REAL, price_week REAL, price_month REAL, has_balcony INTEGER DEFAULT 0, remark TEXT, status INTEGER DEFAULT 1, create_time TEXT DEFAULT CURRENT_TIMESTAMP ) ) cursor.execute(SELECT COUNT(*) FROM house) count cursor.fetchone()[0] if count 0: cursor.execute( INSERT INTO house ( title, address, house_type, bed_info, price_day, price_week, price_month, has_balcony, remark, status ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) , ( 碧桂园山湖城观澜1街1座2903, 碧桂园山湖城观澜1街1座2903, 3室1厅1厨1卫2阳台, 1.8m 1.5m 1.2m, 268.0, 1580.0, 5280.0, 1, 可日租/周租/月租欢迎各位邻居咨询小区环境安静交通便利。, 1, )) conn.commit() conn.close() print(数据库初始化完成数据文件位于:, DB_PATH) if __name__ __main__: init_db()几点说明CREATE TABLE IF NOT EXISTS保证重复执行脚本不会重复建表报错SELECT COUNT(*) FROM house用来判断表里是否已有数据避免重复插入示例数据示例数据里地址信息直接复用了标题中的地址实际项目中也可以把title写成“山湖城三房两阳台整套出租”这种更具吸引力的文案价格字段这里只是示例具体定价请在真实业务中自行决定。执行初始化脚本python init_db.py运行成功后会在instance目录下生成house.db数据库文件。4.2 编写 Flask 主程序接下来创建app.py。这个文件负责创建 Flask 应用、连接数据库、提供页面路由。文件路径house_info_system/app.pyimport os import sqlite3 from datetime import datetime from flask import Flask, render_template, request, redirect, url_for, abort BASE_DIR os.path.dirname(os.path.abspath(__file__)) DB_PATH os.path.join(BASE_DIR, instance, house.db) app Flask(__name__) def get_db(): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row return conn app.route(/) def index(): keyword request.args.get(keyword, ) status request.args.get(status, 1) conn get_db() sql SELECT * FROM house WHERE 11 params [] if keyword: sql AND (title LIKE ? OR address LIKE ? OR remark LIKE ?) like_word f%{keyword}% params.extend([like_word, like_word, like_word]) if status in (0, 1): sql AND status ? params.append(status) sql ORDER BY id DESC houses conn.execute(sql, params).fetchall() conn.close() return render_template(index.html, houseshouses, keywordkeyword, statusstatus) app.route(/house/int:house_id) def detail(house_id): conn get_db() house conn.execute(SELECT * FROM house WHERE id ?, (house_id,)).fetchone() conn.close() if house is None: abort(404) return render_template(detail.html, househouse) app.route(/add, methods[GET, POST]) def add_house(): if request.method POST: title request.form.get(title, ).strip() address request.form.get(address, ).strip() house_type request.form.get(house_type, ).strip() bed_info request.form.get(bed_info, ).strip() price_day request.form.get(price_day, 0) price_week request.form.get(price_week, 0) price_month request.form.get(price_month, 0) has_balcony 1 if request.form.get(has_balcony) else 0 remark request.form.get(remark, ).strip() status 1 if request.form.get(status) else 1 if not title or not address: return 标题和地址不能为空, 400 conn get_db() conn.execute( INSERT INTO house ( title, address, house_type, bed_info, price_day, price_week, price_month, has_balcony, remark, status ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) , (title, address, house_type, bed_info, float(price_day or 0), float(price_week or 0), float(price_month or 0), has_balcony, remark, status)) conn.commit() conn.close() return redirect(url_for(index)) return render_template(add_house.html) app.errorhandler(404) def page_not_found(e): return 页面不存在或房源已下架, 404 if __name__ __main__: app.run(debugTrue)这段代码里最核心的是几个路由/房源列表页支持关键词搜索和状态筛选/house/int:house_id房源详情页/add新增房源表单页面GET 请求显示表单POST 请求保存数据。值得注意的地方sqlite3.Row可以让查询结果像字典一样通过字段名访问在模板里写house.title比house[0]可读性好很多SQL 查询使用?占位符传参不用字符串拼接能有效避免 SQL 注入风险新增房源时如果标题或地址为空直接返回 400 错误。这里只是示例真实项目中更推荐使用 Flask-WTF 表单校验组件把错误信息显示在页面上。4.3 编写列表页模板在templates目录下创建index.html。文件路径house_info_system/templates/index.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title房源信息展示系统/title link relstylesheet href{{ url_for(static, filenamestyle.css) }} /head body div classcontainer header classheader h1房源信息展示系统/h1 a classbtn btn-primary href{{ url_for(add_house) }}新增房源/a /header form classsearch-bar methodget action{{ url_for(index) }} input typetext namekeyword value{{ keyword }} placeholder输入小区名、地址或备注关键词 select namestatus option value1 {% if status 1 %}selected{% endif %}在租房源/option option value0 {% if status 0 %}selected{% endif %}已下架/option /select button typesubmit搜索/button /form div classhouse-list {% for house in houses %} div classhouse-card h2 a href{{ url_for(detail, house_idhouse.id) }}{{ house.title }}/a /h2 p classaddress地址{{ house.address }}/p p classhouse-type房型{{ house.house_type }}/p p classbed-info床型{{ house.bed_info }}/p p classprice span日租{{ house.price_day }}元/span span周租{{ house.price_week }}元/span span月租{{ house.price_month }}元/span /p p classremark{{ house.remark }}/p {% if house.has_balcony 1 %} span classtag阳台/span {% endif %} /div {% else %} p classempty暂无可展示房源请先新增房源。/p {% endfor %} /div /div /body /html模板里用到了 Jinja2 的关键语法{{ house.title }}输出字段值{% for house in houses %}循环渲染列表{% else %}配合for使用当列表为空时显示提示url_for(static, filenamestyle.css)生成静态文件地址url_for(detail, house_idhouse.id)生成详情页链接。4.4 编写详情页模板创建detail.html用来展示单套房源的完整信息。文件路径house_info_system/templates/detail.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title{{ house.title }}/title link relstylesheet href{{ url_for(static, filenamestyle.css) }} /head body div classcontainer a classback-link href{{ url_for(index) }}larr; 返回列表/a h1{{ house.title }}/h1 div classdetail-info pstrong详细地址/strong{{ house.address }}/p pstrong房型/strong{{ house.house_type }}/p pstrong床型/strong{{ house.bed_info }}/p pstrong日租价格/strong{{ house.price_day }} 元/天/p pstrong周租价格/strong{{ house.price_week }} 元/周/p pstrong月租价格/strong{{ house.price_month }} 元/月/p pstrong阳台/strong{% if house.has_balcony 1 %}有{% else %}无{% endif %}/p pstrong房源状态/strong{% if house.status 1 %}可租{% else %}已下架{% endif %}/p pstrong备注/strong{{ house.remark }}/p /div /div /body /html详情页主要用来完整展示字段方便租客快速了解房源。如果后续要做“联系房东”功能可以在这里增加一个按钮展示微信号或电话也可以直接接第三方在线咨询链接。4.5 编写新增房源表单页创建add_house.html这个页面用来提交新房源信息。文件路径house_info_system/templates/add_house.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title新增房源/title link relstylesheet href{{ url_for(static, filenamestyle.css) }} /head body div classcontainer a classback-link href{{ url_for(index) }}larr; 返回列表/a h1新增房源/h1 form classhouse-form methodpost action{{ url_for(add_house) }} label标题/label input typetext nametitle placeholder例如碧桂园山湖城观澜1街1座2903 required label详细地址/label input typetext nameaddress placeholder例如碧桂园山湖城观澜1街1座2903 required label房型/label input typetext namehouse_type placeholder例如3室1厅1厨1卫2阳台 label床型/label input typetext namebed_info placeholder例如1.8m 1.5m 1.2m label日租价格元/label input typenumber step0.01 nameprice_day value0 label周租价格元/label input typenumber step0.01 nameprice_week value0 label月租价格元/label input typenumber step0.01 nameprice_month value0 label input typecheckbox namehas_balcony value1 有阳台 /label label备注/label textarea nameremark rows4 placeholder可以填写租期要求、配套家电、联系方式等/textarea button typesubmit classbtn btn-primary保存房源/button /form /div /body /html需要注意的是HTML 表单里的name属性必须和 Flask 代码中request.form.get(title)的参数名一致否则提交后数据会丢失。这个问题在真实项目里经常出现一旦发现保存后字段为空优先检查name是否匹配。4.6 编写静态样式为了让页面不至于太简陋补一份简单的 CSS。文件路径house_info_system/static/style.css* { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, PingFang SC, Microsoft YaHei, sans-serif; background-color: #f5f7fa; color: #333; line-height: 1.6; } .container { max-width: 900px; margin: 40px auto; padding: 20px; } .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .header h1 { font-size: 24px; } .btn { display: inline-block; padding: 8px 16px; border: none; border-radius: 6px; text-decoration: none; cursor: pointer; font-size: 14px; } .btn-primary { background-color: #1a73e8; color: #fff; } .search-bar { display: flex; gap: 10px; margin-bottom: 20px; } .search-bar input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 6px; } .search-bar select, .search-bar button { padding: 10px 14px; border: 1px solid #ddd; border-radius: 6px; background: #fff; cursor: pointer; } .house-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; } .house-card { background: #fff; border-radius: 10px; padding: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); } .house-card h2 { font-size: 18px; margin-bottom: 8px; } .house-card h2 a { color: #1a73e8; text-decoration: none; } .house-card .address { color: #888; font-size: 13px; } .house-card .price span { display: inline-block; margin-right: 8px; color: #e67e22; font-weight: 600; } .tag { display: inline-block; margin-top: 10px; padding: 2px 8px; background: #e8f0fe; color: #1a73e8; border-radius: 4px; font-size: 12px; } .empty { grid-column: 1 / -1; text-align: center; color: #999; padding: 40px 0; } .back-link { display: inline-block; margin-bottom: 16px; color: #1a73e8; text-decoration: none; } .detail-info p { padding: 8px 0; border-bottom: 1px solid #f0f0f0; } .house-form label { display: block; margin-top: 12px; font-weight: 600; } .house-form input, .house-form textarea, .house-form select { width: 100%; padding: 10px; margin-top: 6px; border: 1px solid #ddd; border-radius: 6px; } .house-form button { margin-top: 20px; }这份样式是演示级别的够用但不花哨。如果你想放到正式对外环境可以基于 Bootstrap、Tailwind CSS 或者 Element Plus 做一版更成熟的 UI核心业务逻辑不需要改变。4.7 运行与验证确认所有文件都创建好后在项目根目录启动 Flask 开发服务器python app.py启动后终端会显示类似下面的日志* Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:5000 * Running on http://192.168.1.101:5000打开浏览器访问http://127.0.0.1:5000就能看到示例房源在列表中展示。点击房源标题进入详情页可以看到该房源的完整信息。再回到首页点击“新增房源”按钮填写另一套房源信息并提交新数据会写入 SQLite 数据库随后自动跳回列表页并显示新增记录。到这里一个最基础版本的房源信息展示系统就跑通了。5. 功能扩展筛选、搜索与上下架管理基础版满足信息展示需求但离“好用”还有一段距离。下面给出几个低成本、高收益的扩展点。5.1 上下架管理现在status字段已经能区分可租与下架状态但还没实现“一键下架”的入口。可以增加一个/house/int:house_id/toggle路由app.route(/house/int:house_id/toggle, methods[POST]) def toggle_house(house_id): conn get_db() house conn.execute(SELECT * FROM house WHERE id ?, (house_id,)).fetchone() if house is None: conn.close() abort(404) new_status 0 if house[status] 1 else 1 conn.execute(UPDATE house SET status ? WHERE id ?, (new_status, house_id)) conn.commit() conn.close() return redirect(url_for(detail, house_idhouse_id))然后在详情页模板里增加一个按钮通过表单 POST 提交form methodpost action{{ url_for(toggle_house, house_idhouse.id) }} button typesubmit{% if house.status 1 %}下架{% else %}重新上架{% endif %}/button /form这里有两个细节值得注意为什么不用GET请求做上下架因为GET链接会被浏览器预加载、被搜索爬虫抓取误触概率很高。修改状态这种操作应该使用POST这是 Web 开发里的一条通用规范。修改数据前先SELECT一次是为了判断记录是否存在。如果记录不存在直接执行UPDATE也不会报错但页面上的跳转逻辑会变得不明确。5.2 按价格区间筛选在列表页加入最低价和最高价输入框可以按日租价或月租价过滤房源。这里演示按月租价格筛选app.route(/) def index(): keyword request.args.get(keyword, ) status request.args.get(status, 1) min_price request.args.get(min_price, ) max_price request.args.get(max_price, ) conn get_db() sql SELECT * FROM house WHERE 11 params [] if keyword: sql AND (title LIKE ? OR address LIKE ? OR remark LIKE ?) like_word f%{keyword}% params.extend([like_word, like_word, like_word]) if status in (0, 1): sql AND status ? params.append(status) if min_price: sql AND price_month ? params.append(float(min_price)) if max_price: sql AND price_month ? params.append(float(max_price)) sql ORDER BY id DESC houses conn.execute(sql, params).fetchall() conn.close() return render_template( index.html, houseshouses, keywordkeyword, statusstatus, min_pricemin_price, max_pricemax_price )搜索页模板只要在原有表单中增加两个 number 输入框即可。这样租客可以快速锁定“月租 3000-6000 元”区间的房源比翻聊天记录高效得多。5.3 增加图片上传房源信息没有图片说服力会弱很多。SQLite 本身可以存 BLOB 二进制数据但更通用的做法是图片上传到服务器静态目录数据库只保存图片文件路径。在 Flask 中处理图片上传需要配置上传目录和允许的扩展名。这里给出一个最简实现思路import os from werkzeug.utils import secure_filename UPLOAD_FOLDER os.path.join(BASE_DIR, static, uploads) ALLOWED_EXTENSIONS {png, jpg, jpeg, gif, webp} app.config[UPLOAD_FOLDER] UPLOAD_FOLDER def allowed_file(filename): return . in filename and filename.rsplit(., 1)[1].lower() in ALLOWED_EXTENSIONS表单提交时判断文件扩展名是否合法然后调用secure_filename清洗文件名保存到static/uploads目录最后把文件名写入数据库中的image_path字段。展示时通过url_for(static, filenameuploads/ house.image_path)生成图片地址。图片上传项目中一定要限制扩展名和文件大小否则很容易被塞入恶意文件。生产环境还需要考虑把图片存到云存储例如阿里云 OSS、腾讯云 COS 等避免应用服务器磁盘被撑满。5.4 导出房源信息为 Excel业主或者运营同学经常需要把房源台账整理成 Excel 发给别人可以用 Python 的pandas和openpyxl实现导出功能。思路是查询数据库后生成 DataFrame再to_excel导出import pandas as pd def export_houses(): conn get_db() rows conn.execute(SELECT * FROM house WHERE status 1).fetchall() conn.close() data [dict(row) for row in rows] df pd.DataFrame(data) df.to_excel(houses.xlsx, indexFalse)这只是一个示例思路实际导出时需要处理临时文件、响应下载、编码等问题。由于涉及导出文件格式和版本差异这里不展开写死等真正需要这个功能时建议先在一份测试数据上验证再接入正式环境。6. 常见问题与排查思路运行 Flask 项目时新手最容易遇到下面几类问题。这里整理成一个排查表问题现象常见原因解决思路启动时报ModuleNotFoundError: No module named flask当前环境没有安装 Flask或没有激活虚拟环境执行pip install flask并确认命令行前缀有(venv)启动后访问http://127.0.0.1:5000提示 404路由写错或 Flask 应用启动的是其他目录的代码检查app.route路径与浏览器访问路径是否一致页面中文出现乱码HTML 文件没有设置 UTF-8或模板编码不对在head中加meta charsetUTF-8并确认代码文件保存为 UTF-8 编码点击“新增房源”提交后页面报 400表单必填字段为空或字段name与后端不匹配检查request.form.get()参数名与 HTML 表单name属性数据库操作报sqlite3.OperationalError: no such table没有先运行init_db.py或数据库路径不一致执行python init_db.py确认DB_PATH指向同一数据库文件address already in use或端口被占用5000 端口已被其他程序占用换端口启动python app.py前设置app.run(port5001)或用lsof -i:5000查看占用进程修改了app.py但页面没有变化没有开启 debug 模式或浏览器缓存重启 Flask 服务并刷新浏览器开发时建议开启debugTrue页面样式完全丢失静态资源路径错误或者static目录结构不对检查模板中url_for(static, filenamestyle.css)确认存在static/style.css文件这里重点说两个新手容易踩的坑。第一个是数据库路径问题。sqlite3.connect(DB_PATH)使用相对路径时会根据“当前工作目录”查找数据库文件。如果你在项目根目录启动python init_db.py后又在其他目录启动python app.py就可能导致两个进程操作不同的数据库文件。解决方法就是像我上面代码里一样基于os.path.dirname(os.path.abspath(__file__))计算出绝对路径保证无论从哪里启动都能找到同一个数据库。第二个是表单 POST 返回 400 或 500 的排查顺序。建议按“字段名是否匹配 → 数据库字段是否存在 → SQL 语法是否正确 → 是否缺少必填校验”的顺序排查。先看页面提交的数据再看后端日志日志里通常会给出具体报错行数。7. 工程建议与部署注意代码能跑通只是第一步真正要应用到实际业务中下面这些工程建议值得提前考虑。7.1 配置分离现在app.py里直接写死了数据库路径只要项目变大这种写法就会很痛苦。建议把配置抽到独立的配置文件中用环境变量覆盖默认值import os class Config: BASE_DIR os.path.dirname(os.path.abspath(__file__)) DB_PATH os.environ.get(HOUSE_DB_PATH, os.path.join(BASE_DIR, instance, house.db)) SECRET_KEY os.environ.get(SECRET_KEY, dev-secret-key-change-me) DEBUG os.environ.get(FLASK_DEBUG, 0) 1 UPLOAD_FOLDER os.path.join(BASE_DIR, static, uploads) MAX_CONTENT_LENGTH 5 * 1024 * 1024这样在本地开发、测试服务器、生产服务器三套环境切换时只需要修改环境变量不用改代码。7.2 数据库备份SQLite 是文件型数据库备份最简单的方式就是复制数据库文件。但是注意直接复制正在写入的 SQLite 文件可能产生不一致的备份。推荐用 SQLite 的在线备份命令sqlite3 instance/house.db .backup backup_house.db在 Python 脚本里也可以调用sqlite3.Connection.backup方法import sqlite3 src sqlite3.connect(instance/house.db) dst sqlite3.connect(backup_house.db) src.backup(dst) dst.close() src.close()备份频率可以根据业务量决定。像房源信息这种低频修改的数据每天备份一次已经足够如果做了“租客在线预订”这类高频写入功能就要考虑更频繁的备份甚至迁移到 MySQL 这类数据库。7.3 SQL 注入防护在模板渲染和 SQL 拼接上要特别留意 SQL 注入。本文示例已经使用?占位符这是最基础也最重要的防护手段。如果以后二次开发时有人把用户输入直接拼进 SQL比如sql fSELECT * FROM house WHERE title LIKE %{keyword}%这就是典型的注入写法。用户输入% OR 11 --就能改变查询逻辑甚至删除数据。一旦在代码审查中发现类似写法必须立刻改成参数化查询。7.4 上线部署注意本地开发用的是 Flask 自带开发服务器。把它直接暴露到公网既不安全性能也扛不住高并发。建议部署方式使用 Gunicorn 作为 WSGI 服务器Linux/macOS或者 WaitressWindows 友好前面再挂 Nginx 做反向代理和静态文件处理数据库文件放在独立数据目录做好备份Flask 的debugTrue必须关闭SECRET_KEY必须设置为随机值开启 HTTPS避免表单信息和联系方式在传输中被明文截获。部署命令示例Gunicorngunicorn -w 4 -b 127.0.0.1:8000 app:app然后让 Nginx 把80/443端口的请求转发到127.0.0.1:8000。部署是另一个比较大的话题这里不展开全部细节。如果你第一次部署 Flask 项目建议先在一台测试服务器上完整走一遍流程再切生产环境。涉及公网访问、权限配置时一定要先确认自己的操作合法合规并且有备份和回滚方案。7.5 权限管理如果这个系统不止一个人使用或者放在公网上就一定要考虑权限。目前/add路由任何人都能访问意味着任何访问者都可以修改数据库这在真实场景不可接受。最轻量的方案是给表单页加一个简单的登录校验用 Flask 的session记录登录状态。复杂一点的方案是接入 Flask-Login 或 Flask-Security。无论哪种方案都要注意密码不能明文存储要使用werkzeug.security的generate_password_hash和check_password_hashsession 需要配置SECRET_KEY所有修改操作包括新增、编辑、上下架都应该校验权限而不是只隐藏按钮。8. 总结与后续学习路线这一篇从一条零散的房源文本信息出发完成了三个层次的事先把房源信息拆成结构化字段再用 Flask 写了一个完整的展示、筛选、新增流程最后补充了搜索、导出、部署、安全等工程化建议。具体来说你应该已经掌握了用CREATE TABLE设计房源信息表用 Flask 路由实现列表页、详情页、新增表单页用 Jinja2 模板循环渲染房源卡片用 SQLite 占位符参数查询避免 SQL 注入用url_for处理静态资源与页面跳转用 POST 请求实现上下架类状态修改操作部署前需要关注的配置分离、数据库备份、权限校验、关闭 debug 等事项。接下来的学习方向可以根据实际需要选择如果想把页面做得更像正式产品可以学学 Bootstrap 或 Tailwind CSS如果房源数量变多、查询变慢可以研究 SQLite 索引优化或者切换到 MySQL如果要做在线预约、订单管理那就需要学习 Flask 的表单校验、用户认证和 REST API 设计。不管往哪个方向深入都建议先把今天这个最小系统跑通然后自己加一个小功能比如增加“带阳台”筛选按钮或者给详情页加一个图片区域。改一个功能比重新读一遍文章学到的东西更多。如果你在跟着文章实操时遇到了报错欢迎把错误日志里关键的报错行截图或复制出来对照第 6 部分的排查表逐个检查。项目相关代码文件不多排查起来不会太复杂。
返回列表