☰
一套PHP源码搞定公众号排版与文章采集:架构与实战解析
2026/9/26 12:15:48 网站建设 项目流程

简介:面向公众号运营者、新媒体编辑和初中级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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

采集接口拿到文章后,把解析出的标题、作者、正文HTML写进这张表,status置为草稿;排版页再从这张表把content_html捞出来渲染成可编辑的富文本。两套功能共用一个数据表,这是这类源码最常见的做法,好处是逻辑简单,不会出现「采集好的文章在排版页找不到」的窘境。

2.2 采集核心原理:微信文章没有开放接口,走的是一条HTML抓取路线

微信公众平台从来没有开放过「把文章正文给我的服务器」这类接口。所有声称能采集公众号文章的工具,底层都离不开同一个动作:对文章链接发一次普通的HTTP请求,拿到服务器返回的HTML,再从HTML里把正文节点抠出来。微信公众号文章链接长这样:

https://mp.weixin.qq.com/s?__biz=MzA5MjMxMjEwNQ==&mid=2654789012&idx=1&sn=3e9b6d2c9e6f4a1c

对PHP的curl来说,这只是一个带了一串查询参数的GET请求,跟访问普通网页没有区别。文章正文通常被包在一个id="js_content"的div里,里面是经过排版的<p>、<section>、<img>标签。采集器要做的,就是把这个节点连同一个class="rich_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.sql

install.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_html:UA、超时与重试

采集模块的核心是一个叫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('//*[@id="js_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('.//*[@style="display: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连接串带上了charset=utf8mb4:

new PDO('mysql:host=127.0.0.1;dbname=wechat_editor;charset=utf8mb4', $user, $pass);

导出文件时在HTML头部显式声明:

<meta charset="utf-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 --days=7

清理脚本逻辑不复杂:遍历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:o="urn:schemas-microsoft-com:office:office">'; echo '<head><meta charset="utf-8"></head><body>'; 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>这类标签的占位符,排版时人工替换成自己的引导卡片。这些边界情况,视频教程里基本不会讲,只有自己反复验收才能摸清。

这套源码的价值在于把「抓取、清洗、入库、排版」串成了流水线,但真正让它变得顺手的,永远是你在这些踩坑记录上积累下来的补丁。我现在的习惯是每修一个坑就在代码里写一行注释,标上日期和现象,半年后再看,那就是最值钱的文档了。希望这篇能帮你把这套源码跑得更顺。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询