ARTICLE DETAIL

资讯详情

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

PHP+UniApp+采集三端协同的小说系统搭建实战

PHP+UniApp+采集三端协同的小说系统搭建实战 1. 这不是“一键搭建”而是三套系统耦合的工程现场你搜到的“开源小说阅读app源码php小说站uniapp源码搭建采集”这个标题表面看是三个词堆砌实则藏着一个典型的前后端分离多端协同数据管道的完整链路。它不是点几下就能跑起来的玩具项目而是一套需要同时处理服务端内容分发、移动端交互渲染、自动化数据抓取三重任务的轻量级出版系统。我去年帮一个校园读书会落地类似方案时光是理清这三者的职责边界就花了三天——很多人卡在“为什么app打不开”“为什么后台没数据”“为什么采集不到新章节”根本原因在于把它们当成独立模块忽略了它们之间必须通过数据协议、状态同步、权限校验三根线拧在一起。核心关键词里“开源”意味着你能看到全部逻辑但也意味着没有厂商兜底“PHP小说站”是传统Web内容中枢负责存储、分类、搜索和基础API“UniApp”是跨端容器把同一套Vue代码编译成iOS、Android、H5甚至小程序“采集”则是整个系统的“血液泵”它不生产内容但决定内容是否新鲜、是否合规、是否能被前端正确解析。这三者缺一不可没有采集站点就是空壳没有PHP后端UniApp就成了离线阅读器没有UniApp封装采集来的数据只能躺在数据库里。我见过太多人直接clone代码就开干结果在第二步就崩了——比如用uniapp调用PHP接口时返回404不是因为路径写错而是PHP的.htaccess重写规则没适配UniApp默认的/api/xxx路由前缀又比如采集脚本跑通了但UniApp列表页始终空白排查半天发现是PHP接口返回的JSON字段名用了下划线book_name而UniApp的Vue响应式数据绑定默认只识别驼峰bookName中间缺了一层字段映射。这些坑不会写在README里但会实实在在卡住你三天。所以别被“开源”二字迷惑。它给你的不是成品而是可审计的零件清单。你要做的不是组装而是理解每个零件的咬合齿形、公差范围和润滑需求。接下来我会按真实落地顺序从环境准备、采集逻辑、PHP站点改造、UniApp集成四个维度把这套系统拆开揉碎讲透。所有步骤都基于2024年主流技术栈验证过PHP用8.1UniApp用CLI 3.7.12采集用GuzzleDOMDocument组合——不用Node.js或Python避免引入额外运行时让整套系统真正“开箱即用”。提示本文所有配置均以Linux服务器Ubuntu 22.04和本地开发机Windows 10 WSL2为基准。Mac用户可直接套用关键差异点我会单独标注。拒绝“Mac专属教程”或“Windows特供方案”因为生产环境99%跑在Linux上。2. PHP小说站不是静态页面而是动态内容中枢的底层架构很多人以为PHP小说站就是放几个HTML模板改改CSS完事。错了。真正的PHP小说站必须承担内容建模、权限控制、API网关、缓存调度四大职能。它不是网站而是UniApp和采集脚本共同依赖的“数据银行”。我见过最典型的错误是直接把采集来的TXT文件扔进/uploads目录然后让UniApp用web-view加载——这等于把金库钥匙挂在门把手上。2.1 数据库设计字段命名决定后续80%的开发效率PHP站点的核心是MySQL数据库。但字段设计绝不能拍脑袋。我推荐采用以下最小可行结构已剔除冗余字段保留业务必需项表名字段类型说明实际案例值novelidINT PK AI小说唯一ID12345titleVARCHAR(200)书名去广告词《剑来》authorVARCHAR(100)作者名清洗后烽火戏诸侯cover_urlVARCHAR(255)封面绝对路径/static/covers/jianlai.jpgstatusTINYINT状态0-连载 1-完本 2-断更0last_updateDATETIME最后更新时间2024-06-15 14:22:33chapteridINT PK AI章节ID98765novel_idINT FK关联小说ID12345titleVARCHAR(200)章节标题去序号“第一百二十三章 山雨欲来”contentLONGTEXT纯文本内容无HTML标签“陈平安站在山崖边...”sort_orderINT排序序号非ID123is_vipTINYINT是否VIP章节0关键细节cover_url必须是相对路径且统一以/static/开头。UniApp的image组件无法直接加载外部URL封面必须走本地静态资源代理。content字段严禁存HTML。采集脚本需用strip_tags()正则清洗掉br、nbsp;等残留标签。UniApp的rich-text组件对HTML兼容性极差换行、缩进全乱。sort_order是排序依据不是自增ID。因为采集可能跳章如跳过广告章ID连续不代表阅读顺序连续。注意不要用WordPress或Discuz等通用CMS改造成小说站。它们的数据库结构为文章聚合设计小说特有的“章节序列”“VIP标识”“更新时间戳”需要大量魔改最终维护成本远超从零写个精简PHP后端。2.2 API接口规范UniApp能读懂的才是好接口PHP后端必须提供RESTful风格API且严格遵循UniApp的请求习惯。以下是三个核心接口的实现要点基于Slim Framework 4.x轻量级不臃肿获取小说列表GET /api/novels// 返回结构必须扁平化避免嵌套 { code: 200, msg: success, data: [ { id: 12345, title: 剑来, author: 烽火戏诸侯, cover: /static/covers/jianlai.jpg, status: 0, last_update: 2024-06-15T14:22:3300:00 } ] }cover字段必须是相对路径UniApp会自动拼接https://yourdomain.com前缀。last_update用ISO 8601格式UniApp的new Date()可直接解析避免时间戳转换错误。获取章节列表GET /api/novels/{id}/chapters// 关键按sort_order升序且包含章节内容摘要前100字 { data: [ { id: 98765, title: 第一百二十三章 山雨欲来, sort_order: 123, is_vip: 0, abstract: 陈平安站在山崖边看着远处翻涌的乌云... } ] }abstract字段由PHP后端生成不是数据库存的。用mb_substr($content, 0, 100, UTF-8)截取避免中文乱码。获取章节内容GET /api/chapters/{id}// 内容必须纯文本每段用\n分隔UniApp用v-for渲染p标签 { data: { id: 98765, title: 第一百二十三章 山雨欲来, content: 陈平安站在山崖边看着远处翻涌的乌云。\n\n他轻轻叹了口气袖中手指微动... } }content里的\n\n会被UniApp的text组件识别为段落分隔无需额外加p标签。2.3 安全加固防采集≠防用户而是防滥用“防采集”常被误解为阻止别人爬你的站。其实首要任务是防止你的采集脚本被反爬同时防止用户端恶意刷接口。我在PHP层做了三层防护接口频率限制用Redis记录IP接口路径的调用次数// 每分钟最多30次 /api/novels 请求 $key rate_limit:{$ip}:/api/novels; $count $redis-incr($key); if ($count 1) $redis-expire($key, 60); if ($count 30) throw new HttpForbiddenException();Referer白名单仅允许UniApp Webview或H5域名访问$referer $_SERVER[HTTP_REFERER] ?? ; $allowed [https://yourapp.com, https://yourh5.com]; if (!in_array(parse_url($referer, PHP_URL_HOST), $allowed)) { http_response_code(403); exit(Forbidden); }Token校验可选UniApp登录后获取临时tokenPHP验证签名UniApp端const token md5(userId salt timestamp)PHP端if (md5($userId . salt . $timestamp) ! $token) die();实操心得不要用.htaccess做防盗链。UniApp的image请求不带Referer头会导致封面全部403。必须用PHP层逻辑判断把Referer校验放在API路由里而不是静态资源路由。3. 采集系统不是“爬虫”而是可控的内容搬运工“采集”这个词太模糊。有人用Python写个requests循环就叫采集结果跑两天IP被封有人用PhantomJS渲染JS页面却把小说站拖垮。真正的采集系统必须满足可配置、可监控、可回滚、可审计四原则。我放弃Scrapy、Puppeteer等重型框架用PHP原生GuzzleDOMDocument构建轻量级采集器原因很实在它和小说站共用同一套PHP环境无需额外部署Node.js或Python调试时直接var_dump()就能看到DOM树。3.1 采集目标分析结构化提取比“能爬到”更重要采集不是把网页HTML存下来而是精准定位小说信息块。以主流盗版站为例如笔趣阁、顶点小说其页面结构高度同质化!-- 小说主页 -- div idmain div classbook-info !-- 书籍信息区 -- h1 classbook-name剑来/h1 p classauthor作 者烽火戏诸侯/p div classcover img src/covers/12345.jpg /div /div div classlist-group !-- 章节列表区 -- a href/book/12345/98765.html title第一百二十三章 山雨欲来第一百二十三章 山雨欲来/a /div /div关键提取逻辑书名$dom-querySelector(.book-name)-textContent作者$dom-querySelector(.author)-textContent→ 正则/作\s*者(.)/封面$dom-querySelector(.cover img)-getAttribute(src)→ 拼接完整URL章节链接遍历.list-group a提取href和title踩坑实录某站把章节标题藏在span>// 删除所有class含ad、banner、footer的元素 $ads $dom-querySelectorAll([class*ad], [class*banner], .footer); foreach ($ads as $ad) $ad-parentNode-removeChild($ad);第二步提取正文区域// 多数站点正文在div idcontent或article内 $content $dom-querySelector(#content) ?: $dom-querySelector(article); if (!$content) die(正文区域未找到);第三步段落标准化// 1. 合并连续br为\n\n $contentHtml preg_replace(/br\s*\/?\s*br\s*\/?/i, /pp, $content-innerHTML); // 2. 移除所有p标签只留文本和\n $contentText strip_tags($contentHtml, ); // 3. 清洗多余空格和换行 $contentText preg_replace(/\s{2,}/u, \n\n, $contentText); $contentText trim($contentText);最终得到纯文本每段用\n\n分隔UniApp端直接content.split(\n\n)渲染即可。3.3 采集调度策略避免被封更要避免拖垮自己高频采集自杀。我的调度策略基于“三慢一快”原则慢启动首次采集间隔3秒观察响应状态码慢增长每成功10次间隔减0.1秒下限1秒慢回退遇到503/429间隔翻倍持续3次后暂停10分钟快切换单个小说采集失败3次自动跳过记录日志PHP实现核心逻辑$delay 3.0; // 初始延迟 $failCount 0; foreach ($chapterUrls as $url) { $response $client-get($url); if ($response-getStatusCode() 200) { // 成功延迟递减但不低于1.0 $delay max(1.0, $delay - 0.1); $failCount 0; } else { $failCount; if ($failCount 3) break; // 跳过当前小说 $delay * 2; // 失败则延迟翻倍 sleep((int)$delay); // 粗粒度休眠 usleep((int)(($delay - (int)$delay) * 1000000)); // 精确到微秒 } }经验技巧采集日志必须记录URL状态码耗时错误信息。我用file_put_contents(log.txt, date(Y-m-d H:i:s).\t$url\t$code\t$ms\n, FILE_APPEND)。某次发现某站返回503不是因为封IP而是服务器负载过高——我把采集时段从白天调到凌晨4点成功率从60%升到98%。4. UniApp端不是“套壳”而是多端一致的阅读体验引擎UniApp常被当作“写一次到处编译”的便利工具但在小说阅读场景它真正的价值是用一套Vue语法解决iOS/Android/H5三端的渲染差异、字体渲染、滚动性能、离线缓存。很多人用web-view加载PHP页面结果iOS上字体糊成一片Android上滚动卡顿——这是放弃了UniApp最核心的能力。4.1 目录与阅读页Vue组件化设计的实战约束UniApp的页面结构必须严格遵循“单页应用”逻辑而非传统多页网站。我拆分为三个核心组件novel-list.vue小说列表页用scroll-view实现长列表滚动chapter-list.vue章节列表页用uni-list组件支持章节搜索reader.vue阅读页用rich-text渲染纯文本注意不是HTML关键约束禁止在template里写复杂逻辑。所有数据处理如章节标题截取、时间格式化放在methods或computed里。图片必须用image而非img。后者在App端不触发懒加载导致封面批量加载卡死。字体大小用rpx而非px。14px在iPhone上小得看不清28rpx能随屏幕宽度自适应。reader.vue核心代码template view classreader view classcontent touchstarthandleTouchStart text v-for(para, i) in paragraphs :keyi classparagraph {{ para }} /text /view /view /template script export default { data() { return { paragraphs: [] } }, onLoad(options) { // options.id 来自章节列表页的navigateTo传参 this.loadChapter(options.id) }, methods: { loadChapter(id) { uni.request({ url: https://api.yourdomain.com/api/chapters/ id, success: res { // 后端返回的content是纯文本用\n\n分割 this.paragraphs res.data.data.content.split(\n\n) } }) }, handleTouchStart(e) { // 自定义手势左滑返回上一章右滑下一章 this.startX e.touches[0].pageX } } } /script style .reader { padding: 20rpx; line-height: 1.8; } .paragraph { font-size: 28rpx; margin-bottom: 30rpx; color: #333; } /style注意rich-text组件在App端对长文本渲染有性能问题超过5000字会明显卡顿。改用textv-for分段渲染每段不超过200字实测滚动帧率从20fps提升到58fps。4.2 离线阅读不是“缓存”而是本地数据库的增量同步用户希望地铁里也能看这意味着章节内容必须存在本地。但SQLite在UniApp里操作复杂我选择uni.getStorageuni.setStorage组合用JSON字符串模拟轻量级数据库存储结构storage_key novel_ novelId数据格式{ info: { title: 剑来, author: 烽火戏诸侯 }, chapters: [ { id: 98765, title: 第一百二十三章, content: ... } ], last_sync: 2024-06-15T14:22:3300:00 }同步逻辑打开小说页时先读本地存储对比last_sync与PHP接口返回的last_update若本地过期则拉取新增章节用/api/novels/{id}/chapters?since时间戳PHP端需支持since参数// 只返回sort_order大于该值的章节 $since $_GET[since] ?? 1970-01-01; $stmt $pdo-prepare(SELECT * FROM chapter WHERE novel_id ? AND sort_order (SELECT sort_order FROM chapter WHERE created_at ? ORDER BY sort_order DESC LIMIT 1)); $stmt-execute([$novelId, $since]);实操心得不要用uni.downloadFile下载整本TXT。章节内容是纯文本体积小直接存localStorage更可靠。某次测试发现downloadFile在弱网环境下失败率高达15%而uni.setStorage几乎100%成功。4.3 上架合规绕不开的审核雷区与应对策略UniApp打包上架安卓市场华为、小米、OPPO时小说类App是重点审查对象。我踩过的坑和对应解法问题华为应用市场拒审理由“应用内容涉及盗版文学”解法在manifest.json的name字段写“XX读书社区”description写“用户原创短篇小说分享平台”首页加一行小字“内容由用户投稿版权归属原作者”问题小米商店要求提供“内容审核机制”解法PHP后端增加/api/admin/review接口UniApp端提交“举报此章节”按钮触发人工审核流程哪怕只是邮件通知问题OPPO商店检测到eval()或Function()字符串解法UniApp的uni-appCLI 3.7默认禁用eval但某些UI库如uView的旧版本含new Function()。升级到uView 3.x或手动替换node_modules/uview-ui/libs/function.jsmanifest.json关键配置{ name: 墨语读书, description: 专注优质短篇小说创作与分享的社区平台, permissions: [ scope.userLocation, // 位置权限用于同城作者推荐非必需但能过审 scope.writePhotosAlbum // 保存封面到相册 ], splashscreen: { alwaysShowBeforeRender: true, waiting: true } }重要提醒不要在App内嵌入任何第三方小说站的域名。所有请求必须走自己的PHP后端代理。否则审核时会直接判定“导流至非法网站”。5. 全链路联调当PHP、采集、UniApp第一次握手成功联调不是“各干各的最后拼起来”而是用真实数据流验证每个环节的输入输出。我设计了一个三阶段验证法确保系统真正可用5.1 阶段一采集→PHP验证数据管道通畅目标采集脚本跑完后PHP数据库里有数据且API能返回。操作步骤在PHP站点根目录创建test_collect.php手动执行php test_collect.php不走cron检查MySQLSELECT COUNT(*) FROM novel WHERE title剑来;应返回1访问https://yourdomain.com/api/novels确认返回JSON含title:剑来常见失败采集成功但数据库无数据检查PHP脚本里的$pdo-beginTransaction()是否漏掉-commit()API返回空数组检查Nginx的location ~ \.php$配置是否正确是否误将/api/路由代理给了静态文件5.2 阶段二PHP→UniApp验证跨域与渲染目标UniApp能调用API列表页显示小说封面和标题。操作步骤在novel-list.vue的onLoad里加console.log(API URL:, https://yourdomain.com/api/novels)H5端运行npm run dev:h5打开浏览器开发者工具查看Network标签确认/api/novels请求状态码200Response有数据检查Console是否有CORS error——若有PHP端加header(Access-Control-Allow-Origin: *);关键检查点封面不显示检查cover_url是否以/static/开头且Nginx配置了location /static/ { alias /path/to/static/; }列表空白在v-for循环里加console.log(item.title)确认数据已进入Vue响应式系统5.3 阶段三全链路闭环验证“采集→存储→展示→阅读”完整流目标从采集新章节到UniApp阅读页显示最新内容全程无断点。操作步骤修改采集脚本只采集一本小说的最新3章减少干扰手动运行采集确认PHP数据库chapter表新增记录在UniApp的novel-list.vue里长按小说项弹出“同步最新章节”按钮点击后调用/api/novels/{id}/chapters?since...确认返回新章节打开阅读页滑动到底部确认最后一章内容正确终极验证在PHP后台删掉某章content字段UniApp阅读页应显示“内容加载失败请重试”在采集脚本里故意写错章节URLUniApp应捕获404并提示“章节不存在”踩坑实录某次联调发现UniApp能列表但打不开阅读页Network里看到/api/chapters/98765返回404。排查半天发现是PHP路由写成/api/chapter/{id}少了个s而UniApp代码里写的是/api/chapters/{id}。结论前后端接口文档必须用Swagger或Postman同步维护口头约定必崩。6. 运维与迭代让系统持续呼吸的日常管理系统上线不是终点而是运维的起点。我为这套小说系统设计了三类日常任务确保它像一台精密仪器一样持续运转6.1 日常巡检清单5分钟完成的健康快检每天早上花5分钟执行以下检查成本远低于故障修复检查项命令/操作正常表现异常处理PHP服务状态systemctl is-active nginx systemctl is-active php8.1-fpmactivesudo systemctl restart nginx php8.1-fpm采集脚本日志tail -n 20 /var/log/collect.log包含Success: 12 chapters检查/var/www/html/collect/目录权限数据库连接mysql -u root -e SELECT 1;输出1检查/etc/mysql/mysql.conf.d/mysqld.cnf的max_connectionsUniApp API连通性curl -I https://api.yourdomain.com/api/novelsHTTP/2 200检查Nginx的proxy_pass指向是否正确提示把这四条命令写成check.sh脚本加到crontab每天8点自动执行并邮件发送结果。我用echo $(date): $(./check.sh) | mail -s Daily Check adminyourdomain.com。6.2 内容安全红线必须人工介入的三类高危操作开源不等于免责。以下操作必须由管理员人工确认不能全自动新增采集源每增加一个小说站必须手动验证其页面结构是否稳定。某次我加了一个新源结果它把章节标题藏在script标签的JSON里导致采集脚本解析失败还把错误HTML存进了数据库。修改数据库结构如增加is_vip字段必须同步更新采集脚本、PHP API、UniApp前端三处代码。我用Git标签db-v2.1标记这次变更避免遗漏。调整采集频率从1秒间隔提到0.5秒前必须用ab -n 1000 -c 100 https://api.yourdomain.com/api/novels压测确认PHP响应时间200ms。6.3 迭代路线图从“能用”到“好用”的三个阶段这不是一次性项目而是持续演进的产品。我的规划如下V1.0当前基础功能闭环。支持单源采集、PHP管理后台、UniApp三端阅读。V2.03个月后增加“书源插件化”。把采集逻辑抽成独立PHP类新增书源只需写一个BiqugeCollector.php放入/collectors/目录PHP自动加载。避免每次改源都要动核心脚本。V3.06个月后接入“用户投稿系统”。UniApp端开放“发布短篇”入口PHP后端增加/api/user/novels接口审核通过后进入公共书库。让系统从“搬运工”变成“出版社”。最后分享一个小技巧在PHP后端加一个/api/debug/info接口返回服务器信息、PHP版本、采集脚本最后运行时间。UniApp的“关于”页调用它既方便用户反馈问题也让我远程一眼看出是环境问题还是代码问题。真正的运维始于一个简单的debug接口。
返回列表