基于协同过滤的智能旅游推荐系统Python源码全解析

发布时间:2026/10/3 2:46:39
基于协同过滤的智能旅游推荐系统Python源码全解析 简介面向毕业设计场景的智能旅游推荐系统完整源码包基于Python与MYSQL开发适合计算机相关专业学生用于课程设计或毕设参考。资源共799个文件压缩包大小25.71MB组成上包含45个Python源码及40个编译产物、53个Vue前端页面、53个HTML与53个CSS文件、162个SVG图标、79个GIF动图等另有SQL数据库脚本、设计说明文档、安装运行批处理文件可支撑从环境搭建到功能演示的完整流程。系统覆盖首页、个人中心、用户管理、旅游资讯、景点信息、景点分类、酒店信息、行程分享、交流论坛、系统管理等模块前后台结构清晰便于二次开发和功能扩展附带论文说明文档还梳理了系统分析、数据库设计及详细设计思路含需求分析、功能模块划分、核心模块实现说明适合毕业设计写作参考。已集成一键启动脚本便于快速部署体验。目前已有231人学习下载旅游类系统开发者或应届毕业生值得收藏与借鉴。1. 智能旅游推荐系统这份 Python 源码 zip核心其实是十几年前的协同过滤“智能旅游推荐系统”这类标题在课程设计和毕业设计源码包里出现频率极高。你从下载站拿到这份包含 python 源码和说明文档的 zip解压后通常是一个 Flask 项目加若干 CSV 数据文件跑起来能在网页上看到“猜你喜欢”的景点卡片。它的“智能”不来自大模型而是经典的协同过滤——先找和你口味最像的一群人把他们评分高的景点加权排序推给你。这套方案在今天依然值得投入不依赖 GPU几千条评分数据就能产出可解释的结果是学生做毕设、新手入门推荐系统成本最低的路径。这篇笔记按实际落地顺序来写先拆数据结构和推荐链路再讲环境启动接着是参数调优和五个高频翻车点最后换成你自己的城市数据做验证。2. 先拆数据流再碰代码一套旅游推荐系统为什么由三张表和一条链路构成拿到源码包先别急着python app.py。推荐系统的核心在数据组织方式页面和接口只是外壳。这套系统的数据层几乎固定在用户表、景点表、评分表三张表上算法层做“离线相似度计算 在线召回排序”两段式处理。把这两块读明白后面的调参和改造才有依据。2.1 用户表、景点表、评分表数据字典是读懂源码的钥匙先看建表语句源码里的init_db.py或create_tables.py通常包含了完整的 SQL。典型的表结构如下-- 用户表记录用户基本属性region 字段常用来做地域过滤 CREATE TABLE user ( user_id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, region TEXT ); -- 景点表city 和 category 是内容过滤的重要维度 CREATE TABLE scenic ( scenic_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, city TEXT, category TEXT, price REAL DEFAULT 0, score REAL DEFAULT 0 ); -- 评分表协同过滤的唯一数据来源 CREATE TABLE rating ( rating_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, scenic_id INTEGER, rating REAL CHECK(rating BETWEEN 1 AND 5), ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(user_id) REFERENCES user(user_id), FOREIGN KEY(scenic_id) REFERENCES scenic(scenic_id) );用户表里region字段值得注意。旅游推荐的典型场景是“杭州用户想看杭州周边”单纯做全局协同过滤会推出大量用户根本不会去的异地景点。很多源码包虽然在算法里没直接用region但改造时它是最便宜的过滤维度。评分表是整个系统的重心rating的取值范围通常约定为 1 到 5ts字段记录评分时间用于时间衰减。旅游数据集的突出特点是稀疏几百个用户对几千个景点打分用户-景点矩阵里往往 95% 以上是空值。这意味着任何协同过滤算法都要处理“绝大多数用户没有共同评分”的情况。初始化脚本一般会从data/下的 CSV 批量导入这三张表导入前先把 CSV 的表头和数据字典对照一遍很多报错都来自字段名对不上。2.2 离线算相似度、在线做召回UserCF 主链路的完整实现这套系统的推荐核心一般集中在cf.py这样的单文件模块里几十行到一百行不等。第一种实现是基于用户的协同过滤 UserCF链路分两步离线阶段计算用户间相似度矩阵在线阶段为某个用户找 K 个最近邻聚合邻居评分产出 TopN 推荐。下面这段代码与源码包里的核心逻辑一致用 Pandas 实现不依赖额外算法库import pandas as pd import numpy as np ratings pd.read_csv(data/ratings.csv, names[user_id, scenic_id, rating, ts]) # 构造用户-景点矩阵缺失评分填 0便于做矩阵运算 matrix ratings.pivot_table(indexuser_id, columnsscenic_id, valuesrating, fill_value0) # 余弦相似度每个用户看成向量计算两两之间的夹角余弦 norm np.linalg.norm(matrix.values, axis1, keepdimsTrue) norm[norm 0] 1e-9 # 防止全零行除零 matrix_norm matrix.values / norm user_sim pd.DataFrame(matrix_norm.dot(matrix_norm.T), indexmatrix.index, columnsmatrix.index) def recommend_for_user(user_id, top_n10, k20): # 取出相似度最高的 k 个邻居排除自己 sim user_sim.loc[user_id].drop(indexuser_id) sim sim.sort_values(ascendingFalse).head(k) # 候选集邻居评分过、但目标用户没去过的景点 cand ratings[ratings[user_id].isin(sim.index)].copy() visited ratings.loc[ratings[user_id] user_id, scenic_id].values cand cand[~cand[scenic_id].isin(visited)].copy() # 预测分 邻居评分按相似度加权求和 / 相似度之和 cand[sim] cand[user_id].map(sim) cand[weighted] cand[rating] * cand[sim] pred (cand.groupby(scenic_id)[weighted].sum() / cand.groupby(scenic_id)[sim].sum()) # 去掉邻居少、偶然分高的景点最少需要 2 个邻居打过分的才参与排序 cnt cand.groupby(scenic_id)[sim].count() pred pred[cnt 2].sort_values(ascendingFalse).head(top_n) return pred.index.tolist()这段代码的动作拆开看pivot_table把长表转成宽表fill_value0把缺失评分填零目的是让矩阵乘法能直接进行。但填零本身有副作用——用户没去过的景点和“打了 0 分”在数学上被等同处理所以这里用的是余弦相似度而不是皮尔逊相关系数否则零填充会严重拉低相似度。norm[norm 0] 1e-9防的是极端情况某个用户只有一行记录且评分为 0除以零会得到 NaN 并污染整行相似度。k20是邻居数top_n10是最终推荐条数这两个就是后面调参的主角。代码最后一步cnt 2是常见但容易被忽略的过滤只有 1 个邻居评分过的景点加权分可能被单个高相似度用户顶得很高实际上缺乏统计意义。很多源码包的推荐结果“看起来不准”问题就出在少了这行过滤。2.3 为什么旅游场景选 UserCF低频、稀疏、带地域偏好源码包一般会同时提供 UserCF 和 ItemCF 两个版本默认跑通的是 UserCF这个选择有场景依据。电商里用户购买高频、物品更新快ItemCF 能抓住“买过 A 的人还买 B”的关联旅游则相反一个人一年去不了几个地方重复访问同一景点的概率极低物品间的共现关系比用户间的关系稀疏得多。UserCF 适合这种“用户少、物品多、兴趣个性化强”的场景——找相似的人比找相似的景点更可靠。但 UserCF 有一个明显软肋冷启动。新用户没有评分记录时sim为空推荐结果直接崩掉。源码包对此的常见处理是在app.py里做一层回退——用户评分不足 3 条时改推城市热度榜。这个逻辑第三、四章会展开讲。理解了“为什么是 UserCF”你在把数据换成自己城市时就不会纠结于换算法而是先保证数据质量和回退策略。3. 用 Conda 在本地跑通环境安装、数据初始化与第一次启动多数人在这套系统上浪费的时间不是在算法上而是卡在环境。Python 版本不对、依赖装错、CSV 路径读不出来每个都够劝退一轮。这一章按顺序走完解压、建环境、初始化、启动四步少踩一个是一个。3.1 解压后先看什么目录结构、说明文档和 requirements.txtzip 解压后先别双击app.py花两分钟看目录结构和说明文档。源码包的布局大同小异常见是这个样子travel-system/ ├── app.py # Flask 入口负责页面渲染和接口 ├── cf.py # 协同过滤核心算法 ├── init_db.py # 建表 从 CSV 导入数据 ├── requirements.txt # Python 依赖清单 ├── data/ │ ├── users.csv │ ├── scenics.csv │ └── ratings.csv ├── templates/ │ ├── index.html │ └── recommend.html └── README.md # 或是一份 说明文档.pdf先打开requirements.txt看依赖版本再打开README.md或说明文档里的“运行步骤”一节。这一步能提前知道作者用的 Python 版本、是否需要先装 MySQL、CSV 是放在data/还是需要手动生成。很多源码包在说明文档里写了“Python 3.8 Flask 2.x”但实际代码用了 3.10 才有的语法提前看到能省去后面排查语法报错的时间。3.2 单独建一个 Python 环境把依赖和版本一次锁死我强烈建议用 Conda 单独建环境而不是直接用系统 Python因为这套系统依赖的旧版本 Pandas 很可能和机器上已有的新版冲突。以下假设你已经装好了 Anaconda 或 Miniconda# 创建 python 3.8 环境旅游推荐系统最常见的运行版本 conda create -n travel_rec python3.8 -y conda activate travel_rec # 在项目目录下安装依赖 cd travel-system pip install -r requirements.txtrequirements.txt里通常锁定着类似下面的版本组合flask2.2.5 pandas1.5.3 numpy1.23.5 scikit-learn1.2.2Python 3.8 配 Pandas 1.5.3 是这套系统最稳的组合。Pandas 2.x 改了不少行为比如append方法被移除、groupby.apply的默认行为变化旧代码在 Pandas 2.x 上经常报错。如果你手头的源码包没有requirements.txt就按上面的版本手工装装完后用pip list | grep pandas确认版本避免装出pandas 2.0这种隐患。如果下载依赖时网速慢可以把 pip 的 index-url 换成你常用的国内 PyPI 镜像只影响下载速度不影响依赖本身。环境建好后不用急着进入下一步先执行一下python -c import flask, pandas; print(flask.__version__, pandas.__version__)确认三个核心库都进得来。3.3 初始化数据库并启动服务从 init_db.py 到 app.py环境就绪后先跑初始化脚本再启动 Web 服务。以 SQLite 版本的源码包为例# 第一步建表并导入 data/ 下的 CSV 数据 python init_db.py --data data/ --db travel.db # 正常情况下终端会输出 Table user created 之类的提示 # 然后用 sqlite3 快速验证数据行数 sqlite3 travel.db SELECT COUNT(*) FROM rating;init_db.py做的事情就是把第二章的三张表建出来然后循环读取data/下的 CSV 逐行插入。这里有两个必须检查的输出点一是建表语句是否执行成功二是rating表的行数是否和ratings.csv的行数一致。如果导入后行数偏少多半是 CSV 里有重复主键或评分超出 1~5 范围被CHECK约束拦掉了。数据初始化成功后启动 Web 服务python app.py # 输出类似 # * Running on http://127.0.0.1:5000浏览器访问http://127.0.0.1:5000输入一个存在的用户 ID页面上应该能看到推荐景点列表和推荐理由。如果页面能打开但推荐为空先回数据库查这个用户有没有评分记录——SELECT COUNT(*) FROM rating WHERE user_id目标ID;如果结果是 0说明你命中冷启动第五章有专门处理。4. 三个必调参数K 邻居数、相似度阈值和评分权重怎么调到“能用”系统跑通只是第一步推荐质量是另一回事。源码包自带的参数不一定适合你的数据尤其是数据量、稀疏度和评分分布变化时默认参数会让推荐结果看起来“完全不像那么回事”。这一章只讲三个最出效果的参数最近邻 K、相似度阈值、评分中心化外加一个兜底机制。4.1 K 值从 10 试到 30邻居数决定推荐是发散还是平庸UserCF 的 K 值直接影响推荐列表的“个性浓度”。K 太小比如k5参与加权的邻居太少推荐结果容易被单个高相似度用户带偏出现“只看过一个景点就反复推同类”的情况K 太大比如k50而数据集总共只有 80 个有效用户那几乎是把所有用户的偏好混在一起平均推荐结果退化成全局热度榜。我在这个数据量级几百用户、几千景点、约一万条评分上一般从k10起步以 5 为步长试到k30。判断标准不是看列表顺不顺眼而是看两点推荐结果里是否出现目标用户所在城市之外的景点旅游场景下这类误差最扎眼以及列表尾部是否出现评分人数特别少的冷门景点。出现前者说明 K 太大或相似度算得不够准出现后者说明需要加上第二章代码里那个cnt 2的过滤条件。参数对比可以这样观察K 值典型现象适用数据5~10推荐结果个性化强但抖动大换一条评分结果大变评分密集、用户数少的小数据集15~25个性化与稳定性平衡常见默认区间首选起点30趋近热度榜用户间差异变小用户数多但每人评分很少的稀疏集源码包里recommend_for_user(user_id, top_n10, k20)的写法把 K 作为参数暴露意味着你可以直接在请求入口临时传k15对比不用改函数内部。如果你改成了配置文件驱动把k写进config.py里更便于批量实验。4.2 评分为何要中心化均值偏移和时间衰减的修正第二章的余弦相似度有个已知缺陷它对用户的打分习惯敏感。有人习惯给 4 分以上有人习惯给 2 到 3 分余弦相似度会把这两种用户算得“不相似”即便他们去过的景点高度重合。解决方法是先对评分做中心化也就是把每个评分减去该用户的平均分def pearson_similarity(ratings): pivot ratings.pivot_table(indexuser_id, columnsscenic_id, valuesrating) # 行中心化减去每个用户的平均评分 pivot pivot.sub(pivot.mean(axis1), axis0) pivot pivot.fillna(0) # 中心化之后用余弦等价于计算皮尔逊相关系数 norm np.linalg.norm(pivot.values, axis1, keepdimsTrue) norm[norm 0] 1e-9 sim pivot.values.dot(pivot.values.T) / (norm norm.T) return pd.DataFrame(sim, indexpivot.index, columnspivot.index)这段代码把“绝对分”变成“相对分”评分为 3 的用户给某个景点打了 4 分说明他喜欢评分为 5 的用户打了 4 分说明一般。中心化后两个 4 分不再被当成同样的偏好强度。改完相似度矩阵后推荐结果通常比直接用原始评分的余弦相似度要稳。这也是很多源码包“跑得通但感觉不准”的第一元凶——不是算法错了是评分没中心化。时间衰减属于进阶修正。旅游偏好会漂移半年前打的高分参考价值应低于上周的评分。常见做法是给评分加一个指数衰减权重import time def time_decay_rating(row, half_life_days180): ts pd.to_datetime(row[ts]) age_days (pd.Timestamp.now() - ts).days decay 0.5 ** (age_days / half_life_days) return row[rating] * decayhalf_life_days180表示评分每过 180 天权重减半。加了衰减之后同一个用户对同一个景点的历史高分不会再“永远压过”最近的新评分。注意这个函数要在求相似度之前作用于评分列否则衰减白做。4.3 混合召回兜底热度榜和内容过滤把边缘用户接住协同过滤不是万能的边缘用户和边缘景点都需要兜底。边缘用户指评分少于 3 条的人他们的相似度向量几乎不可信边缘景点指被评分次数极少的景点cnt 2过滤后它们直接被排除。源码包里的常见做法是在协同过滤结果为空时切换到热度榜用加权分排序而不是简单算平均分# 热度榜用评分人数和平均分综合计算避免“1 个人打 5 分”霸榜 hot (ratings.groupby(scenic_id)[rating] .agg([count, mean]).reset_index()) hot[score] (hot[count] * hot[mean]) / (hot[count] 5) # 按城市过滤优先推用户所在城市的景点 hot hot.merge(scenics[[scenic_id, city]], onscenic_id) hot hot[hot[city] user_city] hot hot.sort_values(score, ascendingFalse).head(top_n)count 5是平滑项可以理解为“先假设这个景点有 5 个平均分 3 分的匿名评分”评分人数越少分数被拉向均值的力量越强这样一条 5 分满分但只有一个人评价的景点排不过 50 个人打了 4.5 分的景点。旅游推荐里城市过滤是性价比最高的特征工程它不需要任何模型训练只靠region和city字段做等值匹配但对推荐结果“看起来合理”的提升立竿见影。5. 避坑五连zip 伪加密、中文乱码、空推荐和换机翻车从解压到上线这套系统有几类高频问题几乎每一个跑过旅游推荐源码包的人都遇到过。按踩坑频率排序写五个典型的现场。5.1 zip 文件“需要密码”但其实是伪加密现象从下载站拿到的 zip 双击后提示输入密码但说明文档里没给密码用 7-Zip 打开却能直接看到内部文件名拖拽解压时又报加密。原因zip 格式头部有一个“加密标志位”有些发布者为了防爬虫或引流只修改了这个标志位而没有真正加密数据。这种称为伪加密文件本身没有加密内容只是解压工具看到标志位后要求输密码。解决先用 7-Zip 或 WinRAR 打开如果能看到文件名和文件大小直接尝试拖拽复制到本地目录多数情况能成功。如果复制失败用 Python 的zipfile模块读取并忽略加密标志import zipfile with zipfile.ZipFile(travel-system.zip) as zf: for info in zf.infolist(): print(info.filename, info.file_size)遇到伪加密infolist()能列出真实文件清单之后可以换用支持忽略标志位的解压工具处理。要区分的是如果数据是真的用密码加密过那说明发布者有意保护内容直接放弃这个来源不要尝试任何绕过手段。5.2 数据库路径和字符集SQLite 连不上的一半原因现象init_db.py执行时报sqlite3.OperationalError: unable to open database file或者服务启动后页面报 500日志里提示no such table: rating。原因源码里用了相对路径travel.db而脚本的工作目录和当前启动目录不一致。你从项目根目录执行python init_db.py没问题但用 IDE 的调试功能或从其他目录调用脚本时相对路径指向的位置就变了。SQLite 找不到文件时不会报“文件不存在”而是报unable to open database file非常具有误导性。解决把数据库路径改成基于脚本文件位置的绝对路径这是最简单的后悔药import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) DB_PATH os.path.join(BASE_DIR, travel.db)app.py和init_db.py里凡是打开数据库的地方统一用DB_PATH不要再依赖“当前工作目录”。5.3 UnicodeDecodeError 血泪Windows 下 gbk 和 utf-8 打架现象init_db.py读取users.csv时报UnicodeDecodeError: gbk codec cant decode byte或者数据导进去了网页上景点名显示乱码。原因源码包的数据文件多为 UTF-8 编码而 Windows 中文系统的 Python 默认用 GBK 打开 CSV两个编码对不上读取到中文字符的高位字节时就炸了。乱码则是另一种情况数据以 UTF-8 存入但 Flask 模板或浏览器按 GBK 解读或者反过来。解决所有 CSV 读取统一显式指定encodingutf-8不要依赖系统默认。写入时如果想用 Excel 打开不乱码用utf-8-sigratings.to_csv(data/ratings_clean.csv, indexFalse, encodingutf-8-sig)utf-8-sig会在文件开头写入 BOM 头Excel 识别后按 UTF-8 解析而普通 UTF-8 的 CSV 在 Excel 里经常被误判为 ANSI 而显示乱码。用代码读回时仍然指定encodingutf-8因为带 BOM 的 UTF-8 对 Python 来说没有兼容问题utf-8就能正确读取。5.4 推荐结果为空的冷启动现场现象服务正常启动页面能打开但输入新注册的用户 ID 后推荐列表为空后端日志没有报错。原因冷启动。新用户没有评分记录user_sim.loc[user_id]取出来的全是 NaNsort_values后head(k)拿到空集候选景点被过滤完函数返回空列表。这属于系统设计缺陷而不是代码 bug——任何协同过滤算法都无法为无历史用户做个性化推荐。解决在app.py的推荐接口里加一个评分数量判断不足阈值就走热度榜回退if ratings[ratings[user_id] user_id].shape[0] 3: # 评分少于 3 条按城市热度榜推荐 result hot_recommend(user_id, cityuser_city, top_n10) else: result recommend_for_user(user_id, top_n10, k20)阈值取 3 是经验值少于 3 条评分的相似度矩阵行太稀疏算出来的邻居基本是噪声。回退到热度榜时加上城市过滤新用户至少能看到“本地热门景点”这种有解释力的结果。5.5 同样的代码换台机器结果不一样版本与顺序问题现象在 A 机器上跑推荐结果稳定换到 B 机器后同一个用户 ID 的推荐列表发生了变化或者推荐顺序和之前不同。原因两种情况最常见。一是 Pandas 或 Python 版本不一致groupby的聚合顺序、浮点运算精度在不同版本里可能有细微差别排序时这些微小差异被放大。二是种子未固定如果源码里有随机采样或模型初始化比如用sklearn的某些模块未设随机种子时每次运行结果都会变。解决在cf.py顶部固定随机种子同时记录依赖版本import numpy as np import pandas as pd np.random.seed(42) # 保证随机过程可复现再精确一点的验证把两次运行的pred结果用np.isclose比较而不是直接判断列表相等——浮点推荐分数本来就允许有 1e-6 量级的抖动只要 TopN 的景点集合和顺序一致就算通过。如果版本差异过大直接按requirements.txt的版本重新建环境不要在旧环境上硬凑。6. 换成自己的数据跑一遍离线评测和冷启动补丁源码包自带的景点数据通常是北京、杭州或成都的示例集换成你自己的城市数据才算真正把这个系统变成能用、敢交出去的东西。数据替换的要点不在算法而在数据清洗和字段映射。先把你的景点数据整理成三个 CSV字段和第二章的数据字典严格对齐。景点表scenic.csv必须包含scenic_id、name、city、category四列价格和评分可为空评分表ratings.csv必须包含user_id、scenic_id、rating、ts四列其中ts统一成YYYY-MM-DD HH:MM:SS格式。清洗时注意同一景点在不同来源数据里名称可能不一致比如“西湖风景区”和“西湖”要预先做名称归一化否则协同过滤会把它们当成两个景点评分散裂。冷启动补丁在实践中比算法优化更常用。新用户没有任何评分时单独写一个兜底函数按城市和热度综合排序def cold_start_recommend(ratings, scenics, city, top_n10): merged ratings.merge(scenics[[scenic_id, city]], onscenic_id) sub merged[merged[city] city] stats sub.groupby(scenic_id)[rating].agg([mean, count]) # 平滑分样本量越大越可信count 5 是平滑系数 stats[score] stats[mean] * stats[count] / (stats[count] 5) return stats.sort_values(score, ascendingFalse).head(top_n).index.tolist()要验证这套改动到底有没有提升推荐质量离线评测是必须的一步。把评分数据按 8:2 切分训练集和测试集只在训练集上算相似度对测试集中的每个用户做 Top10 推荐统计推荐结果命中测试集真实景点的比例from sklearn.model_selection import train_test_split train, test train_test_split(ratings, test_size0.2, random_state42) # 用 train 重新构造 user_sim再为每个测试用户生成 top10 推荐 hit 0 total 0 for user_id in test[user_id].unique(): rec recommend_for_user(user_id, top_n10, k20) real set(test[test[user_id] user_id][scenic_id]) hit len(set(rec) real) total len(rec) print(fPrecision10 {hit / total:.3f})我自己的习惯是改任何参数之前先跑一次这个脚本记录基线 Precision每调一个 K 或中心化改动再跑一次对比。没有基线的调参全是玄学有了基线才能区分改动是真实提升还是随机波动。希望这个流程能帮你在自己的数据和源码包之间搭起一座可靠的桥少走我当年走过的弯路。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询