PHP 驱动的 AI 内容矩阵:利用 LLM API 与 PHP 协程构建高性能自动化 SEO 文章生成引擎

各位好,把你们手里的 Java Spring Boot 那个大杀器稍微放一放,把那杯温吞的 Node.js 热水放下。今天咱们不聊那些高不可攀的微服务架构,也不搞什么分布式数据库的一致性难题。

咱们来聊聊一个被低估、被误解,但最近正火得一塌糊涂的东西——PHP

尤其是配合了 Swoole 或者 Workerman 这类高性能扩展之后的 PHP。很多人还在说 PHP 是“面条代码”,还在吐槽它是“脚本语言”,那是因为他们没见过协程的威力。今天,咱们要打造的是一个“PHP 驱动的 AI 内容矩阵”。简单说,就是写一个 PHP 程序,利用 LLM API(比如 OpenAI 或者国内的各大模型),让 AI 像工厂流水线一样疯狂生产 SEO 文章,而且速度要快到让你怀疑人生。

准备好了吗?咱们直接开始。


第一部分:为什么是 PHP?为什么是协程?

SEO 需要什么?需要数量,需要质量,最重要的是,需要速度。如果你手写循环,每个请求都要 sleep 或者等待网络 IO,那你的 CPU 就在干瞪眼,看着 CPU 占用率 0%,却还在傻傻地等待 API 响应。这在多任务处理下简直就是性能黑洞。

这时候,PHP 的协程就登场了。别被“协程”这两个字吓到了,想象一下:

  • 普通线程:你去食堂排队打饭,轮到你的时候,你站在那儿,整个窗口都停了,直到你拿完饭走人。
  • 协程:你去食堂打饭,你刚把钱付了,服务员让你去旁边坐着等(挂起)。这时候,服务员立马去服务下一个人,等你的饭好了,再叫你(恢复)。在这期间,你可以去处理下一个订单,效率倍增。

PHP 协程的核心库 Swoole,就是为了解决这个问题而生。它让 PHP 从“同步阻塞”变成了“异步非阻塞”。咱们写代码的时候,依然可以用 awaitgo 这种顺手的语法,但底层它是在疯狂并发处理。

目标:在 1 秒钟内,发起 100 个 LLM 请求,生成 100 篇文章,而不是等 100 秒。


第二部分:基础架构——像个真正的产品经理一样思考

在动代码之前,咱们先定个调。一个“矩阵”系统,得有层级。

  1. 调度层:这是大脑,负责分配任务,决定写什么关键词,写什么标题。
  2. Worker 层:这是肌肉,负责并发执行,向 LLM 发送请求,获取内容。
  3. 持久层:这是仓库,负责把生成的文章存进 MySQL 或者 Redis,防止程序挂了数据丢失。
  4. 输出层:这是分拣员,负责把 Markdown 变成 HTML,或者推送到 CMS。

咱们今天的代码,重点放在 Worker 层的并发控制和 Prompt 构建上。


第三部分:连接 LLM——别把网线烧了

首先,你得连上 LLM。最简单的办法是用 SwooleCoroutineHttpClient

别再像以前那样写 curl 循环了,那是对协程的侮辱。咱们直接上 Swoole 的异步 HTTP 客户端。

<?php
// engine.php
require_once 'vendor/autoload.php';

use SwooleCoroutine as Co;
use SwooleHttpClient;

/**
 * 简单的 LLM 客户端封装
 * 这里以 OpenAI 接口为例,但你可以换成国内大模型的接口
 */
class LLMClient {
    private string $apiKey;
    private string $baseUrl = 'https://api.openai.com/v1/chat/completions';

    public function __construct(string $apiKey) {
        $this->apiKey = $apiKey;
    }

    /**
     * 发送请求的核心逻辑
     * @param array $messages 对话历史
     * @param string $modelName 使用的模型
     * @return string 返回的内容
     */
    public async function generate(array $messages, string $modelName = 'gpt-3.5-turbo'): string {
        // 创建一个 HTTP Client 实例
        $client = new Client('api.openai.com', 443, true);

        Cogo(function() use ($client, $messages, $modelName) {
            $client->setHeaders([
                'Authorization' => 'Bearer ' . $this->apiKey,
                'Content-Type' => 'application/json',
            ]);

            $payload = [
                'model' => $modelName,
                'messages' => $messages,
                'temperature' => 0.7, // 偶尔给点随机性,文章才生动
                'max_tokens' => 1000, // SEO 文章不需要太长,2000 字以内即可
            ];

            // 发送 POST 请求
            $client->post('/chat/completions', json_encode($payload));

            // 关闭连接
            $client->close();
        });

        // 等待结果
        $result = yield $client;

        if ($result->statusCode == 200) {
            $data = json_decode($result->body, true);
            return $data['choices'][0]['message']['content'] ?? '';
        }

        throw new Exception("API Error: " . $result->body);
    }
}

