各位老铁,把手机调成静音,把零食放一边,今天咱们不聊那些虚头巴脑的框架,也不讲什么设计模式八股文。咱们来聊聊怎么用 PHP 这门“上古神兽”去驾驭 AI 的风潮,搞一个能自动洗稿、自动生成、自动发文章的“内容矩阵核弹”。
听好了,这可不是那种每天让你手动复制粘贴的脚本,这是一个基于PHP 协程 + Google Gemini API + WordPress 构建的自动化工业化流水线。
准备好了吗?咱们开始“炸”服务器。
第一部分:为什么你要搞这个?(打破你的阿Q精神)
在座的各位,可能都经历过这种绝望:老板说,我们需要 1000 篇关于“减肥餐”或者“区块链”的长文。你一咬牙一跺脚,开始写。
第一天,你是神,键盘敲得飞起,觉得自己是鲁迅转世。
第三天,你觉得自己是便秘患者,键盘敲得火星四溅。
第七天,你看着屏幕上的“由于篇幅过长,此处省略一万字”,直接把电脑砸了。
手动写文章是反人类的,就像让大猩猩弹钢琴。但是,SEO 需要海量内容。怎么办?去买数据?去抄袭?那是要封号的,而且低级。现在的玩法是:采集 -> 重写 -> 再采集 -> 再重写。
这就需要一台“永动机”。这台机器的核心,就是协程。
第二部分:为什么是 PHP?为什么是协程?
你可能会问,Go 语言不是协程之王吗?为什么不用 Go?
Go 语言适合写后端服务,但在 PHP 的世界里,你已经有 WordPress 了。你想把这些文章发回你的 WP 网站,继续用 PHP 是最顺滑的。而且,PHP 8 的性能已经脱胎换骨,配合 Swoole 或 OpenSwoole,它能跑出惊人的 I/O 密集型吞吐量。
什么是协程?
简单说,传统的 PHP 是“打电话”模式:你拨通客服,挂了等回电。这期间你啥也干不了,得干等。
协程是“面谈”模式: 你和客服面谈,他说“稍等我去查个档”,然后你转头去帮别人倒杯水,聊两句。等他查完了,自然叫你。
这就是非阻塞 I/O。对于我们要疯狂调用 Gemini API 和爬虫抓取页面来说,这种能力简直是救命稻草。
第三部分:系统架构概览
我们的系统长这样:
- 数据源采集层(爬虫): 利用 PHP 协程并发抓取 100 个不同的网站,提取文章标题和正文。
- 语义重组层(LLM): 把抓来的文章喂给 Gemini,让它用 100 种不同的语气、句式重新写一遍。
- 落地方案(WP-API): 把生成的文章通过 WordPress 的 REST API 批量推送到你的站点。
- 任务调度器(大脑): 管理进度、限流、重试、去重。
第四部分:核心技术 —— PHP 协程实战
咱们先不谈脑子(LLM),先让 PHP 的手脚动起来。
Swoole 的协程 HTTP 客户端是神器。你可以想象一下,如果你用传统的 curl_multi 或者多进程,管理起来头都大了。但在 Swoole 里,写同步代码就能得到异步结果。
来看看这段代码,感受一下什么是“赛博朋克写法”:
<?php
require_once __DIR__ . '/vendor/autoload.php';
use SwooleCoroutine;
use SwooleCoroutineHttpClient;
/**
* 并发采集 10 个网页内容
*/
function fetchMultipleUrls(array $urls)
{
// 使用 Co::create 或直接在协程函数中写
// 这里的关键点:我们并不等待每一个 curl 完成,而是让它们同时跑
$tasks = [];
foreach ($urls as $index => $url) {
$tasks[] = Coroutine::create(function () use ($url, $index) {
$client = new Client(parse_url($url, PHP_URL_HOST), 443, true);
// 协程的魔法:这里的 yield 实际上把 CPU 释放回调度器
// 但代码写起来像同步一样
$client->get(parse_url($url, PHP_URL_PATH));
if ($client->statusCode === 200) {
// 这里拿到了网页内容,你可以直接解析 DOM
return [
'url' => $url,
'content' => $client->body,
'title' => '抓取成功 ' . $index
];
}
return null;
});
}
// 这里返回的是一个 Generator,协程其实已经在后台跑了
foreach ($tasks as $task) {
$result = $task->yield();
if ($result) {
echo "拿到了 " . $result['title'] . " 的内容长度: " . strlen($result['content']) . "n";
}
}
}
// 模拟数据
$urls = [
'https://example.com/article/1',
'https://example.com/article/2',
'https://example.com/article/3',
// ... 假设有 100 个
];
Coroutine::run(function () use ($urls) {
fetchMultipleUrls($urls);
});
echo "所有任务执行完毕n";
看到了吗?没有复杂的回调地狱,没有 Promise 对象的纠结。就这么几行代码,10 个请求同时发出。如果你的网络快,可能 0.5 秒就抓完了 10 个网页。这就是协程的魅力。
第五部分:大脑 —— Gemini API 的调用艺术
现在我们有了数据,得让它变成新东西。Google 的 Gemini API 是目前最聪明的模型之一,比 GPT-4 便宜,比 GPT-3.5 聪明。
我们的目标是“语义重构”。不是简单的替换同义词(那是老黄历了,会被 Google 算法打脸),而是要改变段落结构,改变叙述角度。
1. 构建提示词
怎么让 AI 不写烂文?你得像训狗一样训它。
/**
* 构建 Gemini 的请求体
*/
function buildGeminiPrompt($sourceText)
{
return [
'contents' => [
[
'parts' => [
[
'text' => <<<PROMPT
你是一位资深的内容架构师和 SEO 专家。你的任务是将以下提供的网页内容,改写为一篇高质量、原创性强的长文。
**核心指令:**
1. **彻底重组结构:** 不要保留原文的段落顺序。将关键点打乱,按照新的逻辑重新组织。
2. **同义转换与扩写:** 使用完全不同的词汇和句式。例如,不要把 "good" 写成 "nice",要用 "excellent" 或 "beneficial"。
3. **增加深度:** 在改写的同时,适当补充相关的背景信息或数据(如果源文本中有),使文章更丰满。
4. **排版优化:** 文章中必须包含:一个引人入胜的标题、清晰的小标题(H2)、以及适当的强调标记。
**源文本:**
{$sourceText}
请直接输出改写后的 HTML 格式文章。
PROMPT
]
]
]
]
];
}
2. 协程调用 API
这里要注意,Gemini API 是有速率限制的。如果你在一个循环里疯狂调用,几秒钟就会遇到 429 Too Many Requests 的错误。这时候,协程的信号量 功能就派上用场了。
use SwooleCoroutineSemaphore;
function rewriteContentWithGemini(string $text)
{
// 限制并发数为 5,避免被 Google 封 IP
$semaphore = new Semaphore(5);
return Coroutine::create(function () use ($text, $semaphore) {
$semaphore->lock();
$client = new SwooleHttpClient('generativelanguage.googleapis.com', 443, true);
// 替换 YOUR_API_KEY
$apiUrl = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=YOUR_API_KEY";
$data = buildGeminiPrompt($text);
$client->post($apiUrl, json_encode($data), function ($client) {
if ($client->statusCode === 200) {
$response = json_decode($client->body, true);
if (isset($response['candidates'][0]['content']['parts'][0]['text'])) {
return $response['candidates'][0]['content']['parts'][0]['text'];
}
}
throw new Exception("API 调用失败: " . $client->body);
});
// 关键:等待协程结束
Coroutine::join($client->task);
$semaphore->unlock();
})->yield();
}
第六部分:矩阵化策略 —— 怎么避免内容雷同
你可能会问,如果我有 1000 个源 URL,全部喂给同一个 Prompt,出来的文章是不是都一个味儿?
这正是我们需要解决的“同质化”问题。我们需要给每个任务分配不同的角色。
角色分配矩阵:
- 权威专家: 强调数据、深度分析、专业术语。
- 新手小白: 强调通俗、易懂、使用反问句、讲段子。
- 养生博主: 强调健康、注意事项、情感共鸣。
- 硬核极客: 强调代码、底层原理、速度。
在代码里,我们定义一个配置文件:
$personas = [
'expert' => "你是一个行业专家,用数据说话,语气严肃。",
'casual' => "你是一个热爱分享的朋友,语气轻松,多使用感叹号。",
'storyteller' => "你是一个讲故事的人,把枯燥的技术转化为跌宕起伏的故事。",
'salesman' => "你是一个推销员,强调产品的好处,诱导用户点击。"
];
function getPromptWithPersona($text, $personaKey)
{
$persona = $personas[$personaKey];
// ... 在构建 Prompt 时,将 $persona 插入到指令中
return <<<TEXT
{$persona}
重写以下内容:
{$text}
TEXT;
}
这样,当你循环抓取文章时,随机打乱 $personas 数组,或者按照特定顺序分配,生成的 1000 篇文章虽然讲的是同一个源素材,但读起来就像 1000 个人在说话。
第七部分:落地 WordPress —— 这里的坑比数据还深
有了文章内容,怎么存进 WP?直接 SQL 插入?太丑陋,而且容易报错。
用 WordPress 的 REST API。这是最优雅的方式。
1. 认证
你需要一个 Application Password。在 WP 后台 -> 用户 -> 应用密码,创建一个。
function createPostInWordPress($title, $content, $categoryIds = [])
{
$client = new SwooleHttpClient('你的站点域名', 443, true);
$payload = [
'title' => $title,
'content' => $content, // 假设 Gemini 返回的是纯文本或 HTML
'status' => 'publish', // 或者 'pending' 稍后审核
'categories' => $categoryIds
];
$client->post('/wp-json/wp/v2/posts', json_encode($payload), function ($client) {
if ($client->statusCode === 201) {
$data = json_decode($client->body, true);
echo "发布成功!Post ID: " . $data['id'] . "n";
return $data['link'];
} else {
echo "发布失败: " . $client->body . "n";
}
});
Coroutine::join($client->task);
}
2. 图片处理
文章里通常需要配图。你可以让 Gemini 返回图片的 URL,或者自己写个爬虫去 Unsplash/Pexels 搜关键词。
这里有个技巧:CDN 加速。不要把图片存到 WP 的 uploads 目录,直接把图片 URL 放进去,WP 会自动从远程拉取并缓存。
第八部分:全链路整合 —— 炸裂的主循环
现在,把上面的所有零件拼起来。我们要做一个 Master Worker 模式。
虽然 Swoole 的单进程协程已经很快了,但如果你想跑 10 万篇文章,你需要多进程。
use SwooleProcess;
use SwooleProcessPool;
$pool = new Pool(4); // 开启 4 个 Worker 进程
$pool->on('workerStart', function ($pool, $workerId) {
echo "Worker #{$workerId} 已启动n";
// 每个 Worker 进程负责一部分 URL
$myUrls = array_slice($allUrls, $workerId, 10);
foreach ($myUrls as $url) {
try {
// 1. 抓取
$content = fetchContent($url);
// 2. LLM 重写 (带限流)
$rewritten = rewriteContentWithGemini($content);
// 3. 存入 WP
createPostInWordPress($rewritten['title'], $rewritten['content']);
// 4. 记录日志
logToDB($url, 'success');
} catch (Exception $e) {
logToDB($url, 'failed', $e->getMessage());
}
}
});
$pool->start();
第九部分:避坑指南与运维
代码写得再漂亮,跑起来也是一地鸡毛。这里有三个必须要知道的“坑”。
1. 内存泄漏
PHP 的垃圾回收(GC)虽然强大,但如果你在协程里创建了大量对象且不释放,Swoole 进程可能会内存溢出(OOM)。
对策: 及时 unset 变量。不要把整个 HTML 文档存到数组里,抓取到什么处理什么。
2. DNS 解析慢
在 PHP 中使用协程 HTTP 客户端,默认不会缓存 DNS。如果你有 1000 个请求,意味着要解析 1000 次 DNS。
对策:
// 开启 Swoole 的全局 DNS 缓存
Co::set(['dns_cache_expire' => 600]);
// 或者手动缓存
3. AI 的幻觉
Gemini 有时候会一本正经地胡说八道,编造不存在的数据。
对策: 在 Prompt 里加一条铁律:“如果源文本中没有某个数据,请不要编造,请使用通用的描述性语言代替。” 并且,写完文章后,自己做一个简单的正则检查,确保关键实体没有跑偏。
第十部分:终极形态 —— 自动化闭环
当你把这个系统跑通后,你拥有的不仅仅是一堆文章,而是一个内容农场。
你可以进一步优化:
- A/B 测试 Prompt: 把生成的文章发到推特或测试域名,看哪个流量高,然后自动调整 Prompt。
- 去重算法: 在存入 WP 之前,先调用一个简单的相似度算法(比如 Edit Distance 或 Jaccard 相似度),如果和库里文章太像,就扔掉。
结束语:代码是冷冰冰的,但数据是热的
写到这里,咱们今天的讲座也该收尾了。
这套系统,利用了 PHP 协程的高并发特性解决了“时间”问题,利用了 Gemini 的大脑解决了“质量”问题,利用了 WordPress 解决了“落地”问题。
它不是在偷懒,它是在榨取互联网的价值。它把原本需要人类花费一个月的重复劳动,压缩到了几分钟。人类不应该被束缚在键盘上,人类应该去思考如何优化算法,如何调整 Prompt,如何设计更好的架构。
所以,别再纠结写不写得出来那 1000 篇文章了。去装个 Swoole,去申请个 API Key,让你的 PHP 服务器动起来。
这就是技术,这就是生产力。散会!
(注:以上代码仅为演示逻辑,实际生产环境需增加重试机制、异常捕获、日志记录及代理 IP 池,切勿用于非法用途。)