Amazon Advertising API
最终目标效果
1. 授权 (加入亚马逊开发者白名单)
官方文档 https://advertising.amazon.com/API/docs/en-us/setting-up/account-setup
相对来说 授权还是比较麻烦的, 由于公司业务原因,我们注册的是第三方管理账户。 也就是说一个开发者账号管理多个店铺(防关联)。
建议开发这种大平台接口功能在前期阶段先多研究研究文档,一步一步来,这样在后面的功能开发阶段还是比较有用的。
由于我们注册的是第三方管理,在于亚马逊邮件往来阶段对方要我们提供一个公司介绍…
但我们这是自己的后台ERP没啥官网,只能去网上找个模板花两个小时做了一个单页面官网给他们发过去(有自己公司官网的直接发官网链接就好)…
正常的往来邮件最后会得到API所需的 client_id, client_secret
2. 获取店铺授权token (Create API authorization and refresh tokens)
https://advertising.amazon.com/API/docs/en-us/setting-up/generate-api-tokens
这个就是给你一个链接, 把你实际的参数替换进去, 最后会跳转到登录界面(网页有登录记录的可能会跳过) 然后让你授权。
https://www.amazon.com/ap/oa?client_id=YOUR_LWA_CLIENT_ID&scope=cpc_advertising:campaign_management&response_type=code&redirect_uri=YOUR_RETURN_URL
参数说明
- 链接的前半部分自己判断区域选择对应地址
- YOUR_LWA_CLIENT_ID: client_id
- YOUR_RETURN_URL: 这个当时我也蒙蔽了半天… 下图红色圈内的回调地址就是这个了…
登陆之后会给你也授权页面 点击 允许 就行
接下来就会跳转到你的回调地址(YOUR_RETURN_URL),并将authorization code 以GET 的形式传回来
array(2) { [“code”]=> string(20) “ANesDVfOevJXAuKXZVut” [“scope”]=> string(35) “cpc_advertising:campaign_management” }
自己处理逻辑 保存这个 code 有了这个code 下面的 获取token和刷新token直接按文档curl请求就好
public function getToken()
{
switch ( $this->region ) {
case 'NA':
$region = 'https://api.amazon.com/auth/o2/token';
break;
case 'EU':
$region = 'https://api.amazon.co.uk/auth/o2/token';
break;
case 'FE':
$region = 'https://api.amazon.co.jp/auth/o2/token';
break;
default:
$region = 'https://api.amazon.com/auth/o2/token';
break;
}
$header = array (
'Content-Type:application/x-www-form-urlencoded;charset=UTF-8'
);
$code = "ANesDVfOevJXAuKXZVut";
$redirect_uri = "https://network.*******.com/var/amz_advertising_return/file_getToken.php";
$curlData = "grant_type=authorization_code&code={$code}&redirect_uri={$redirect_uri}&client_id={$this->client_id}&client_secret={$this->client_secret}";
$res = $this->sendCurlPostRequest($region, $curlData, $header);
$data = json_decode($res, true);
// var_dump($res);
// var_dump($data);die;
$access_token = $data['access_token'];
$refresh_token = $data['refresh_token'];
$token_type = $data['token_type'];
$token_time = time() + 3500;
// db操作...
}
// 刷新 token
public function refreshToken()
{
switch ( $this->region ) {
case 'NA':
$region = 'https://api.amazon.com/auth/o2/token';
break;
case 'EU':
$region = 'https://api.amazon.co.uk/auth/o2/token';
break;
case 'FE':
$region = 'https://api.amazon.co.jp/auth/o2/token';
break;
default:
$region = 'https://api.amazon.com/auth/o2/token';
break;
}
$header = array (
'Content-Type:application/x-www-form-urlencoded;charset=UTF-8'
);
$curlData = "grant_type=refresh_token&client_id=$this->client_id&refresh_token=$this->refresh_token&client_secret=$this->client_secret";
$res = $this->sendCurlPostRequest($region, $curlData, $header);
$data = json_decode($res, true);
$data['token_time'] = time() + 3500;
$this->token = $data['access_token'];
$this->local_log($data);die;
global $link, $id;
$sql="UPDATE db_ads SET access_token='{$data['access_token']}', refresh_token='{$data['refresh_token']}', token_time='{$data['token_time']}' WHERE shop_id='".$id."' ";
mysqli_query($link, $sql);
}
获取token之后要获取 profile_id,之后的接口请求基本都需要这个
// 获取店铺信息 profile_id
public function getProfiles()
{
switch ( $this->region ) {
case 'NA':
$host = "https://advertising-api.amazon.com";
break;
case 'EU':
$host = "https://api.amazon.co.uk/auth/o2/token";
break;
case 'FE':
$host = "https://api.amazon.co.jp/auth/o2/token";
break;
default:
$host = "https://advertising-api.amazon.com";
break;
}
$url = $host."/v2/profiles";
$headers = array(
"Authorization: Bearer $this->token",
"Amazon-Advertising-API-ClientId:$this->client_id",
"Access-Control-Allow-Credentials: true",
"Content-Type:application/json",
"User-Agent:test1",
);
$profile = $this->sendCurlGetRequest($url, $headers);
$profiles = json_decode($profile, true);
// db操作...
$this->profile_id = number_format($profiles[0]['profileId'], 0, '', '');
global $link, $id;
$sql="UPDATE db_ads SET profile_id='{$this->profile_id}', profiles='{$profile}' WHERE shop_id='".$id."' ";
mysqli_query($link, $sql);
}
到这为止基本上准备工作就做完了,接下来就是正式请求报告信息了
3.获取REPORTS
大概流程就是
请求生成报告 - 等待报告完成 - 获取报告信息 - 下载报告内容 - 解压
这里有个小坑,当时我下载报告怎么传参都有问题,查了好久发现他最终curl请求会307重定向,直接去跳转下载页面, 但是会报错,大概意思就是标头有问题:
Only one auth mechanism allowed; only the X-Amz-Algorithm query parameter, Signature query string parameter or the Authorization header should be specified
就是你带header参数,他说你多余, 你不带,告诉你没权限…
谷歌N久之后找到原因:
解决办法就是把重定向的链接提取出来,再次不带header请求一次。
// 请求报告
public function requestReport()
{
// Record types can be: campaigns, adGroups, keywords, productAds, and targets
// productAds: 'metrics' => 'campaignName,campaignId,adGroupName,adGroupId,impressions,clicks,cost,currency,asin,sku,attributedConversions1d,attributedConversions7d,attributedConversions14d,attributedConversions30d,attributedConversions1dSameSKU,attributedConversions7dSameSKU,attributedConversions14dSameSKU,attributedConversions30dSameSKU,attributedUnitsOrdered1d,attributedUnitsOrdered7d,attributedUnitsOrdered14d,attributedUnitsOrdered30d,attributedSales1d,attributedSales7d,attributedSales14d,attributedSales30d,attributedSales1dSameSKU,attributedSales7dSameSKU,attributedSales14dSameSKU,attributedSales30dSameSKU,attributedUnitsOrdered1dSameSKU,attributedUnitsOrdered7dSameSKU,attributedUnitsOrdered14dSameSKU,attributedUnitsOrdered30dSameSKU'
$recordType = 'productAds';
$header_ = $this->returnHeader();
$url = $header_['host']."/v2/sp/{$recordType}/report";
// 获取 前一天的信息
$date = date('Ymd', time()-3600*24);
$curlData = array(
// 'segment'=> 'query',
'reportDate'=> $date,
'metrics' => 'campaignName,campaignId,adGroupName,adGroupId,impressions,clicks,cost,currency,asin,sku,attributedConversions1d,attributedConversions7d,attributedConversions14d,attributedConversions30d,attributedConversions1dSameSKU,attributedConversions7dSameSKU,attributedConversions14dSameSKU,attributedConversions30dSameSKU,attributedUnitsOrdered1d,attributedUnitsOrdered7d,attributedUnitsOrdered14d,attributedUnitsOrdered30d,attributedSales1d,attributedSales7d,attributedSales14d,attributedSales30d,attributedSales1dSameSKU,attributedSales7dSameSKU,attributedSales14dSameSKU,attributedSales30dSameSKU,attributedUnitsOrdered1dSameSKU,attributedUnitsOrdered7dSameSKU,attributedUnitsOrdered14dSameSKU,attributedUnitsOrdered30dSameSKU'
);
$curlData = json_encode($curlData);
$res = $this->sendCurlPostRequest($url, $curlData, $header_['header']);
$res = json_decode($res, true);
$this->local_log($res);
if (isset($res['reportId'])) {
sleep(60); // 留点时间生成报告 测试用 实际情况需将reportId存入数据库, 间隔时间请求 getResquestInfo
$this->getResquestInfo('reports', $res['reportId']);
}
}
// 获取 报告/快照 请求信息
public function getResquestInfo($type, $id)
{
if (!$id) {
return false;
}
$header_ = $this->returnHeader();
$url = $header_['host'] . "/v2/sp/$type/" . $id;
$res = $this->sendCurlGetRequest($url, $header_['header']);
$res = json_decode($res, true);
$this->local_log($res);
if ( $res['status'] === 'SUCCESS' ) {
$data = $this->reportDownload($res['location']);
echo file_get_contents($data);
$this->local_log($data);
}
}
// 下载报告
public function reportDownload($location)
{
if (!$location) {
return false;
}
$header = $this->returnHeader()['header'];
$redirect_url = $this->sendCurlGetRequest($location, $header);
if ($redirect_url) {
$file = $this->downloadFile($redirect_url);
$path = $file["save_path"];
$size = $file["file_size"];
if($size == 0)
{
// 删除文件
$this->delFile($path);
return false;
}
// 解压
$res = $this->unzipGz($path);
return $res;
}
die;
}
public function downloadFile($url, $save_dir = '.')
{
if ( !$url ) {
return false;
}
global $id;
$filename = date("Y-m-d")."_".$id.".json.gz";
//创建保存目录
if (!file_exists($save_dir) && !mkdir($save_dir, 0777, true))
{
echo "目录失败";
return false;
}
$file = $save_dir."/". $filename;
ob_start();
readfile($url);
$content = ob_get_contents();
ob_end_clean();
//文件大小
$size = strlen($content);
$fp2 = @fopen($file, 'a');
fwrite($fp2, $content);
fclose($fp2);
unset($content, $url);
$return = array(
'save_path' => $file,
'file_size' => $size
);
return $return ;
}
// 解压GZ文件
public function unzipGz($path)
{
if (!file_exists($path)) {
return false;
}
$buffer_size = 4096; // read 4kb at a time
$out_file_name = str_replace('.gz', '', $path);
$file = gzopen($path, 'rb');
$out_file = fopen($out_file_name, 'wb');
$str='';
while(!gzeof($file)) {
fwrite($out_file, gzread($file, $buffer_size));
}
fclose($out_file);
gzclose($file);
$this->delFile($path);
return $out_file_name;
}
// 删除文件
public function delFile($path)
{
file_exists($path) && unlink($path);
return ;
}
// 返回公共请求头
public function returnHeader()
{
switch ( $this->region ) {
case 'NA':
$host = 'https://advertising-api.amazon.com';
break;
case 'EU':
$host = 'https://advertising-api-eu.amazon.com';
break;
case 'FE':
$host = 'https://advertising-api-fe.amazon.com';
break;
default:
$host = "https://advertising-api.amazon.com";
break;
}
$header = array(
"Authorization: Bearer $this->token",
"Amazon-Advertising-API-ClientId:$this->client_id",
"Access-Control-Allow-Credentials: true",
"Content-Type:application/json",
"User-Agent:test1",
"Amazon-Advertising-API-Scope:$this->profile_id"
);
return array('host'=>$host, 'header'=>$header);
}
// 模拟post请求
public function sendCurlPostRequest($url, $curlData, $header)
{
$curl = curl_init();
curl_setopt ($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl,CURLOPT_TIMEOUT,120);
curl_setopt($curl,CURLOPT_ENCODING,'gzip');
curl_setopt($curl,CURLOPT_HTTPHEADER,$header);
curl_setopt ($curl, CURLOPT_POST, 1);
curl_setopt ($curl, CURLOPT_POSTFIELDS, $curlData);
// https请求 不验证证书和hosts
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
$result = curl_exec($curl);
if ( $result == false ){
var_dump(curl_error($curl));
die('CURL ERROR');
}
curl_close ($curl);
return $result;
}
// 模拟get请求
public function sendCurlGetRequest($url, $headers){
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_URL,$url);
if($headers){
curl_setopt($ch, CURLOPT_HTTPHEADER,$headers);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
// https请求 不验证证书和hosts
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$output = curl_exec($ch);
if ( $output == false ){
var_dump(curl_error($ch));
curl_close($ch);
die('CURL ERROR');
}
$redirect_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
curl_close($ch);
// 判断重定向
if ( $url != $redirect_url ) {
return $redirect_url;
}
return $output;
}
// 本地打印结果
public function local_log($msg)
{
if (!is_string($msg)) {
$msg = json_encode($msg);
}
file_put_contents('local_log.log', date("Y-m-d H:i:s") . "\t" . $msg . "\n\n" .PHP_EOL, FILE_APPEND);
}
到这就基本结束了,剩下的就是自己的业物逻辑处理了。 上面这个方法基本都是只写了一半, 都只是先获取数据 。现在我也还没彻底完成这个功能,只是先记录一下API的请求过程。防止过两天就忘了…毕竟刚完成的印象比较深刻。
欢迎互相交流…