专家点评:看懂了吗?注意 yield $client。这就是魔法。我们没有在这个函数里 sleep,PHP 引擎切走了,去处理其他事情了。等 API 响应回来了,它再回来接住这个结果。


第四部分:Prompt 工程——教 AI 怎么“写烂文章”(划掉)写好文章

AI 生成的内容好不好,全看 Prompt。如果你问它“帮我写一篇关于 SEO 的文章”,它可能会给你写一篇教科书。

我们需要一个 Prompt Builder。咱们要告诉 AI:“你是一个资深 SEO 写手,请根据这个关键词,生成一篇包含以下结构…”

class PromptBuilder {
    /**
     * 构建一个 SEO 优化的 Prompt
     * @param string $keyword 核心关键词
     * @param string $context 上下文(比如行业)
     * @return array 消息数组
     */
    public static function buildSEOContext(string $keyword, string $context = '科技'): array {
        return [
            [
                'role' => 'system',
                'content' => "你是一个拥有 10 年经验的 SEO 内容策略师。你的任务是生成高质量的、SEO 友好的博客文章。
                规则:
                1. 标题必须包含关键词,且具有吸引力。
                2. 正文必须自然地插入关键词,不要堆砌。
                3. 结构清晰,包含 H2 和 H3 标签。
                4. 输出格式为 Markdown。
                5. 字数控制在 800-1200 字之间。
                "
            ],
            [
                'role' => 'user',
                'content' => "请为以下关键词生成一篇 SEO 文章,行业背景是:{$context}。关键词:{$keyword}"
            ]
        ];
    }
}

专家点评:这里的 system 提示词就像是给 AI 的“人设”。如果你不设定规则,它生成的文章可能前言不搭后语。在这个 Prompt 里,我们强制它输出 Markdown,这很重要,因为后端可以直接把 Markdown 转成 HTML。


第五部分:矩阵的核心——并发控制器

现在我们有客户端了,也有 Prompt 了。怎么让它们同时跑起来?这就是“矩阵”的精髓。

我们需要一个“任务队列”和一个“工人池”。为了简单起见,咱们写一个简单的并发控制器。

class ContentMatrix {
    private LLMClient $client;
    private array $keywords; // 待生成的关键词列表

    public function __construct(LLMClient $client, array $keywords) {
        $this->client = $client;
        $this->keywords = $keywords;
    }

    /**
     * 启动矩阵,生成所有文章
     * @param int $maxConcurrent 最大并发数
     */
    public async function run(int $maxConcurrent = 10): void {
        $total = count($this->keywords);
        echo "开始生成 {$total} 篇文章...n";

        // 这里的逻辑有点像贪吃蛇
        // 我们维护一个计数器,当并发数达到上限时,等待

        for ($i = 0; $i < $total; $i++) {
            $keyword = $this->keywords[$i];

            // 如果当前并发数满了,就 sleep 一会儿
            while (Co::stats()['coroutines'] >= $maxConcurrent) {
                Co::sleep(0.1); // 抢占式调度,让出 CPU
            }

            // 启动一个新协程去生成文章
            Co::create(function() use ($keyword, $i) {
                try {
                    echo "正在生成: [{$i}/{$total}] {$keyword} ... n";

                    $messages = PromptBuilder::buildSEOContext($keyword);
                    $content = yield $this->client->generate($messages);

                    // 这里可以插入数据库操作
                    $this->saveContent($keyword, $content);

                    echo "完成: [{$i}/{$total}] {$keyword} n";
                } catch (Exception $e) {
                    echo "错误: [{$i}/{$total}] {$keyword} - " . $e->getMessage() . "n";
                }
            });
        }

        // 等待所有协程结束(确保主线程不退出)
        while (Co::stats()['coroutines'] > 1) {
            Co::sleep(0.1);
        }

        echo "所有文章生成完毕!n";
    }

