WordPress 自动化内容重构:利用 PHP 协程驱动 Gemini API 实现海量文章的二次建模

各位老铁,各位码农,各位还在为了几块钱的流量费在 WordPress 后台抓耳挠腮的站长们,大家好!

今天咱们不聊虚的,咱们来聊聊怎么让你的博客“起死回生”。想象一下,你的 WordPress 服务器就像是一个巨大的仓库,里面堆满了成千上万篇旧的博客文章。这些文章有的像刚出土的文物,有的像十年前的错别字集锦,有的读起来像是在用摩斯密码写文章。

你看着这些数据,心里那个急啊,就像看着一屋子坏掉的游戏机,想修吧,时间不够;想扔吧,舍不得。你想把它们全部重构一遍,把标题改得性感点,把内容写得像诺贝尔奖得主,把 SEO 关键词塞得像过期的棉花糖一样满。

但是,问题来了。在传统的 PHP 逻辑里,如果你想把 1000 篇文章全部重新生成一遍,你要怎么干?你要一条一条地读,一条一条地发 API,一条一条地存。这就好比你一个人去麦当劳打工,同时要负责炸薯条、做汉堡、刷盘子、收银。炸薯条的时候,你不能做汉堡;做汉堡的时候,你不能刷盘子。你的 CPU 被这些任务死死地占住了,除了在那傻傻地等 API 返回,你什么都干不了。

这时候,传统的 PHP 程序员可能会告诉你:“兄弟,你写个循环,加个 sleep 休眠几秒,慢慢磨呗。” 我告诉你,磨到地老天荒,你的服务器 CPU 都烧了,你的 API Key 都被 Google 封了。

今天,咱们要讲的就是如何打破这个“同步阻塞”的魔咒。我们要用 PHP 协程,配合 Gemini API,像变魔术一样,把这些老文章瞬间变成金子。

第一幕:同步 PHP 的“便秘”模式

首先,咱们得认清现实。PHP 是什么?PHP 是单线程的,也是同步阻塞的。这是什么意思?就是你的代码像是一条单行道的公交车。

比如这段代码,这是新手最爱写的:

// 这就是传说中的“串行便秘”模式
$posts = get_all_old_posts(); 

foreach ($posts as $post) {
    $new_content = call_gemini_api($post->content); // 等待 API 响应,这里阻塞 2 秒
    update_post($post->id, $new_content); // 等待数据库写入,这里又阻塞 0.5 秒

    echo "文章 {$post->id} 处理完成n";
}

假设你有 1000 篇文章,每篇处理平均耗时 3 秒。那你要多久?3000 秒!整整 50 分钟!这期间,你的服务器像个死鱼一样一动不动,谁访问谁得等。而且,Gemini API 对并发有限制,你这样一窝蜂地冲,人家后台一看:“哟,这有个脚本在暴力破解我的接口,封!”

所以,咱们得换条路。这条路,就是协程

第二幕:协程—— PHP 的“多线程”梦魇

协程,简单来说,就是让你的代码在等待网络请求(比如 Gemini 返回结果)的时候,不要傻等,而是像个调度员一样,跳到下一个任务去处理。

这就像什么呢?就像你点外卖。传统模式是:下单 -> 厨师做菜 -> 厨师端上来 -> 你吃。厨师做菜的时候,你在那里干瞪眼。

协程模式是:下单 -> 厨师A做宫保鸡丁(同时你让厨师B做麻婆豆腐)-> 两个菜都做好了 -> 你端走。

在 PHP 里,我们要用 Swoole。Swoole 是 PHP 生态里的一匹黑马,它让 PHP 拥有了异步、协程、TCP/UDP 网络通信的能力。虽然 Swoole 有个“像 C++ 一样的学习曲线”,但它绝对是做这种批量自动化任务的“屠龙刀”。

第三幕:架构设计—— 怎么让 Gemini 乖乖听话

我们的目标是:利用协程并发调用 Gemini API,对 WordPress 的旧文章进行重写。

核心架构如下:

  1. 数据获取层:使用 WordPress 的 WP_Query 或者直接操作 $wpdb,从数据库里把那些需要重构的文章筛选出来。
  2. 调度层(Swoole):建立一个协程池,把成千上万个文章处理任务扔进这个池子里。
  3. 执行层(业务逻辑):每个协程独立运行,负责获取当前文章内容,调用 Gemini API,拿到结果后更新回数据库。
  4. 防封层:控制并发数,防止 API 超时或封禁。

