在对接外部数据接口时,不少系统不再使用简单的page=1、page=2这类数字分页,而是采用动态Base64分页键。服务端把下一次抓取所需的状态信息编码成一段字符串返回,客户端必须带着这段游标请求下一页。本文以PHP为例,完整演示如何抓取此类JSON接口数据并入库。

一、理解动态Base64分页键的原理
动态分页键本质上是服务端生成的一种无状态游标。它通常包含当前已读取的记录位置、查询条件快照以及防篡改签名,然后使用Base64编码返回给客户端。客户端不需要理解内部逻辑,只需在下次请求时原样回传即可。这种方式可以避免服务端维护会话状态,也方便水平扩展。
当我们用PHP抓取时,第一次请求往往不需要传分页键,或者传空值。接口返回的JSON里会带一个类似next_cursor的字段,内容是一串Base64字符。我们把它保存下来,作为下一次请求的cursor参数。直到接口返回的next_cursor为空或者不存在,说明数据已经抓完。
二、PHP抓取脚本的实现
下面示例展示如何用PHP的curl扩展循环抓取数据。我们使用do-while结构,每次请求带上游标,解析JSON后提取数据与下一个游标。注意设置超时与重试,防止网络抖动导致中途失败。
<?php
$apiUrl = 'https://api.ipipp.com/v1/data/list';
$token = 'your_api_token';
$cursor = '';
$allRows = [];
do {
$query = http_build_query([
'cursor' => $cursor,
'limit' => 100
]);
$ch = curl_init($apiUrl . '?' . $query);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $token,
'Accept: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$resp = curl_exec($ch);
if ($resp === false) {
echo '请求失败: ' . curl_error($ch) . "n";
break;
}
curl_close($ch);
$json = json_decode($resp, true);
if (!isset($json['items'])) {
echo "响应格式异常n";
break;
}
foreach ($json['items'] as $item) {
$allRows[] = $item;
}
$cursor = $json['next_cursor'] ?? '';
} while (!empty($cursor));
echo '共抓取 ' . count($allRows) . " 条记录n";
?>
上述代码将每一页的items合并到$allRows数组中。在实际生产环境里,如果数据量很大,不应该全部放在内存,而应该抓取一批就入库一批,后面会讲到。另外Base64分页键本身不需要我们解码使用,除非你想做调试,可以用base64_decode()查看内容,但切勿自行构造游标传给服务端,否则可能被判为非法请求。
三、数据入库方案设计
抓到的JSON数据一般是关联数组,我们需要映射到数据表字段。假设目标表为sync_data,包含id、title、amount、sync_time等列,并且对业务唯一键biz_id做了唯一索引,这样重复入库时可以用INSERT IGNORE或者ON DUPLICATE KEY UPDATE避免报错。
使用PDO开启事务批量写入,能显著提升吞吐量并保证中途出错可回滚。下面代码承接前面的抓取循环,将每批数据写入数据库。
<?php
$pdo = new PDO(
'mysql:host=127.0.0.1;dbname=test;charset=utf8mb4',
'db_user',
'db_pass',
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
$pdo->beginTransaction();
$stmt = $pdo->prepare(
'INSERT IGNORE INTO sync_data (biz_id, title, amount, sync_time) VALUES (?, ?, ?, ?)'
);
foreach ($allRows as $row) {
$stmt->execute([
$row['biz_id'],
$row['title'],
$row['amount'],
date('Y-m-d H:i:s')
]);
}
$pdo->commit();
?>
如果数据量在十万级以上,建议每抓取五百到一千条就提交一次事务,而不是全部抓完再写。这样既控制内存,也减少单事务锁表时间。若接口有限流,可在循环里用usleep()降低请求频率。
四、常见误区与优化建议
一个典型误区是试图把Base64分页键解码后自己拼接参数去请求,结果服务端校验签名失败返回403。游标应由服务端签发,客户端只透传。另一个误区是忽略JSON里的空值字段,直接入库导致NOT NULL约束报错,应先做isset判断或设默认值。
在架构层面,可以把抓取与入库拆成两个脚本,用消息队列解耦。抓取脚本只负责把原始JSON推到队列,消费脚本负责解析入库,这样即使入库慢也不会阻塞抓取,也能避免被接口限流拖垮。对于超大数据量,还可以用多进程配合不同的起始游标做分片,但要注意业务上游标是否支持并行。
五、小结
基于动态Base64分页键的JSON抓取并不复杂,核心就是透传游标、循环请求、批量入库。PHP借助curl与PDO可以很简洁地实现全套逻辑。只要处理好异常、限流与重复数据,就能稳定地完成全量同步任务。