php使用guzzlehttp/guzzle进行数据采集
一、需求目的
1、采集某个平台网站的数据
2、将采集回来的数据与本身平台系统的现有数据汇总
二、实现步骤
1、分析平台网站
- 分析登录校验方式
- session 或 token
- 分析界面数据渲染方式
- 异步数据渲染 或 直接页面和数据一起渲染
2、确定方案
- 分析后,发现网站的登录比较简单,直接账号密码登录即可,并且登录校验是通过cookie进行校验的
- 分析后,发现界面数据的渲染方式是异步数据渲染,代表有接口可以返回数据,直接对接口进行请求即可
3、封装功能类
确认完方案后,为了后续使用方便,特意封装一个功能类,其他地方使用的时候就比较简单
一、设置基础信息
- 设置对应平台域名
- 设置对应的账号密码
- 设置cookie序列化后保存的路径
二、功能方法
- 获取句柄方法(带cookie)
- 认证授权方法
- 带cookie的请求方法
- 简单的http方法
- 外部API返回处理方法
- 某个数据列表页面的获取等
4、使用功能类进行后续处理
三、具体实现
开启数据抓取脚本
使用功能类获取某个数据列表接口,得
```json
{
'count'=>10000,
'data_list'=>[{...}]
}
```
获取这批数据在数据库的流水号,并分开保存在数据库表 log 中(为了后续有个计算抓取进度,我把这个数据保存放在了功能类中,每获取完一次接口,都先保存到数据库,同时还保存了要抓取的总数量)
开启数据同步脚本
读取数据库 log 表,按id升序取n条记录
循环记录列表
设置记录状态修改为处理中
具体同步的逻辑(每个人不一样,这里就不展开了)
将记录状态修改为已完成(或者处理失败等)
界面展示
其他数据展示就不说了,这里写一下进度条的展示
找个进度条的代码样式(网上应该很多,我这里用的是后台管理模版自带的)
获取进度数据
- 数据抓取进度
- 获取log表最后一条数据的流水号,并得到该流水号要获取的总数量
- (总数量/每次获取的数量)+1 ,得到该流水号共有多少条记录
- 总记录数量/当前流水号的记录数量,得数据抓取进度
- 数据同步进度
- 获取同批流水号记录,通过状态 未处理 等状态判断未处理和已处理的数量
- 已处理 / 总数量,得数据同步进度
- 将进度和当前属于哪种进度的相关信息返回(界面简单通过定时器和ajax,所以可以看到进度条和文字在变化)
功能类代码
<?php
namespace App\Common\Features;
use App\Common\Models\SyncLog;
use App\Common\Repositories\LogRepositorie;
use App\Exceptions\ErrorCode;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Cookie\SetCookie;
use Illuminate\Support\Facades\Redis;
class Feature
{
//地址
private $apiHost;
//账号
private $username;
//密码
private $password;
//cookie保存路径
private $cookieJar;
private $cookie;
private $client;
private $LogRepo;
public function __construct()
{
$this->apiHost = '';
$this->username = '';
$this->password = '';
$this->cookieJar = 'cookie/login.cookie';
$this->LogRepo = new LogRepositorie();
}
/**
* 获取句柄
* @return Client
*/
public function getClient()
{
if (empty($client)) {
$this->cookie = new CookieJar;
$this->client = new Client(array(
'cookies' => $this->cookie
));
}
return $this->client;
}
/**
* 认证授权
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function auth()
{
$retryCount = 3;//认证问题不通过3次重试
$response = null;
while ($retryCount > 0) {
$retryCount--;
try {
$isAuth = false;
try {
//获取cookie和句柄
$cookieContent = file_get_contents($this->cookieJar);
$cookieObject = unserialize($cookieContent);//
$client = new Client(array(
'cookies' => $cookieObject
));
$response = $client->request('GET', $this->apiHost . '/xxx');//先尝试请求某个要登录的接口
$result = $this->loadResult($response);
$this->client = $client;//如果成功则句柄和cookie有效
$this->cookie = $cookieObject;
return;
} catch (\Exception $e) {
$isAuth = true;
}
//判断是否需要登录
if ($isAuth) {
//使用登录接口
$this->getClient();
$result = $this->client->request('POST', $this->apiHost . '/login', [
'timeout' => 30,
'form_params' => [
'username' => $this->username,
'password' => $this->password,
]
]);
//登录成功保存cookie
$cookie = $this->cookie;
$cookieContent = serialize($cookie);
file_put_contents($this->cookieJar, $cookieContent);
}
$retryCount = 0;
return;
} catch (\Exception $e) {
}
}
//抛异常
}
public function dataSearch()
{
$uri = '/xxx';
//获取记录批次流水
$serial_number = $this->LogRepo->getSerialNumber([
'uri' => $uri
]);
//循环获取数据
$flag = true;
$pageSize = 500;
$page = 1;
$totalItems = 0;
$orderArrayList = [];
while ($flag) {
$request = [
'pageSize' => $pageSize,
'page' => $page,
];
$request_type = 'post';
$result = $this->request($uri, $request_type, $request, $serial_number);
if (!empty($result['list'])) {
$orderArrayList[] = $result['list'];
} else {
$totalItems = $result['totalItems'];
$flag = false;
}
echo '共 ' . (ceil(($result['totalItems'] / $pageSize)) + 1) . " 页,第 " . $page . ' 页结束,' . date('Y-m-d H:i:s') . "\n";
sleep(5);
$page++;
}
$response = [
'count' => count($orderArrayList),
'data_list' => $orderArrayList,
];
return $response;
}
/**
* 带cookie的请求方法
* @param $uri
* @param string $method
* @param array $queryData
* @param string $serial_number
* @param string $resultType
* @return false|mixed|string
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function request($uri, $method = 'get', $queryData = [], $serial_number, $resultType = 'array')
{
//这里新增记录,为了后续的进度统计
$syncLog = $this->LogRepo->addSyncLog([
'uri' => $uri,
'serial_number' => $serial_number,
'request_type' => $method,
'request' => $queryData,
]);
$syncLogId = $syncLog['id'];
$this->auth();
$result = $this->http($uri, $method, $queryData, [], $resultType);
//更新同步记录的结果
$this->LogRepo->updateSyncLog([
'id' => $syncLogId
], [
'response' => $result,
'status' => SyncLog::STATUS_WAIT_SYNC,
'grab_time' => time(),
]);
return $result;
}
/**
* 简单的http方法
* @param $uri
* @param string $method
* @param array $queryData
* @param array $headerData
* @param string $resultType
* @return false|mixed|string
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function http($uri, $method = 'get', $queryData = [], $headerData = [], $resultType = 'array')
{
$url = $this->apiHost . $uri;
$headers = [
'Content-Type' => 'application/x-www-form-urlencoded'
//'Content-Type' => 'application/json;charset=UTF-8'
];
if (is_array($headerData)) {
$headers = array_merge($headers, $headerData);
}
$params = [];
if (!empty($queryData) && is_array($queryData)) {
if ($method == 'get') {
$params['query'] = $queryData;
} else {
if ($headers['Content-Type'] == 'application/x-www-form-urlencoded') {
$params['form_params'] = $queryData;
} else {
$params['json'] = $queryData;
}
}
}
if (!empty($headers) && is_array($headers)) {
$params['headers'] = $headers;
}
$response = $this->client->request($method, $url, $params);
$result = $this->loadResult($response);
if (strtolower($resultType) == 'array') {
return $result;
} else if (strtolower($resultType) == 'json') {
return json_encode($result, true);
}
}
/**
* 外部API返回处理
* @param $response
* @return mixed
*/
protected function loadResult($response)
{
$response_code = $response->getStatusCode();
$result = $response->getBody()->getContents();
$result = json_decode($result, true);
if (!isset($result['success']) || $result['success'] != true) {
}
return $result['data'];
}
}