ARTICLE DETAIL

资讯详情

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

一套PHP源码搞定公众号排版与文章采集:架构与实战解析

一套PHP源码搞定公众号排版与文章采集:架构与实战解析 简介面向公众号运营者、新媒体编辑和初中级PHP开发者这份源码经二次封装后可直接用于搭建公众号文章编辑排版平台解决手动排版效率低、模板样式单一等痛点。核心功能包括内容采集模块与多种预设样式可快速抓取指定页面素材并一键套用版式用户无需从零设计即可生成风格统一的图文页面。压缩包共1355个文件以PHP后台逻辑、JS交互脚本、CSS样式表、HTML结构模板为主并配大量GIF动态演示和PNG界面图标其中GIF多为操作演示PNG用于界面设计参考JS与CSS负责动态效果和视觉表现整体大小63.41MB。包内另附视频使用教程从环境部署到功能操作均有演示适合零基础学习者快速上手。目前已有445人学习下载。通过实际阅读源码读者能掌握文章采集接口的编写思路、前端样式渲染的调度方式以及模板数据绑定流程也可抽取其中样式资源迁移至个人项目显著减少公众号排版耗时同时为二次开发提供完整参考。1. 一套PHP源码能同时解决排版和采集先看清它的定位这套源码要解决的是公众号运营里两个最常被放在一起说的需求把文章排版得像样以及把别处的文章内容搬过来二次加工。它用PHP把「排版编辑器」和「采集器」装进同一个后台后端抓到的正文可以直接送进前端排版模板不用手动复制粘贴。适合两类人一类是单兵作战的新媒体编辑想摆脱在网页编辑器里反复调格式、调间距的重复劳动另一类是接外包的PHP开发者需要给客户交付一个带采集能力的公众号内容后台。要提醒的是采集能力针对的是单篇文章链接层面的正文抓取不涉及登录公众号后台也不存在对平台接口的逆向。内容合规这件事要自己把关采集回来的素材只建议用于你有权使用的范围。2. 排版工具与采集功能的架构关系两条业务线怎么合到一起2.1 前端排版页与后端采集任务是两套入口打开这个后台你会看到两个完全不同的页面形态。排版页是编辑器界面左侧是标题、作者、封面图输入框右侧是正文的可视化预览区域你可以在里面调整字号、行距、段间距、高亮颜色甚至插入分隔线和二维码组件。采集页则是另一个入口通常只有一个输入框和一个按钮粘贴微信公众号文章的链接点一下「采集」等上两三秒标题和正文就被填进排版页的草稿箱里。这两套入口在后端是分开处理的。排版页读的是草稿表字段大致是这样CREATE TABLE article_draft ( id int(11) unsigned NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL DEFAULT , author varchar(100) NOT NULL DEFAULT , cover varchar(500) NOT NULL DEFAULT , content_html mediumtext NOT NULL, source_url varchar(500) NOT NULL DEFAULT , status tinyint(4) NOT NULL DEFAULT 0, created_at int(11) NOT NULL DEFAULT 0, updated_at int(11) NOT NULL DEFAULT 0, PRIMARY KEY (id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;采集接口拿到文章后把解析出的标题、作者、正文HTML写进这张表status置为草稿排版页再从这张表把content_html捞出来渲染成可编辑的富文本。两套功能共用一个数据表这是这类源码最常见的做法好处是逻辑简单不会出现「采集好的文章在排版页找不到」的窘境。2.2 采集核心原理微信文章没有开放接口走的是一条HTML抓取路线微信公众平台从来没有开放过「把文章正文给我的服务器」这类接口。所有声称能采集公众号文章的工具底层都离不开同一个动作对文章链接发一次普通的HTTP请求拿到服务器返回的HTML再从HTML里把正文节点抠出来。微信公众号文章链接长这样https://mp.weixin.qq.com/s?__bizMzA5MjMxMjEwNQmid2654789012idx1sn3e9b6d2c9e6f4a1c对PHP的curl来说这只是一个带了一串查询参数的GET请求跟访问普通网页没有区别。文章正文通常被包在一个idjs_content的div里里面是经过排版的p、section、img标签。采集器要做的就是把这个节点连同一个classrich_media_title的标题节点一起取出来再按你的规则清洗一遍去掉脚本、去掉隐藏元素、把图片下载到本地。需要强调的是这条路能通的前提是文章链接是公开可访问的。如果链接里带着需要登录态才能访问的参数或者文章被删除、被设置成仅粉丝可见那任何PHP采集脚本都拿不到内容。遇到这种情况源码通常会返回一个「采集失败链接不可访问」的错误这属于正常现象不是代码坏了。2.3 PHP在那个年代选型为什么够用并发不高但够单人维护很多做Java、Python的人会问为什么这类工具还是用PHP写。原因很现实这类源码的目标运行环境是虚拟主机或低配云服务器PHP不用常驻进程一个请求来了启动、干完活结束生命周期干净利落。采集任务是串行的一次只处理一篇文章吞吐量要求极低PHP的curl扩展完全够用。另一个原因是维护成本。排版工具本质上是给运营人员用的后台PHP配MySQL的「所见即所得」式管理后台开发效率极高。改一个模板文件刷新浏览器就生效不需要重新编译、不需要重启服务。对比用Node.js或Python写PHP在这类低频管理型工具上的综合成本最低。如果你预期一天要采集成千上万篇文章或者要做高并发的排版服务那这套PHP架构确实不是最优解但它是「一个人就能维护、虚拟主机就能跑」的最轻方案。3. 本地部署这套源码从环境检查到浏览器打开的最小流程3.1 开始前的环境体检PHP版本、扩展与目录权限压缩包里通常带了完整的目录结构和一份install.sql数据库脚本但别急着解压导入。先用命令行确认运行环境避免后面出现「装好了但白屏」这种最打击人的状况。在服务器或本地集成环境里执行php -v php -m | grep -E curl|pdo_mysql|mbstring|openssl|gd第一行看PHP版本置信区间是7.x以上。php -m列出已加载的扩展grep过滤出这五个关键扩展。curl负责抓取文章pdo_mysql负责连数据库mbstring负责中文编码转换openssl负责HTTPS链接的SSL握手gd负责封面图的裁剪和缩略图生成。只要缺了其中一个采集或排版都会在运行中途翻车。然后检查目录权限。这套源码的uploads目录图片落地、cache目录模板缓存、logs目录运行日志需要可写权限。用下面命令把权限放宽注意生产环境按最小权限收紧chmod -R 755 /path/to/wechat_editor chmod -R 777 /path/to/wechat_editor/uploads chmod -R 777 /path/to/wechat_editor/cache chmod -R 777 /path/to/wechat_editor/logs提示logs目录里如果出现php_error.log第一条排错线索就翻它比猜原因快得多。3.2 数据库初始化与核心配置conf/config.php 里必须改的五个值解压后先找conf/config.php这是全站唯一的配置入口。把数据库连接信息改成你自己的下面五个值是最低要求?php // conf/config.php 部分内容 define(DB_HOST, 127.0.0.1); // 数据库地址 define(DB_NAME, wechat_editor); // 数据库名 define(DB_USER, root); // 数据库账号 define(DB_PASS, your_password); // 数据库密码 define(BASE_URL, https://editor.example.com); // 后台对外访问地址DB_HOST在本地部署填127.0.0.1云服务器上填内网地址更安全。BASE_URL容易被忽略它决定后台生成的资源链接和采集回调地址填错会导致排版页图片加载不出来。数据库名要提前建好字符集用utf8mb4否则存emoji表情会变成问号。初始化数据库用命令行导入脚本mysql -uroot -p wechat_editor install.sqlinstall.sql里除了建表通常还会插入一条默认管理员账号。安装完成后第一步就是登录后台改掉默认密码这一步别偷懒网上扫描默认密码的工具一大堆这是「血泪经验」。3.3 启动与自检确认排版页和采集入口都通完成配置后访问BASE_URL对应的域名能看到登录页就说明PHP和Web服务器已经通了。然后进后台按视频教程里的演示路径点一遍新建文章 → 填标题正文 → 保存草稿。保存后刷新列表页如果刚保存的文章出现在列表里说明数据库读写正常。接着测采集入口。找一个你自己公众号的文章链接粘贴进去点采集。成功的标志是标题和正文自动出现在排版页配图正常显示。如果采集失败先看logs目录下的curl_error.log里面记录的是curl返回的错误码比如Could not resolve host说明DNS有问题Operation timed out说明对方服务器响应慢。提示视频教程会把后台界面完整点一遍但部署前的环境体检和数据库配置它默认你已经做完了这两步才是决定能不能跑起来的关键。4. 把采集功能真正用起来抓取、清洗、转存三步代码解剖4.1 入口函数 fetch_article_htmlUA、超时与重试采集模块的核心是一个叫fetch_article_html的函数它负责把文章URL变成原始HTML。微信服务器对请求头里的User-Agent很敏感默认的PHP UA会被直接拒绝所以必须伪装成浏览器。下面是这类源码里最常见的一段实现function fetch_article_html($url, $timeout 15) { $ua Mozilla/5.0 (Windows NT 10.0; Win64; x64) . AppleWebKit/537.36 (KHTML, like Gecko) . Chrome/120.0.0.0 Safari/537.36; $ch curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER true, CURLOPT_FOLLOWLOCATION true, CURLOPT_MAXREDIRS 3, // 最多跟随3次重定向 CURLOPT_TIMEOUT $timeout, // 整体超时15秒 CURLOPT_CONNECTTIMEOUT $timeout, // 连接超时15秒 CURLOPT_USERAGENT $ua, CURLOPT_SSL_VERIFYPEER false, // 本地测试用生产建议改true CURLOPT_ENCODING gzip, deflate, // 微信会返回压缩内容 ]); $html curl_exec($ch); if (curl_errno($ch)) { error_log([curl error] . curl_error($ch)); curl_close($ch); return false; } curl_close($ch); return $html; }这里几个参数是多年踩坑换来的CURLOPT_ENCODING必须设置微信服务器默认返回gzip压缩过的HTML不设置会拿到一堆乱码CURLOPT_FOLLOWLOCATION配合CURLOPT_MAXREDIRS 3防止微信把链接302跳转到登录页时让脚本无限循环CURLOPT_SSL_VERIFYPEER false在本地调试时可以省去配CA证书的麻烦但放到生产环境建议改成true并加载正规CA证书否则有中间人攻击风险。拿到HTML后先别急着解析检查返回内容里是否有rich_media_content字样。没有的话大概率是微信返回了一个验证页这时候需要把$html前500个字符写进日志人工确认触发原因而不是盲目调参重试。4.2 正文清洗去掉导航、脚本和个性化推荐采集回来的HTML是个完整网页里面除了正文还有页面顶部导航、底部推荐阅读、脚本文件引用。直接入库会让排版页乱成一锅粥。清洗这步用DOMDocument比正则更可靠因为正则处理嵌套标签容易翻车。核心逻辑是先定位正文节点再递归删除不需要的子节点function extract_article_body($html) { $doc new DOMDocument(); // 微信正文是UTF-8必须做编码声明转换 $doc-loadHTML(mb_convert_encoding($html, HTML-ENTITIES, UTF-8)); $xpath new DOMXPath($doc); // 优先按ID定位微信改版后ID仍保留 $nodes $xpath-query(//*[idjs_content]); if ($nodes-length 0) { // 降级按class定位 $nodes $xpath-query(//div[contains(class,rich_media_content)]); } if ($nodes-length 0) { return ; } $body $nodes-item(0); // 移除脚本、样式、iframe、隐藏元素 foreach ($xpath-query(.//script | .//style | .//iframe, $body) as $node) { $node-parentNode-removeChild($node); } foreach ($xpath-query(.//*[styledisplay:none], $body) as $node) { $node-parentNode-removeChild($node); } // 清除正文里残留的推荐位区块 foreach ($xpath-query(.//*[contains(class,recommend)], $body) as $node) { $node-parentNode-removeChild($node); } return $doc-saveHTML($body); }这里最关键的降级策略微信前端改版不是一次两次了js_content这个ID从公众号诞生起就存在但谁也不敢保证未来不变。所以用DOMXPath查询时先查ID查不到再按rich_media_content这个class查双保险。符号抑制loadHTML对不规范HTML产生的警告因为微信返回的HTML不一定通过W3C校验警告日志会被刷爆。清洗后的saveHTML返回的字符串里可能还带着body标签或多余的空格。入库前再用trim和strip_tags做一次兜底。用正则只保留p、section、img、h1~h3、blockquote这些排版会用到的标签是保守但可靠的做法。4.3 图片本地化防盗链和临时链接过期都靠这一步微信正文里的图片域名是mmbiz.qpic.cn这些图片有两个特性一是带防盗链直接外链到你自己网站会显示裂图二是临时链接有时效过几天就失效。所以采集之后必须立刻把图下载到本地服务器并替换img标签里的src地址。这段代码是采集功能里最容易出问题的地方function localize_images($html, $save_dir) { preg_match_all(/img[^]src[\]([^\])[\]/i, $html, $matches); foreach ($matches[1] as $src) { if (strpos($src, mmbiz.qpic.cn) false) { continue; // 只处理微信图片域名其他外链不动 } $filename md5($src . time()) . .jpg; $local_path $save_dir . / . $filename; // 下载时带Referer绕过防盗链 $ch curl_init($src); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER true, CURLOPT_TIMEOUT 10, CURLOPT_USERAGENT Mozilla/5.0, CURLOPT_REFERER https://mp.weixin.qq.com/, CURLOPT_SSL_VERIFYPEER false, ]); $img curl_exec($ch); curl_close($ch); if ($img ! false strlen($img) 1024) { file_put_contents($local_path, $img); $html str_replace($src, $save_dir . / . $filename, $html); } } return $html; }CURLOPT_REFERER填https://mp.weixin.qq.com/是绕过防盗链的关键微信服务器检查的是请求来源域名。如果这一步省略下载下来的图片全是403页面strlen($img)会被误判成正常下载。另外文件名用md5($src . time())生成避免两张不同文章里的相同图片互相覆盖也避免中文文件名在部分服务器上写入失败。最后加一道strlen($img) 1024的判断因为微信的403错误页也差不多这个大小低于这个阈值的基本可以认定下载失败。图片保存目录建议按日期分文件夹比如uploads/article/2025/06/这样后续做定时清理时直接按文件夹删除即可不用遍历数据库。5. 采集与排版联调路上最容易翻车的五个坑5.1 图片全部裂开防盗链惹的祸现象采集成功后排版页正文里的图片全部显示成裂图后台编辑器里一张都看不到。单独打开图片URL却能在浏览器里正常显示。原因微信图片服务器检查Referer浏览器直接打开图片时带的Referer是空的或者是你自己的域名微信会放行但在排版页里图片src指向mmbiz.qpic.cn浏览器请求时会带上你后台的域名作为Referer微信识别到非自家域名返回403。解决分两步走。第一采集时立刻把图片下载到本地这一步必须带着Referer: https://mp.weixin.qq.com/去请求第二如果历史文章里已经存了外链图片地址写一个批量替换脚本扫描article_draft.content_html里的mmbiz.qpic.cn域名逐个下载替换。别指望在排版页里做个「图片中转」就能一劳永逸微信的临时链接有时效过了有效期中转也没用。5.2 正文带着站点尾巴清洗规则没匹配到新版微信结构现象采集回来的正文末尾跟着一大段「推荐阅读」「喜欢此内容的人还喜欢」排版预览里删都删不干净。原因微信改版后部分公众号文章页面的推荐位从js_content节点外挪到了节点内之前按ID定位后直接saveHTML的方案把推荐位一起带回来了。还有一类是公众号运营者自己在正文末尾插入了名片和二维码图片这种属于正文的一部分删了反而丢内容。解决先看日志里记录的原始HTML片段确认推荐位包裹在哪个class里把那个class加进extract_article_body的删除规则里。对正文末尾的运营二维码判断依据是图片域名和上下文如果是mmbiz.qpic.cn域名且连续两张以上可以加一个「尾部连续图片清理」规则。这里没有通解微信每改一次版你就要跟进一次清洗规则。5.3 一采集就504或白屏内存与执行时间双上限现象点采集按钮后页面转圈十几秒然后返回504 Gateway Timeout或者干脆白屏。但直接在服务器命令行执行同一段采集脚本却正常。原因PHP的max_execution_time默认30秒memory_limit默认128M。微信文章正文HTML动辄几百KB再加上图片下载和DOM解析刚好卡在这两个阈值边缘。Web服务器层还有一层超时Nginx默认fastcgi_read_timeout是60秒。解决在采集入口文件开头加两行set_time_limit(120); ini_set(memory_limit, 256M);然后改Nginx配置里的fastcgi_read_timeout 120s;并重载。如果是Apache检查mod_fcgid的FcgidIOTimeout。这里要补一句set_time_limit(120)只对当前请求生效如果你把采集改成后台任务用cron触发就不受这个限制这也是生产环境更推荐的做法。5.4 导出后中文乱码编码声明缺失现象采集正常排版正常但把文章从后台导出或者用编辑器复制到公众号后台时中文变成一串「鍙戦」之类的乱码。原因微信正文是UTF-8编码但导出脚本生成的文件头里没有声明字符集。Word和部分文本编辑器默认用GBK解码读到UTF-8字节流就显示成乱码。数据库连接如果没设置utf8mb4入库时也可能已经转了一次码双重叠加。解决首先确保config.php里数据库PDO连接串带上了charsetutf8mb4new PDO(mysql:host127.0.0.1;dbnamewechat_editor;charsetutf8mb4, $user, $pass);导出文件时在HTML头部显式声明meta charsetutf-8如果是从命令行导出记得先执行mb_internal_encoding(UTF-8)。这条坑排查起来最玄学因为后台界面看着一切正常只有导出才炸往往浪费半小时在数据库字符集上。5.5 本地图片堆在服务器不清理磁盘悄悄满掉现象用了几个月后网站后台突然打不开SSH一查磁盘100%。uploads目录占了几个G。原因每采集一篇文章就下载一批图片但后台删除文章时只删了数据库记录没有同步删除服务器上的图片文件。时间一长垃圾图片越堆越多。视频教程里没演示过删除逻辑因为源码作者默认你会定期手动清理。解决写一个清理脚本跑在cron里找出数据库里不存在的图片文件并删除# 每天凌晨3点执行清理uploads下7天前、且不在数据库记录里的图片 0 3 * * * php /path/to/cli/cleanup_images.php --days7清理脚本逻辑不复杂遍历uploads目录把文件名和数据库article_draft表里的content_html字段做匹配匹配不上的且文件修改时间超过7天就删掉。执行前先跑一次--dry-run看会删哪些文件确认无误再去掉参数正式执行。6. 进阶技巧把采集结果一键导出成Word给排版补最后一块拼图6.1 导出脚本的写法与参数很多运营场景要求留存Word版比如给领导汇报、作为素材归档。这套源码自带的导出功能往往只是把HTML原样输出打开Word后版面乱掉。这里用一个零依赖的办法生成一个HTML格式的.doc文件Word能直接打开并保留大部分排版。在后台加一个导出按钮指向export_word.php?id文章ID?php // export_word.php $id intval($_GET[id]); // 伪代码实际从数据库查article_draft表取content_html $row get_article_by_id($id); header(Content-Type: application/msword); header(Content-Disposition: attachment; filename . $row[title] . .doc); echo html xmlns:ourn:schemas-microsoft-com:office:office; echo headmeta charsetutf-8/headbody; echo h1 . $row[title] . /h1; echo $row[content_html]; echo /body/html;这里的核心是Content-Type: application/msword和Content-Disposition的filename参数。intval($_GET[id])做了一次防注入的兜底别嫌多此一举直接在SQL里拼$_GET的写法是要被同行笑话的。6.2 校验一套采集流程是否可靠的习惯部署完成后我习惯用十篇文章做一轮「采集验收」找五篇不同公众号的文章覆盖文字长篇、多图短篇、带视频链接的、带代码块的再找五篇旧文章测试微信临时链接过期后的表现。验收标准很简单标题无误、正文无缺失段落、图片下载成功率100%允许重试一次、排版预览与原文视觉顺序一致。这个习惯帮我提前发现了不少隐藏问题比如某类文章中插了小程序卡片清洗规则会把整个卡片删掉导致排版出现空洞。处理办法是保留mp-common-materialsapp这类标签的占位符排版时人工替换成自己的引导卡片。这些边界情况视频教程里基本不会讲只有自己反复验收才能摸清。这套源码的价值在于把「抓取、清洗、入库、排版」串成了流水线但真正让它变得顺手的永远是你在这些踩坑记录上积累下来的补丁。我现在的习惯是每修一个坑就在代码里写一行注释标上日期和现象半年后再看那就是最值钱的文档了。希望这篇能帮你把这套源码跑得更顺。本文还有配套的精品资源点击获取
返回列表