    private function saveContent(string $keyword, string $markdown): void {
        // 模拟数据库写入
        // $pdo->prepare("INSERT INTO articles (content, keyword) VALUES (?, ?)")->execute([$markdown, $keyword]);
        echo "已保存到数据库: {$keyword} (模拟)n";
    }
}

专家点评:看到了吗?Co::stats()['coroutines'] 这个函数是我们的控制阀门。它让我们能精确控制同时有多少个 LLM 请求在跑。

通常 LLM API 都有速率限制(Rate Limit),比如每分钟只能调 60 次。如果你设的并发数是 100,OpenAI 的服务器会直接把你拉黑(封号)。所以,设置 maxConcurrent 非常重要,建议从 5 或 10 开始测。


第六部分:高级优化——连接池与缓存

如果你要生成 10,000 篇文章,上述代码可能还行,但如果你要生成 100,000 篇呢?你会发现网络请求的开销(TCP 握手、TLS 握手)开始拖慢你的速度。

这时候,你需要一个 HTTP Client 连接池。Swoole 已经内置了强大的连接池功能。咱们不要每次请求都新建一个 TCP 连接,而是重用已有的连接。

use SwooleCoroutineHttpClient;

class PooledLLMClient {
    private array $pools = [];

    public async function request(string $url, array $headers, string $body): string {
        // 简单的哈希算法选择连接池(实际项目中可以用更复杂的负载均衡)
        $poolKey = md5($url);

        if (!isset($this->pools[$poolKey])) {
            // 初始化连接池,最大连接数 10
            $this->pools[$poolKey] = new SwooleCoroutineHttpClientPool(function() use ($url) {
                return new Client(parse_url($url, PHP_URL_HOST), parse_url($url, PHP_URL_PORT), true);
            }, 10);
        }

        $pool = $this->pools[$poolKey];
        $client = yield $pool->get();

        try {
            $client->setHeaders($headers);
            $client->post(parse_url($url, PHP_URL_PATH), $body);
            yield $client;

            return $client->body;
        } finally {
            // 归还连接
            $pool->put($client);
        }
    }
}

专家点评:这就是“性能优化”的下一步。连接复用能节省大量的握手时间。对于 AI 生成来说,这几毫秒的节省,在生成 10 万篇文章时,就是几小时的差距。


第七部分:结构化输出与 Markdown 转 HTML

SEO 不仅仅是文字,结构也很重要。如果生成的全是纯文本,搜索引擎蜘蛛不喜欢的。

我们需要让 AI 输出 Markdown,然后在后端把它转换成 HTML。PHP 有很多现成的库,比如 league/commonmark

use LeagueCommonMarkConverterFactory;
use LeagueCommonMarkEnvironmentEnvironment;
use LeagueCommonMarkExtensionCommonMarkCommonMarkExtension;
use LeagueCommonMarkExtensionTableTableExtension;

class MarkdownToHTML {
    public static function convert(string $markdown): string {
        $environment = new Environment([
            'html_input' => 'escape',
            'allow_unsafe_links' => false,
        ]);

        $environment->addExtension(new CommonMarkExtension());
        $environment->addExtension(new TableExtension());

        $converter = new ConverterFactory($environment)->createConverter();

        return $converter->convertToHtml($markdown);
    }
}

在你的 Prompt Builder 里,告诉 AI:“你必须使用 Markdown 语法,并且必须使用 H1, H2, H3 标签来组织内容。”

这样生成的文章,不仅有可读性,还有完美的 SEO 结构,简直是搜索引擎的“心头好”。


第八部分:容错机制——别让 API 断气了

网络是无情的。有时候 API 崩了,有时候返回 500 错误,有时候你的 API Key 过期了。

普通的 try-catch 只能抓一次。如果网络抖了一下,你的程序就停了。我们需要一个 熔断器 模式。

class ResilientLLMClient {
    private LLMClient $client;
    private int $failCount = 0;
    private int $maxFailures = 5; // 连续失败5次,触发熔断
    private bool $circuitOpen = false;

    public async function generate(array $messages, string $model): string {
        // 如果熔断器打开了,先别试了,睡一会儿再试
        if ($this->circuitOpen) {
            Co::sleep(1); // 冷却时间
            $this->circuitOpen = false; // 尝试恢复
            $this->failCount = 0;
        }

        try {
            $result = yield $this->client->generate($messages, $model);
            $this->failCount = 0; // 成功了,重置计数器
            return $result;
        } catch (Exception $e) {
            $this->failCount++;
            echo "API 调用失败: {$e->getMessage()}n";

            if ($this->failCount >= $this->maxFailures) {
                $this->circuitOpen = true;
                echo "熔断器开启!API 可能不稳定,暂停请求。n";
                // 递归调用自己,等待冷却后重试
                return $this->generate($messages, $model);
            }

            throw $e;
        }
    }
}