咱们先不谈代码,先谈谈怎么跟 Gemini 吐槽。你给 Gemini 的 Prompt(提示词)写得好不好,直接决定了重构后的文章是“宝马香车”还是“拖拉机拉稀”。

Prompt 模板建议:

你是一位资深的内容架构师。请对以下 WordPress 文章进行深度重构。

**文章原始内容:**
{{CONTENT}}

**重构要求:**
1. **保留核心信息**:不要编造事实,保持原意。
2. **优化 SEO**:在标题和正文中自然地融入关键词 [KEYWORD]。
3. **增强可读性**:使用更生动的词汇,增加段落过渡,避免枯燥的陈述。
4. **格式规范**:使用 Markdown 格式。

**请直接返回重构后的 Markdown 内容:**

第四幕:代码实战—— 这才是硬菜

好了,别眨眼。咱们要开始写代码了。为了演示方便,我们假设环境已经安装了 Swoole 扩展。

首先,咱们得写个类,叫 AutoRefactorService

1. 初始化数据库连接

在 Swoole 协程模式下,每个协程都有自己独立的上下文。这意味着每个协程都需要自己建立数据库连接,不能共享。如果共享,就会出现“脏读”或者“连接冲突”。这就像你在公共澡堂洗澡,不能穿着别人的衣服出去再穿回来,不然谁都觉得身上痒痒的。

class AutoRefactorService {
    private $wpdb;
    private $apiKey;
    private $maxConcurrent = 50; // 同时处理 50 篇文章

    public function __construct() {
        global $wpdb;
        $this->wpdb = $wpdb;

        // 你的 Google Gemini API Key,记得用环境变量存,别硬编码!
        $this->apiKey = getenv('GEMINI_API_KEY');

        // 初始化 Swoole 的 HTTP 客户端
        $this->httpClient = new SwooleHttpClient('generativelanguage.googleapis.com', 443, true);
        $this->httpClient->setHeaders([
            'Content-Type: application/json',
        ]);
    }

    // ... 更多方法
}

2. 并发处理核心逻辑

这是最关键的一步。我们要利用 SwooleCoroutineChannel 来控制并发数。

public function refactorAllPosts($postType = 'post', $status = 'publish') {
    echo "开始抓取文章...n";

    // 1. 先把文章 ID 全抓出来
    $postIds = $this->wpdb->get_col("SELECT ID FROM {$this->wpdb->posts} WHERE post_type = '$postType' AND post_status = '$status'");

    if (empty($postIds)) {
        echo "没有找到需要处理的文章。n";
        return;
    }

    echo "总共找到 " . count($postIds) . " 篇文章。准备启动协程风暴!n";

    // 2. 创建一个通道(就像一个队列)
    $channel = new SwooleCoroutineChannel($this->maxConcurrent);

    // 3. 启动消费者(Worker)
    // 这部分代码会同时启动 N 个 Worker 线程去处理任务
    SwooleCoroutinecreate(function () use ($channel, $postIds) {
        foreach ($postIds as $index => $postId) {
            // 将任务放入通道
            $channel->push($postId);

            // 每 100 个文章打印一下进度,不然你不知道它在跑没跑
            if ($index % 100 === 0) {
                echo "已推入 $index 篇文章进队列,等待处理...n";
            }
        }

        // 标记队列为空,虽然我们用 for 循环遍历了数组,但为了逻辑清晰,这里显式标记
        $channel->push('exit');
    });

    // 4. 执行任务(Worker)
    while (true) {
        // 从通道里取任务
        $postId = $channel->pop();

        // 如果收到退出信号,结束循环
        if ($postId === 'exit') {
            break;
        }

        // 这里是核心:启动一个新协程来处理这一篇
        SwooleCoroutinecreate(function () use ($postId) {
            $this->processOnePost($postId);
        });
    }

    echo "所有任务分发完毕,正在等待结果...n";
    // 在实际生产环境中,你需要等待所有协程完成,或者轮询数据库状态
    // 这里为了演示简洁,我们让主线程休眠一会儿等待所有协程执行完
    SwooleCoroutine::sleep(10); 
}

