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操作...}// 刷新 tokenpublic 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_idpublic 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请求 不验证证书和hostscurl_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请求 不验证证书和hostscurl_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的请求过程。防止过两天就忘了…毕竟刚完成的印象比较深刻。
欢迎互相交流…