专家点评:这就像是一个尽职的守门员。如果对方一直踢不进门,守门员就把门关上,别再浪费体力去扑救了,歇会儿再试。这保证了你的矩阵不会因为 API 的暂时故障而挂掉。


第九部分:内容去重与洗稿

AI 最怕什么?重复。如果你给 AI 同样的关键词,它每次生成的文章结构都差不多,那搜索引擎会认为这是垃圾内容。

为了解决这个问题,咱们在 Prompt 里加一个“随机种子”或者“风格切换”的逻辑。

class PromptBuilder {
    public static function buildSEOContext(string $keyword, string $context = '科技', string $style = 'professional'): array {
        $styles = [
            'professional' => "风格:专业、严谨、数据驱动。",
            'casual' => "风格:幽默、口语化、像博客博主写的。",
            'academic' => "风格:学术性强,引用大量理论。"
        ];

        $styleDesc = $styles[$style] ?? $styles['professional'];

        return [
            [
                'role' => 'system',
                'content' => "你是一个拥有 10 年经验的 SEO 内容策略师。{$styleDesc}
                重要提示:每次生成的内容,段落顺序和引用数据必须完全不同,即使关键词一样,也要写出新鲜感。
                "
            ],
            // ... rest of the code
        ];
    }
}

在调度层,我们可以随机打乱 style 参数,让每次生成的文章都有点区别。虽然 AI 的随机性有时候很烂,但加上 Prompt 的引导,至少能稍微打破一点模板化。


第十部分:终极形态——从生成到发布

你有了代码,有了速度,有了内容。现在怎么落地?

假设你有几十个 WordPress 网站,你想自动填坑。

  1. CMS API:WordPress 有 REST API。
  2. 自动化:让 PHP Worker 生成完文章,通过 WP REST API 提交草稿,然后使用 WP-CLI 批量发布。

这需要一点额外的代码,但逻辑是一样的。

// 伪代码:发布到 WordPress
private async function publishToWordPress(string $title, string $contentHTML): void {
    $client = new Client('your-wordpress-site.com', 443, true);
    Co::go(function() use ($client, $title, $contentHTML) {
        $client->setHeaders([
            'Authorization' => 'Basic ' . base64_encode('user:pass'),
            'Content-Type' => 'application/json',
        ]);

        $payload = [
            'title' => $title,
            'content' => $contentHTML,
            'status' => 'draft' // 先存草稿
        ];

        $client->post('/wp-json/wp/v2/posts', json_encode($payload));
        // ... 处理响应
    });
    yield $client;
}

想象一下,当你的竞争对手还在人工敲键盘写文章的时候,你的 PHP 协程矩阵正在 24 小时不停歇地生产着数百篇结构完美的文章,源源不断地喂给搜索引擎。这就是“内容矩阵”的恐怖之处。


第十一部分:伦理与底线

作为一个“资深专家”,我必须提醒你:不要过度使用

虽然我们用了“洗稿”技巧,但生成的内容必须要有价值。如果全是垃圾内容,你的网站权重(DA/PA)会掉得很快。

  • 不要把关键词堆砌得让人看不懂。
  • 不要生成虚假的医疗、法律建议。
  • 保持一定的更新频率,不要一次性生成几万篇然后停更一个月。

AI 是工具,不是你的奴隶。用得好,它是提款机;用得烂,它是垃圾站。

总结(虽然我不喜欢写总结)

好了,咱们今天的讲座就到这儿。回过头来看看:

  1. PHP 早就不是以前的那个“快跑”脚本了。配合 Swoole,它就是高性能的代名词。
  2. 协程 是解决大量并发 IO 问题的最佳方案。
  3. LLM API 提供了内容生成的燃料。
  4. Prompt Engineering 是控制质量的舵。
  5. 架构设计(连接池、熔断器)决定了系统的健壮性。

Swoole 安装上,把你的 Prompt 写好,把 go 指令用溜,然后去构建你的内容帝国吧。别忘了,代码写得好不好看是一回事,能不能给你搞到流量才是王道。

去写代码吧,PHPer!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注