private function processOnePost($postId) {
    // 获取文章内容
    $post = get_post($postId);
    if (!$post) return;

    $currentContent = $post->post_content;
    $newContent = $this->askGemini($currentContent);

    if ($newContent) {
        // 更新文章
        wp_update_post([
            'ID' => $postId,
            'post_content' => $newContent,
            'post_modified' => current_time('mysql'),
        ]);
        echo "文章 ID: $postId 处理成功!n";
    } else {
        echo "文章 ID: $postId 处理失败,可能是 API 出错。n";
    }
}

3. 调用 Gemini API

这是最繁琐的部分,因为涉及到 HTTP 请求的构建。Swoole 的 HTTP 客户端比 PHP 的原生 curl 要快得多,因为它底层利用了事件循环。

private function askGemini($content) {
    $url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=" . $this->apiKey;

    // 构造 Prompt
    $prompt = "你是一位资深的内容架构师。请重构以下 WordPress 文章,要求优化 SEO 和可读性。文章内容:n" . $content;

    $data = [
        'contents' => [
            [
                'parts' => [
                    ['text' => $prompt]
                ]
            ]
        ],
        // 限制返回长度,防止 Token 超出限制
        'generationConfig' => [
            'maxOutputTokens' => 2000,
            'temperature' => 0.7
        ]
    ];

    $this->httpClient->post($url, json_encode($data));

    // 检查返回状态
    if ($this->httpClient->statusCode === 200) {
        $response = json_decode($this->httpClient->body, true);
        // 这里需要解析 JSON,提取 text 字段
        // 假设返回格式是标准的 Gemini API 格式
        return $response['candidates'][0]['content']['parts'][0]['text'] ?? '';
    } else {
        echo "API 请求失败,状态码: " . $this->httpClient->statusCode . "n";
        return false;
    }
}

第五幕:进阶优化—— 避坑指南

上面的代码跑起来是能用的,但是有几个大坑,你不填平了,服务器准得崩。

1. 数据库连接池(Connection Pool)

上面的代码里,$this->wpdb 是全局变量。如果 $maxConcurrent 是 1000,那岂不是 1000 个协程同时连 1 个数据库连接?MySQL 早就炸了。默认情况下,Swoole 的协程里直接用全局的 wpdb 会报错“Can’t use mysql link handle in this function context”之类的。

解决方案: 每个 processOnePost 里,都要自己 new wpdb

private function processOnePost($postId) {
    // 必须在协程内部重新初始化数据库连接
    global $wpdb;
    $local_wpdb = new wpdb(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);

    // ... 获取文章逻辑 ...
    // ... 调用 API 逻辑 ...

    // 别忘了断开连接,虽然 Swoole 会自动回收,但好习惯要养成
    $local_wpdb->flush();
}

2. 速率限制(Rate Limit)

Gemini API 并不是无限的。如果你在一个协程里疯狂发请求,不到 1 分钟,你的 Key 就会变成红色警告。

解决方案: 加个计数器。

private $requestCount = 0;
private $maxRequestsPerMinute = 50; // 假设限制是每分钟 50 次

private function checkRateLimit() {
    $this->requestCount++;
    // 这里可以结合时间戳做更复杂的滑动窗口算法
    // 简单粗暴的每分钟限制
    if ($this->requestCount > $this->maxRequestsPerMinute) {
        echo "触发速率限制,休眠 65 秒...n";
        SwooleCoroutine::sleep(65);
        $this->requestCount = 0;
    }
}

// 在调用 API 前调用
$this->checkRateLimit();

3. 错误重试机制(The Retry Game)

网络是不稳定的。有时候 API 会宕机,有时候 JSON 会解析错。你不能让一个协程跑错了就死掉,导致整条流水线停摆。

private function processOnePost($postId) {
    $retry = 3;
    while ($retry > 0) {
        try {
            // ... 处理逻辑 ...
            // 假设 $result 是处理结果
            if ($result) break; // 成功则跳出循环
        } catch (Exception $e) {
            echo "文章 $postId 处理出错: " . $e->getMessage() . "n";
            $retry--;
            if ($retry > 0) {
                SwooleCoroutine::sleep(2); // 失败后睡 2 秒再试
            }
        }
    }
}

第六幕:实战场景模拟

咱们来模拟一个具体的场景。

假设你是个卖“SEO 优化软件”的站长。你的博客里以前有很多关于“如何提升排名”的旧文章,有的写于 2018 年,那时候百度算法还没变,那时候关键词堆砌还管用。现在拿出来看,全是垃圾。

