Python Flask + 微信小程序:新闻网站前后端分离实战

发布时间:2026/9/14 20:30:54
Python Flask + 微信小程序:新闻网站前后端分离实战 刚接到这个需求的时候说实话我挺有感触的。这几年“微信小程序”和“Python”都是高频词但真正能把这两样东西串起来做出一个完整项目的教程并不多大部分资料要么只讲后端、要么只讲前端中间那段联调和踩坑的经历没人愿意写。我也是一路从零摸过来的中间踩了好些坑今天就把“用Python做后端接口 微信小程序做前端展示”的新闻网站从架构设计、数据源选择、后端API实现到小程序页面搭建、真机联调完整拆开讲一遍。内容偏实战不管你是准备做毕业设计、个人作品还是公司里突然接到一个“做个新闻资讯小程序”的需求应该都能用得上。1. 项目还没动手前先把这套架构想清楚1.1 一个新闻网站拆成两半看很多人一听到“微信小程序Python新闻网站”第一反应就是“我要写个小程序”然后直接打开微信开发者工具开始堆页面。这是个非常容易踩的坑。你仔细想想新闻网站的核心是什么是内容。内容从哪里来要么你自己编辑录入要么从外部抓取要么接第三方API。这些活小程序端干不了或者说干起来很别扭。所以正确的思路是把这个项目从逻辑上拆成两块——后端负责提供数据小程序负责展示和交互。后端用Python提供JSON格式的接口比如“首页新闻列表接口”“新闻详情接口”“分类接口”“搜索接口”。小程序端通过wx.request去请求这些接口拿到JSON数据后渲染到页面上。两边通过HTTP协议通信互不干扰。这个就是最典型的前后端分离架构也是目前绝大多数真实项目采用的方式。我见过有人非要把爬虫逻辑直接写在小程序里用wx.request去访问别人的新闻网站再解析HTML结果被跨域、防盗链、页面结构变更搞得焦头烂额。没必要真没必要。小程序只做展示数据获取和整理全部放到Python后端这样你换数据源、改数据结构都不会影响到已经上线的小程序端。1.2 为什么后端用Python首选FlaskPython做后端主流的方案有三类Flask、Django、FastAPI。我这次用的是Flask理由很简单项目规模小、接口数量不多、希望代码尽量精简、部署方便。Flask是一个微框架它不会强制你按照某种固定的项目结构来写你可以用一个文件跑起来也可以自己规划目录结构自由度很高。对于新闻网站这种以提供接口为主要任务的项目Flask非常合适。Django适合那种后台管理功能特别重、模型关系特别复杂的项目它自带Admin后台、ORM、认证系统功能全但学习成本也高。FastAPI性能好自动生成接口文档但是生态和资料相对前两者少一些新手遇到问题不太好查。这里我整理了一个简单的对比框架上手难度适合场景优点缺点Flask低中小型API服务、个人项目灵活轻量、资料多功能需自己拼装Django中高大型内容系统、后台管理复杂全家桶、自带Admin笨重、学习曲线陡FastAPI中高并发接口、异步需求性能好、自动生成文档异步概念对新手不友好如果你跟我一样为毕业设计或者个人作品写新闻网站Flask是最稳的选择。如果以后要做成商业项目接口量大了再迁移到FastAPI也不难核心的数据库和接口设计思路是通用的。1.3 小程序端用原生还是uni-app热搜词里一大半都在问uniapp微信小程序说明很多人纠结到底用原生写还是用uni-app跨端写。我的建议是只要你的目标平台只有微信小程序就老老实实用原生。原生小程序框架的WXML、WXSS、JavaScript虽然写法有点独特但是文档全、社区大、调试工具好用而且不存在编译层带来的兼容性问题。uni-app的价值在于“一套代码多端复用”适合那种既要微信小程序又要支付宝小程序、还要H5和App的项目。但代价是你得额外理解它的生命周期、路由规则和编译机制出了问题排查起来比原生麻烦得多。比如热搜词里那条“ios 微信小程序渲染机制特殊如果uni-datetime-picker放在scroll-view里面”出问题这就是跨端框架在iOS上的适配坑。原生框架虽然也有坑但至少问题能用微信官方文档找到答案而uni-app的坑往往要翻社区帖子猜半天。所以这篇博文后面所有的小程序代码示例我全部用原生写法。你看完如果觉得跨端有需求再把这些逻辑迁到uni-app也不迟接口是不变的变的只是页面那层皮。2. 新闻数据从哪儿来我的数据源选型思路2.1 免费API、爬虫还是本地数据新闻网站得有新闻这是整个项目的数据源头。常见的数据源有三种第三方免费API、爬虫抓取、本地模拟数据。我建议你按照下面的优先级来考虑。首先要明确一点新闻数据本身是有版权的。我不建议你去采集那些明确禁止转载的内容尤其是时政类的新闻很容易惹麻烦。安全的做法是找那些允许公开转载的内容源或者只提取新闻标题和摘要不拿全文点进去跳转原文。第三方免费API是最省事的方案。有些公开的新闻聚合接口返回的就是标准JSON字段包括标题、来源、发布时间、缩略图等后端拿过来存到数据库里再提供给自己小程序用。这种方案适合前期快速跑通项目也是我推荐给第一次做这个项目的同学的方式。要注意接口的调用频率限制和允许使用范围有些接口只允许个人学习使用不能商用。本地模拟数据是最笨但最可控的方案。你先手工整理20到30条新闻包括标题、摘要、内容、封面图、分类这些字段存到数据库里。这样做的好处是不依赖外部网络联调小程序时不受任何干扰适合先把前后端链路跑通。等整个流程没问题了再接入真实数据。爬虫抓取是我个人最推荐你去掌握、但使用时最需要克制的方案。它的价值在于你学会之后可以任意扩展数据源不局限于别人提供的API。我在这次项目里的做法是写一个爬虫脚本定期从一些公开的技术类、科技类新闻站点抓取标题和摘要入库后供小程序展示。2.2 用requestsBeautifulSoup采集公开内容如果你的数据源没有现成API那就需要自己写爬虫。Python做爬虫最经典的两件套就是requests和BeautifulSoup4。requests负责把网页拿下来BeautifulSoup4负责从HTML里提取想要的信息。一个最基本的采集流程是访问新闻列表页定位新闻标题和链接所在的HTML标签逐条解析把标题和链接提取出来再进入详情页抓取正文或摘要。这里我写了一个精简但完整的示例import requests from bs4 import BeautifulSoup def fetch_news_list(page_url): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } resp requests.get(page_url, headersheaders, timeout10) resp.encoding utf-8 soup BeautifulSoup(resp.text, html.parser) news_list [] for item in soup.select(.news-item a): title item.get_text(stripTrue) link item.get(href) if title and link: news_list.append({title: title, link: link}) return news_list有几个细节值得注意。第一headers一定要带很多站点看到没有User-Agent的请求会直接拒绝。第二resp.encoding要设置成目标站点的编码格式大部分中文站点是utf-8但也有一部分是gbk不设置就会出现乱码。第三time.sleep(1)这种访问间隔一定要加不要短时间高频请求别人的服务器这是基本素养。我的建议是爬虫抓到的内容不要直接实时返回给小程序而是抓完后统一入库。一方面实时爬取的速度很慢小程序端等不起另一方面新闻数据具有时效性存到数据库里可以按时间排序、去重、缓存体验好得多。2.3 数据库表设计新闻表的基本字段数据源的问题解决了接着就是把数据存起来。数据库我采用的是SQLite原因很简单单文件、零配置、Python内置支持。对于新闻网站这种数据量不大的项目SQLite完全够用。以后数据量大了换MySQL只需要改连接配置SQL语句基本不用动。新闻表的设计决定了整个项目后续好不好扩展。我在这个项目里用的表结构是这样的CREATE TABLE news ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, summary TEXT, content TEXT, cover_url TEXT, source TEXT, category TEXT DEFAULT 科技, publish_time DATETIME, view_count INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );字段含义很清楚title是标题summary是摘要content是正文cover_url是封面图链接source是来源站点category是分类publish_time是发布时间view_count是阅读数created_at是入库时间。我特别想强调一下索引的问题。新闻列表页最常用的查询是“按分类按时间倒序”所以一定要给category和publish_time加索引不然数据量积累到几万条之后查询会明显变慢。加索引就是一句SQL的事情CREATE INDEX idx_news_category_time ON news(category, publish_time);如果你想让首页有“热门阅读”之类的小模块那view_count字段就可以用来排序不用单独再做一套数据统计系统。3. Python后端接口落地Flask环境到API实现3.1 环境准备Python安装与虚拟环境这一步对于没接触过Python的同学确实是第一个拦路虎。我简单说下路径去Python官网下载对应你操作系统的安装包安装的时候注意勾选“Add Python to PATH”这个选项否则命令行里输入python会提示找不到命令。装完之后在终端验证一下python --version看到版本号输出就说明安装成功了。接着为项目创建一个独立的虚拟环境。虚拟环境的作用是隔离不同项目的依赖包版本防止A项目用的Flask 2.x和B项目用的Flask 3.x互相干扰。创建和使用虚拟环境的命令如下python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate激活之后命令行前面会出现(venv)这样的标识说明你已经进入了虚拟环境。然后安装依赖包pip install flask flask-cors requests beautifulsoup4flask-cors是解决跨域问题的后面会专门讲。requests和beautifulsoup4是用来采集数据的。3.2 项目目录结构和初始化后端代码不建议全部堆在一个文件里但也不需要搞得特别复杂。对于这个项目我认为一个清晰的目录结构是news-backend/ ├── app.py # Flask应用入口 ├── models.py # 数据库初始化和查询函数 ├── crawler.py # 数据采集脚本 ├── news.db # SQLite数据库文件由程序自动生成 └── requirements.txt # 依赖包列表app.py是接口层只负责接收请求、调用查询函数、返回JSON。models.py是数据层只负责跟数据库打交道。crawler.py是独立脚本用来采集新闻入库。这样分层之后你改接口不会碰数据库代码改爬虫也不会影响线上接口。app.py的初始化部分大概是这样的from flask import Flask, jsonify, request from flask_cors import CORS import models app Flask(__name__) CORS(app) app.route(/api/news, methods[GET]) def get_news(): page request.args.get(page, 1, typeint) page_size request.args.get(page_size, 10, typeint) category request.args.get(category, ) keyword request.args.get(keyword, ) total, news_list models.query_news( pagepage, page_sizepage_size, categorycategory, keywordkeyword ) return jsonify({ code: 0, message: success, data: { total: total, list: news_list } }) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)host0.0.0.0很重要。如果你只写127.0.0.1那就只有你本机能访问手机和真机小程序都连不上。3.3 核心接口的实现细节接口是整个系统的门面微信小程序端所有的数据展示都依赖这些接口。我这个项目设计了四个核心接口第一个是新闻列表接口GET /api/news。它要支持分页、分类筛选、关键词搜索三个功能。实现的时候参数从request.args里取分页用page和page_size控制分类和关键词用SQL的WHERE条件过滤。模型的查询函数可以这么写def query_news(page1, page_size10, category, keyword): conn get_db_connection() conditions [] params [] if category: conditions.append(category ?) params.append(category) if keyword: conditions.append((title LIKE ? OR summary LIKE ?)) params.append(f%{keyword}%) params.append(f%{keyword}%) where_sql WHERE AND .join(conditions) if conditions else total conn.execute(fSELECT COUNT(*) FROM news {where_sql}, params).fetchone()[0] offset (page - 1) * page_size rows conn.execute( fSELECT id, title, summary, cover_url, source, category, publish_time, view_count fFROM news {where_sql} ORDER BY publish_time DESC LIMIT ? OFFSET ?, params [page_size, offset] ).fetchall() news_list [dict(row) for row in rows] conn.close() return total, news_list这里有几个SQL细节我吃了不少亏。第一LIMIT和OFFSET不能直接用字符串拼接要用参数占位否则有SQL注入风险而且SQLite的占位符是?不是%s。第二ORDER BY publish_time DESC必须放在LIMIT前面顺序错了直接报语法错误。第三分页的offset从0开始计算所以第N页的偏移量是(page - 1) * page_size。第二个是详情接口GET /api/news/int:news_id。这个接口返回的是单条新闻的完整内容包括正文的content字段。详情接口的另一个作用是用来增加阅读数我用一句UPDATE news SET view_count view_count 1 WHERE id ?来实现简单高效不会出现并发写冲突的问题。第三个是分类接口GET /api/categories。返回所有不重复的分类列表用于小程序的分类导航栏。SQL语句是SELECT DISTINCT category FROM news注意要排除空值。第四个是搜索接口。我把它跟列表接口合并了通过keyword参数实现小程序端输入关键词请求列表接口就行不必单独开一个接口。3.4 统一返回格式和跨域问题接口返回格式一定要统一这点在设计初期就要定下来。我用的格式是{ code: 0, message: success, data: {} }code为0表示成功非0表示失败。data里是具体数据可以是对象也可以是数组。这样小程序端的封装就特别简单只要判断code是否等于0再处理data即可。有的同学会问为什么不用HTTP状态码来表示业务错误因为HTTP状态码只有那几十个不够表达业务语义。比如“新闻不存在”和“参数错误”都可以是400但业务上完全是两回事所以要在code里做区分。跨域问题比较隐蔽。如果你只是用微信开发者工具调试小程序跨域问题不大。但如果后期把页面放到浏览器里测试Flask后端口是5000浏览器页面到5000端口的请求会触发CORS限制。解决方案就是用flask-cors在初始化时执行一行CORS(app)所有路由就都允许跨域了。这个坑我当时排查了很久网页端怎么都访问不了接口控制台报CORS错误加了这行代码立刻就好了。关于中文乱码还需要在Flask里设置一处app Flask(__name__) app.config[JSON_AS_ASCII] False不设置的话返回的JSON中文会被转成ASCII码小程序端显示成\uXXXX这种格式虽然没有错但调试起来非常痛苦。4. 微信小程序端的页面搭建与数据联动4.1 app.json配置和tabBar导航后端接口搞定了接下来就是小程序前端。新建一个微信小程序项目时开发者工具会自动生成app.json、app.js、app.wxss和pages目录。app.json是全局配置文件页面注册、窗口样式、tabBar导航都在这里配。新闻网站我设计两个主tab首页和我的。tabBar需要三个颜色属性——color、selectedColor、backgroundColor每个tab需要iconPath和selectedIconPath两张图标图片。图标用简单的PNG图片就行这个不限制。配置完的app.json大概是这个感觉{ pages: [ pages/index/index, pages/detail/detail, pages/category/category, pages/search/search ], window: { navigationBarTitleText: 今日新闻, navigationBarBackgroundColor: #ffffff, navigationBarTextStyle: black }, tabBar: { color: #999999, selectedColor: #1296db, list: [ { pagePath: pages/index/index, text: 首页, iconPath: images/home.png, selectedIconPath: images/home_active.png }, { pagePath: pages/category/category, text: 分类, iconPath: images/category.png, selectedIconPath: images/category_active.png } ] } }pages数组里的第一个页面就是小程序的启动页所以首页放在第一位。这里有个细节tabBar只能配置2到5个tab如果你有别的页面不想出现在tabBar里就不需要在这里注册通过wx.navigateTo跳转即可。关于热搜词里提到的“修改刚进入的加载页面”其实就是两件事一是window配置里的导航栏标题、背景色二是设置启动屏。个人主体的小程序无法修改微信官方的启动屏但可以通过配置页面导航栏颜色、为首页加骨架屏来提升首屏体验。骨架屏我后面会讲到先记住“首屏体验由导航栏配置页面加载状态决定”这个结论就够用了。4.2 首页新闻列表的实现首页是整个小程序的核心它要做的事情是进入页面后请求GET /api/news接口拿到新闻列表数据渲染成卡片列表用户下拉刷新时重新请求第一页用户上拉时加载下一页。对应的生命周期函数是onLoad和onReachBottom。onLoad是页面首次加载时执行onReachBottom是页面滚动到底部时执行。这里要特别注意onLoad里请求第一页数据时一定要用一个page变量记录当前页码加载更多时页码加1避免数据重复。我踩过的一个坑是在onReachBottom里请求数据后直接concat到数组尾部但忘记判断是否已经是最后一页结果下滑到列表底部时反复请求同一页数据造成重复。后来加了hasMore这个标志位每次接口返回后判断page * page_size total小于说明还有更多否则设置hasMore为false。首页请求接口的核心逻辑Page({ data: { newsList: [], page: 1, pageSize: 10, hasMore: true, loading: false }, onLoad() { this.fetchNews(); }, fetchNews() { if (!this.data.hasMore || this.data.loading) return; this.setData({ loading: true }); wx.showLoading({ title: 加载中... }); wx.request({ url: http://127.0.0.1:5000/api/news, data: { page: this.data.page, page_size: this.data.pageSize }, success: (res) { if (res.data.code 0) { const list res.data.data.list; const total res.data.data.total; const page this.data.page; const mergedList page 1 ? list : this.data.newsList.concat(list); this.setData({ newsList: mergedList, page: page 1, hasMore: page * this.data.pageSize total }); } }, complete: () { wx.hideLoading(); this.setData({ loading: false }); wx.stopPullDownRefresh(); } }); }, onPullDownRefresh() { this.setData({ page: 1, hasMore: true, newsList: [] }); this.fetchNews(); }, onReachBottom() { this.fetchNews(); } });关于wx.request返回的数据有一种简写方式是把res.data.data.list直接拿去做渲染但这样一旦接口返回异常小程序端就会报错。我建议对res.data.code做一次判断再取数据。这是防御式编程的习惯后端接口谁也不敢保证永远不返回异常。首页WXML层展示新闻卡片我用的是一个block配合wx:for循环每张卡片展示封面图、标题、摘要、来源和阅读数。封面图用wx:if判断没有封面图就不渲染image组件避免出现一张很大的破图。这里要特别提醒小程序image组件默认的尺寸是320px乘以240px如果不给它的mode属性设置值图片会被拉伸得非常难看。封面图建议统一用modeaspectFill它会在保证图片比例的前提下裁剪填满视觉上最整齐。view classnews-card wx:for{{newsList}} wx:keyid bindtapgoDetail>goDetail(e) { const id e.currentTarget.dataset.id; wx.navigateTo({ url: /pages/detail/detail?id id }); }详情页的onLoad接收参数后请求单个新闻的详情接口拿到数据后用setData赋值给页面。这里有个细节onLoad(options)里的options是一个对象options.id就是传递过来的新闻ID但是这个ID是字符串类型。如果后端接口要求的是整数ID直接传给wx.request虽然大多数后端都能处理但严谨起见还是用parseInt(options.id)转一下避免出现类型不匹配的诡异问题。详情页的正文展示我用的是rich-text组件。新闻正文是我在数据库里存的HTML片段包含p标签和img标签。rich-text组件可以直接渲染这段HTML不需要自己解析。但要注意rich-text里的图片不会自动适配屏幕宽度有些图片宽几百像素有些宽几千像素显示效果会很乱。我处理的办法是在后端入库前对正文做一次处理给图片标签统一加上stylemax-width:100%;height:auto;从源头解决。详情页还有一个阅读数增加的功能这个不需要页面单独请求后端的详情接口里已经做了自增处理。页面上展示阅读数时直接用后端返回的最新值就行。4.4 搜索、分类和下拉刷新搜索和分类是新闻网站的标配功能也是很多教程会漏掉的部分。搜索页我建议单独建一个搜索框点击搜索按钮或键盘确认时触发搜索逻辑请求GET /api/news?keywordxxxx。输入框的值绑定到data里的keyword字段通过bindinput事件更新。这里性能上有个优化点不需要每次输入都请求接口等用户点击搜索或者按回车再请求否则会打出几个字就发几个请求既浪费流量又增加后端压力。分类页的思路是顶部一个横向滚动的分类导航栏下方是新闻列表。分类导航用scroll-view组件设置scroll-x属性内容用wx:for循环渲染分类按钮。选中的分类高亮显示点击切换分类时重新请求第一页数据。计算公式记住scroll-view横向滚动时内部元素需要用display: inline-block或flex布局否则内容会被压缩成一行且无法滚动。下拉刷新和触底加载在首页已经实现过分类页复用同样的逻辑这里不再重复。加载体验这块我建议至少做到三点。第一请求数据时用wx.showLoading配合透明遮罩防止用户重复点击第二onPullDownRefresh必须配合wx.stopPullDownRefresh使用否则加载圈会一直转第三首屏渲染大数据列表时尽量用setData一次传整个数组避免循环里反复调用setData这是性能优化的基础常识。5. 联调踩坑实录域名、真机、外部跳转5.1 本地联调不校验合法域名选项前后端都写完之后接下来就是联调。这一步看似简单实际上坑最多。最基础的问题是在微信开发者工具里请求http://127.0.0.1:5000/api/news默认情况下会报“url not in domain list”的错误。因为微信小程序要求所有请求的域名必须是在小程序后台配置过的合法域名并且必须是HTTPS协议。本地调试时你需要手动解除这个限制。具体操作是微信开发者工具右上角的“详情” - “本地设置” - 勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”。勾选之后开发工具里就能正常请求本地HTTP接口了。但是这里有一个非常迷惑的差异你在开发者工具里能请求通不代表真机上能请求通。真机调试时如果不做任何处理同样会报域名校验失败。这个问题的根源是小程序的安全策略它只对代码包里的请求做校验不管开发者工具里勾了什么选项。所以真机调试前要么把后端部署到有HTTPS证书的服务器上要么在开发者工具里选择“真机调试”而不是“预览”真机调试模式会复用开发者工具的代理走的是本地网络可以绕过一部分限制但也不是万无一失。5.2 真机预览必须面对的三个配置当你准备让真机直接访问部署好的后端接口时有三个配置必须搞清楚缺一个都跑不通。第一个是request合法域名。小程序前端所有wx.request请求的域名不含端口必须配置在小程序后台的“开发管理” - “服务器域名” - “request合法域名”里。注意不带端口。也就是说如果你的后端跑在https://example.com:8443这个带端口的地址是配不进的要么把服务放到标准443端口要么通过Nginx做端口转发。第二个是downloadFile合法域名。新闻详情页里的图片如果是通过image组件直接加载的需要配置downloadFile合法域名。很多同学只配了request域名忽略了downloadFile结果发现新闻文字能显示图片全挂了控制台报错提示域名不在合法列表中。这个坑非常隐蔽因为它只在资源加载的一瞬间报错如果网络好你根本来不及看控制台。第三个是业务域名。如果你用了web-view组件来加载外部网页就要配置业务域名。而且业务域名有一个硬性要求你必须在小程序后台下载校验文件放到该域名的根目录下验证你对这个域名的所有权。审核时间比较长建议提前配置。我个人的建议是个人开发者在没有域名和服务器的情况下用“真机调试”就够完成功能验证了。真正要上线发布再去购买域名、服务器配置SSL证书把小程序的域名白名单配齐。这是一个从“能跑”到“能发布”的必经过程。5.3 iOS端scroll-view内的组件渲染问题热搜词里提到一个iOS端渲染机制的特殊问题说的是把uni-datetime-picker放在scroll-view里会出问题。这个问题原生小程序也有类似的坑。iOS对scroll-view内部的组件渲染处理和Android有差异某些交互组件放进去之后会出现滚动事件被吞掉、点击不响应、弹层位置错乱等问题。我的建议是交互密集型的组件尽量不要嵌套在scroll-view里。比如搜索框、日期选择器、弹窗这些组件直接放在页面主体层级通过position: fixed或普通流式布局来定位。只有纯展示类的数据列表才适合放在scroll-view里做滚动。如果确实需要在滚动区域内放交互组件优先用页面级别的滚动page滚动替代scroll-view滚动或者尝试给scroll-view设置enhanced属性来启用增强渲染这个属性在iOS上有一定的兼容性提升。还有一个更基础的坑iOS上的scroll-view如果内部没有足够的内容撑开会出现滚动不流畅、回弹卡顿的问题。处理办法是给scroll-view设置一个最小高度或者用一个高度为100%的父容器包裹。这些经验都是反复调试出来的写在这里帮大家少走弯路。5.4 weixin://dl/business外部跳转的限制热搜词里有一条“微信小程序跳转链接weixin://dl/business从生成到触发的全流程避坑”这个我也研究过一段时间。weixin://dl/business是微信开放给商家的一种scheme协议通过这个协议可以从小程序跳转到微信生态内的其他业务场景。但问题是这个能力不是对小程x空主体开放的它通常要求企业主体、特定类目并且要走商业流程的申请。我在一个项目里就吃过这个亏产品经理要求从新闻详情页跳转到商家的企业微信让我在小程序端用wx.navigateToMiniProgram跳转结果报错说没有权限。后来查文档才发现普通的小程序不能随意调起weixin://dl/business这种scheme微信对这类跳转有严格的权限控制。如果你确实有从新闻网站跳到外部链接的需求合法的替代方案有两个一是用web-view组件把外部链接放到一个网页里在小程序内通过web-view承载二是如果目标是一个微信小程序用wx.navigateToMiniProgram并配置好目标小程序的AppID。但这两个方案都有前提条件web-view必须配置业务域名wx.navigateToMiniProgram跳转的小程序需要在同一个微信开放平台账号下或已关联。没有捷径微信平台对跳转生态的控制是出于安全考虑大家按规矩配合就是最快的路线。6. 部署上线前的几个善后工作6.1 gunicornnginx部署本地环境跑通之后离真正上线还差一步部署。Flask自带的开发服务器只能用于开发调试不能用于生产环境它的并发能力和安全性都不够。我用的是gunicorn加nginx的组合。gunicorn是Python的WSGI服务器负责运行Flask应用和处理Python侧的请求nginx是反向代理服务器负责接收外部请求转发给gunicorn同时处理静态文件、HTTPS证书这些事。部署流程大致如下# 安装gunicorn pip install gunicorn # 用4个worker进程启动Flask应用 gunicorn -w 4 -b 127.0.0.1:5000 app:app这里-b 127.0.0.1:5000表示gunicorn只在本地监听不直接对外暴露。然后配置nginx反向代理server { listen 80; server_name yourdomain.com; location / { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }将yourdomain.com换成你自己的域名然后配置HTTPS证书小程序的合法域名要求必须HTTPS。证书可以用各云服务商提供的免费证书申请和配置步骤都不复杂。部署过程中有个细节如果gunicorn启动失败多半是app:app的模块导入路径问题app是文件名后面那个app是Flask实例名两个都要对。-w 4表示启动4个worker进程不是一个固定的最优值一般按CPU核心数乘2加1来设置比如两台核心的机器可以用5四核的用9。实际压测之后再调整初期用4就行。6.2 上线后的数据更新和缓存新闻网站的数据是实时变化的上线之后你不能每次都手工去数据库里录入。我的做法是写一个定时任务每天凌晨自动运行爬虫脚本抓取新数据入库。在Linux上用crontab配置0 4 * * * cd /path/to/news-backend /path/to/venv/bin/python crawler.py crawler.log 21这里指定了虚拟环境里的Python解释器路径这一点非常关键。如果直接写python系统可能找不到你虚拟环境里安装的requests和bs4任务会静默失败然后在crawler.log里留下一堆导入错误。另外 crawler.log 21是把标准输出和错误信息都追加到日志文件方便你排查定时任务是否正常运行了。爬虫脚本需要做去重处理。同一篇新闻如果连续两天被抓到数据库里不能出现两条一模一样的记录。我的做法是抓取时用新闻URL链接作为唯一标识在入库前查一下数据库中是否已经有这个链接有就跳过。SQL是SELECT COUNT(*) FROM news WHERE source_url ?source_url字段在之前的表结构里没有加你想支持去重的话需要加上这个字段并给它建一个唯一索引。这是数据采集型项目很容易忽略的一环不加去重逻辑数据量很快就会失控。接口响应速度方面SQLite加上索引之后新闻列表接口的响应时间通常都在几十毫秒初期不需要引入Redis做缓存。如果真的有一天数据量上来了再在Flask里加一层flask-caching的Redis缓存也不难。6.3 后续可以扩展的方向整个项目跑通之后可以扩展的功能其实很多。我觉得比较值得做的方向有这么几个。第一个是阅读数统计和推荐。现在已经有了view_count字段可以做一个热门新闻排行榜在首页加一个“热门”tab。推荐系统的话可以做个最简单的基于分类的推荐就是当你点开某篇科技新闻时推荐同分类下的其他新闻。这个逻辑在后端一条SQL就能搞定分页查询加上排序即可。第二个是用户收藏和评论。收藏功能需要引入用户的微信登录态用wx.login拿到code后端通过code换openid再和新闻ID关联存库。评论功能类似需要建一张评论表表结构有评论内容、新闻ID、用户openid、评论时间四个字段就够。第三个是后台管理。现在所有数据都是爬虫自动采集的如果想让新闻质量可控可以加一个简易的审核后台用Flask的render_template渲染几个管理页面实现新闻列表的增删改查。完全不依赖第三方管理框架手写就够了。第四个是多端适配。如果后续想上App、H5或者支付宝小程序把后端接口直接复用即可只需要重写前端页面。这也是当初坚持前后端分离的好处。最后再分享一个小技巧。小程序端做首屏加载时别让用户干等着可以给新闻列表加一个简单的骨架屏——用几块灰色的色块模拟标题和图片的位置数据回来后替换掉。这个用wx:if控制渲染即可骨架屏用wxss的渐变动画模拟一个左右扫光的视觉效果。纯CSS实现不依赖任何组件库网上搜“小程序骨架屏CSS代码”能找到很多现成的模板拿过来改改就能用。这个小优化对用户体验的提升非常明显特别是在弱网环境下打开小程序首页时用户至少不会觉得页面卡死没反应了。这个项目我已经完整跑通过一遍从零开始到小程序端流畅展示新闻前后花了大概四天时间。最耗时间的其实不是写代码而是排查联调阶段那些“为什么开发工具里正常、真机上就挂了”之类的问题。希望这篇博文能帮你把这条链路里的坑提前填平让你把精力集中在真正有意思的功能扩展上。如果你照着搭建过程中遇到了其他问题结合微信官方文档和Flask源码钩子大部分都能找到答案动手去试永远比看教程更快。