你写了一个定时任务脚本,挂载在 wp-cron 上。

// 在你的插件主文件里
register_activation_hook(__FILE__, function() {
    // 激活时创建一个后台管理页面,方便你点按钮启动
});

add_action('admin_menu', function() {
    add_management_page('AI 内容重构器', 'AI 内容重构器', 'manage_options', 'ai-refactor', function() {
        echo '<div class="wrap">';
        echo '<h1>WordPress 内容自动化重构系统</h1>';
        echo '<button onclick="startRefactor()" class="button button-primary">启动百万文章重构风暴</button>';
        echo '<div id="status-log"></div>';
        echo '</div>';

        echo '<script>
            function startRefactor() {
                const log = document.getElementById("status-log");
                log.innerHTML = "正在启动 Swoole 进程,请稍候...";

                // 使用 fetch 发送请求到后台 API
                fetch("' . admin_url('admin-ajax.php') . '?action=run_refactor&nonce=' . wp_create_nonce('run_refactor') . '")
                .then(res => res.text())
                .then(text => {
                    log.innerHTML += "<br/>" + text;
                    // 这里可以加个自动滚动到底部的逻辑
                });
            }
        </script>';
    });
});

// AJAX 处理器
add_action('wp_ajax_run_refactor', function() {
    // 必须在 AJAX 回调里显式调用 Swoole 的初始化
    SwooleRuntime::enableCoroutine(true);

    $service = new AutoRefactorService();
    // 只重构 100 篇做演示,实际写 0 或 null 即可全部重构
    $service->refactorAllPosts('post', 'publish', 100); 

    echo "任务执行完毕,请查看服务器日志。";
    exit;
});

第七幕:性能与金钱的博弈

说到这里,大家肯定兴奋了。1000 篇文章,只要几分钟就搞定了。

但是,咱们得算算账。Gemini API 是按 Token 计费的。重构一篇文章,可能需要消耗 500-1000 个 Token。1000 篇文章,那就是 50 万到 100 万个 Token。按照现在的价格,可能需要几十美元。

如果你有 10 万篇文章呢?那就是几百美元。几百美元能买多少个插件?能买多少个服务器?

这时候,批处理(Batching) 的艺术就体现出来了。不要一篇一篇地发请求,要把文章拼成一个数组,一次性发给 Gemini。

// 批处理伪代码
$batchData = [];
foreach ($posts as $post) {
    $batchData[] = $post->content;
}

// 调用一次 API,传入 $batchData
$response = call_gemini_batch($batchData);
// 解析返回的 $response,把每一块内容分别存回对应的文章 ID

虽然 Gemini 单次调用的 Token 上限有限,但这能极大地降低 HTTP 开销,提高成功率。这就像你寄快递,每件都寄一次很贵,你攒一邮包一起寄就便宜多了。

第八幕:哲学思考—— 数据的进化

最后,咱们来点升华。

写代码,有时候不仅仅是写功能,更是在写逻辑。利用 PHP 协程重构文章,其实是在模拟“生物进化”。我们保留了基因(核心信息),改变了表型(文章内容和格式),让它适应新的环境(搜索引擎的新算法、读者的新口味)。

传统 PHP 就像是一个刻板的书呆子,死记硬背,不懂变通,遇到困难就卡壳。而协程技术赋予了我们 PHP 程序员一双“千里眼”和“顺风耳”,让我们能够在同一个时间片内,处理成千上万个任务。

当然,技术是中立的。你可以用这个技术生成高质量的 SEO 文章,也可以用来刷流量,甚至用来生成假新闻。但作为开发者,我们的底线在于:内容为王,技术为辅。 如果没有好的内容,再快的协程也只是把垃圾倒得更快而已。

结语

好了,今天的讲座就到这里。希望这篇代码和思路能给你带来启发。

如果你在部署 Swoole 遇到困难,别慌。有时候你需要修改 php.ini 里的 swoole.use_shortname,有时候你需要检查防火墙。这就像修车,总会遇到小毛病的。

现在,回到你的服务器前,打开终端,敲下那行命令,看着你的文章一篇接一篇地被重写,那感觉,就像是在看着自己的孩子一夜之间长大成人。这就是编程的魅力,这就是自动化带来的快感。

祝各位重构愉快,API 不封号!

发表回复

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