本篇文章更新時間:2020/04/27
如有資訊過時或語誤之處,歡迎使用 Contact 功能通知。
一介資男的 LINE 社群開站囉!歡迎入群聊聊~
如果本站內容對你有幫助,歡迎使用 BFX Pay 加密貨幣 或 新台幣 贊助支持。
2019 年 Google 發一則 公告 表示即將停用 AdSense 手機 App 。前天又來發信通知,索性就開始研究 API ,想把手機版報表功能移植到 LINE Notify 定時通知。 Ref: [PHP] LINE Notify 應用於行政流程的方法(範例)
一開始看 API 文件還以為是 AdSense Host API 這個,結果串好看回應表示我不是 Host 帳號XD 一查才知道 Host 帳號是指 Blogger 或 痞客邦 這樣使用子網域的內容平台申請者,可以透過這種類型帳號幫旗下客戶申請廣告聯播網功能。Ref: Google API: Customer is not an AdSense Host
執行結果如下圖:
內容目錄
前置準備
- 一組完整權限的 Google AdSense 帳號
- 一個網站伺服器與可以使用的網域
- 看得懂文件與改點 PHP 程式的能力
申請 Google APIs 與使用憑證相關資料
開始前要先到 Google Cloud Platform 中啟用 AdSense Management API。
確認啟用後再到「憑證」建立一組 OAuth 用戶端 ID,類型為網路應用程式。申請過程中會需要驗證網域的就先去驗證,然後把「已授權的 JavaScript 來源」填入含 HTTP 協議的網址,例如: https://www.mxp.tw
,而「已授權的重新導向 URI」則填入授權後跳轉回去的應用程式路徑,例如:https://www.mxp.tw/adsense/callback.php
申請 OAuth 這一步是建立一個規範角色,確保參與 OAuth 協議過程中都能遵守申請的設定,完成授權。所以會需要指定授權的網域以及回傳接收資料的位置!針對 OAuth 協議的細節可以參考這份文件:Using OAuth 2.0 to Access Google APIs
申請完這步驟後會得到一組「OAuth 2.0 用戶端編號與密碼」,這些都不用記,把這組資訊下載 JSON 檔案到電腦中先暫存著。
程式設計邏輯
官方範例其實已經夠詳細,稍微 Google 一下就找到 AdSense Management API sample for PHP 。 這專案寫了很多範例,但都不是本篇要的,僅是提供串接參考。
除非環境的限制,例如 PHP 版本過低或硬是不想用 Google 那一大包的函式庫,不然絕對是用他的那包方便!(想用 cURL 自己串的可以看這邊:Google oAuth 2.0 PHP Curl Boilerplate)
首先要安裝 Google APIs Client Library for PHP 這包客戶端工具,非常推薦使用 Composer
套件安裝方式,裝好後只需要在程式中引用一行即可。
require_once __DIR__ . '/vendor/autoload.php';
使用 Google APIs 客戶端工具分為兩大方向:
- Google Client OAuth 2.0 交握取得授權
- 拿著取得的授權開始使用 API 服務
Google Client OAuth 2.0 交握取得授權
所以取得授權的程式如下:
$client = new Google_Client();
$client->addScope('https://www.googleapis.com/auth/adsense.readonly');
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$client->setAuthConfig('/path/to/client_secrets.json');
AuthConfig
就是剛剛下載的客戶端編號等相關設定 JSON 檔案路徑。
然後輸出組合出前往授權的網址:
echo '綁定';
打開瀏覽器點擊此連結登入 Google 帳號後會到 Google 授權服務的頁面,一系列確認完成授權後即轉往先前設定的那個服務頁面接收授權 code
。
if (isset($_GET['code'])) {
$client->authenticate($_GET['code']);
// Note that "getAccessToken" actually retrieves both the access and refresh
// tokens, assuming both are available.
$access_token = $client->getAccessToken();
$refresh_token = $client->getRefreshToken();
file_put_contents(TOKEN_FILENAME, json_encode(array(
'access_token' => $access_token,
'refresh_token' => $refresh_token,
'expires_time' => intval($access_token['expires_in']) + intval($access_token['created']) - 30,
)));
}
Google_Client
物件收到 GET 方法取得的 code
後完成授權,即可取得接下來需要的授權碼。範例中僅是使用 PHP Session 方法記錄,我則是改成寫入一個檔案裡暫存,用來之後離線還可以請求。
如果有離線使用的需求,關鍵是要設定這兩行
$client->setAccessType('offline');
與$client->setApprovalPrompt('force');
。然後會取得一個refresh_token
更新 Access Token 的 Token,之後當 Access Token 過期時可以拿這個refresh_token
去換過一組新的。換 Access Token 的方法直接寫在下方完整程式碼的章節。
使用取得的授權開始使用 API 服務
將授權過的客戶端物件帶入 AdSense 的方法來呼叫。
$service = new Google_Service_AdSense($client);
然後就能用這個 Google_Service_AdSense
物件開始回傳報表囉!
$t = time();
$FMD = date('Y-m-01', $t);
$TODAY = date('Y-m-d', $t);
$account_id = 'pub-你的 AdSense 編號';
$result = $service->accounts_reports->generate(
$account_id,
$FMD,
$TODAY,
array(
'metric' => array("EARNINGS", "PAGE_VIEWS", "PAGE_VIEWS_RPM", "MATCHED_AD_REQUESTS", "CLICKS", "COST_PER_CLICK", "AD_REQUESTS_COVERAGE"),
'dimension' => array('DOMAIN_NAME', 'DATE'),
'useTimezoneReporting' => true,
)
);
這個方法是「取得網站月初到今日的每日營收相關報表」。指標和維度的參數可以參考連結文件說明。
取得每日的資訊後,要做什麼統計資訊就完全看個人了!我則是參考 App 裡常用的「今日累計」與「本月累計」。各別網站的查看「涵蓋率」也是能抓出廣告放送狀態是否正常。
程式組裝
上述都算是片段的程式解析,並非完全部分,完整的程式邏輯還有些設計,如下:
addScope('https://www.googleapis.com/auth/adsense.readonly');
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$client->setAuthConfig('/PATH/TO/YOUR/client_secrets.json');
$auth = "";
$access_token = "";
$refresh_token = "";
$service = new Google_Service_AdSense($client);
function gapp_refresh_token($client, $auth) {
$params = array(
"client_id" => $client->getClientId(),
"client_secret" => $client->getClientSecret(),
"refresh_token" => $auth['refresh_token'],
"grant_type" => "refresh_token",
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, 'https://accounts.google.com/o/oauth2/token');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$r = curl_exec($ch);
curl_close($ch);
$g_res = json_decode($r, true);
$access_token = $g_res['access_token'];
$expires_in = $g_res['expires_in'];
$scope = $g_res['scope'];
$auth['access_token']['access_token'] = $access_token;
$auth['access_token']['created'] = time();
$auth['expires_time'] = intval($expires_in) + time() - 30;
file_put_contents(TOKEN_FILENAME, json_encode($auth));
$client->setAccessToken($auth['access_token']);
return $client;
}
if (isset($_GET['code'])) {
$client->authenticate($_GET['code']);
$access_token = $client->getAccessToken();
$refresh_token = $client->getRefreshToken();
file_put_contents(TOKEN_FILENAME, json_encode(array(
'access_token' => $access_token,
'refresh_token' => $refresh_token,
'expires_time' => intval($access_token['expires_in']) + intval($access_token['created']) - 30,
)));
}
if (file_exists(TOKEN_FILENAME) && filesize(TOKEN_FILENAME) > 0) {
$auth = json_decode(file_get_contents(TOKEN_FILENAME), true);
//還沒過期
if (time() < $auth['expires_time']) {
$client->setAccessToken($auth['access_token']);
$access_token = $client->getAccessToken();
} else {
//過期了去刷過
if (isset($auth['refresh_token']) && !empty($auth['refresh_token'])) {
$client = gapp_refresh_token($client, $auth);
} else {
echo '綁定';
exit;
}
}
} else {
echo '綁定';
exit;
}
//到這邊就是授權狀態中,可以開始請求
$t = time();
$FMD = date('Y-m-01', $t);
$TODAY = date('Y-m-d', $t);
$account_id = 'pub-YOUR_PUB_ID';
$result = $service->accounts_reports->generate(
$account_id,
$FMD,
$TODAY,
array(
'metric' => array("EARNINGS", "PAGE_VIEWS", "PAGE_VIEWS_RPM", "MATCHED_AD_REQUESTS", "CLICKS", "COST_PER_CLICK", "AD_REQUESTS_COVERAGE"),
'dimension' => array('DOMAIN_NAME', 'DATE'),
'useTimezoneReporting' => true,
)
);
$rows = $result['rows'];
$domain = array();
foreach ($rows as $key => $row) {
if (!isset($domain[$row[0]])) {
if (in_array($row[0], array('www.mxp.tw'))) {
$domain[$row[0]] = array();
$domain[$row[0]][] = array('DATE' => $row[1], 'EARNINGS' => $row[2], 'PAGE_VIEWS' => $row[3], 'PAGE_VIEWS_RPM' => $row[4], 'MATCHED_AD_REQUESTS' => $row[5], 'CLICKS' => $row[6], 'COST_PER_CLICK' => $row[7], 'AD_REQUESTS_COVERAGE' => $row[8]);
}
} else {
if (in_array($row[0], array('www.mxp.tw'))) {
$domain[$row[0]][] = array('DATE' => $row[1], 'EARNINGS' => $row[2], 'PAGE_VIEWS' => $row[3], 'PAGE_VIEWS_RPM' => $row[4], 'MATCHED_AD_REQUESTS' => $row[5], 'CLICKS' => $row[6], 'COST_PER_CLICK' => $row[7], 'AD_REQUESTS_COVERAGE' => $row[8]);
}
}
}
$total = array();
foreach ($domain as $key => $value) {
$total[$key] = array();
$days = count($value);
//累計收入
$total[$key]['T_EARNINGS'] = 0;
$total[$key]['AVG_EARNINGS'] = 0;
//累計瀏覽數
$total[$key]['T_PAGE_VIEWS'] = 0;
$total[$key]['AVG_PAGE_VIEWS'] = 0;
//累計廣告曝光量
$total[$key]['T_MATCHED_AD_REQUESTS'] = 0;
$total[$key]['AVG_MATCHED_AD_REQUESTS'] = 0;
//累計點擊數
$total[$key]['T_CLICKS'] = 0;
$total[$key]['AVG_CLICKS'] = 0;
//累計點擊數
$total[$key]['T_COST_PER_CLICK'] = 0;
$total[$key]['AVG_COST_PER_CLICK'] = 0;
//累計涵蓋率
$total[$key]['T_AD_REQUESTS_COVERAGE'] = 0;
$total[$key]['AVG_AD_REQUESTS_COVERAGE'] = 0;
//累計千次瀏覽收益
$total[$key]['T_PAGE_VIEWS_RPM'] = 0;
$total[$key]['AVG_PAGE_VIEWS_RPM'] = 0;
foreach ($value as $index => $data) {
$total[$key]['T_EARNINGS'] += floatval($data['EARNINGS']);
$total[$key]['T_PAGE_VIEWS'] += intval($data['PAGE_VIEWS']);
$total[$key]['T_CLICKS'] += intval($data['CLICKS']);
$total[$key]['T_MATCHED_AD_REQUESTS'] += intval($data['MATCHED_AD_REQUESTS']);
$total[$key]['T_COST_PER_CLICK'] += floatval($data['COST_PER_CLICK']);
$total[$key]['T_AD_REQUESTS_COVERAGE'] += floatval($data['AD_REQUESTS_COVERAGE']);
$total[$key]['T_PAGE_VIEWS_RPM'] += floatval($data['PAGE_VIEWS_RPM']);
if ($index == ($days - 1)) {
$total[$key]['TODAY'] = $data;
}
}
//平均收入
$total[$key]['AVG_EARNINGS'] = $total[$key]['T_EARNINGS'] / $days;
//平均瀏覽數
$total[$key]['AVG_PAGE_VIEWS'] = $total[$key]['T_PAGE_VIEWS'] / $days;
//平均點擊數
$total[$key]['AVG_CLICKS'] = $total[$key]['T_CLICKS'] / $days;
//平均曝光量
$total[$key]['AVG_MATCHED_AD_REQUESTS'] = $total[$key]['T_MATCHED_AD_REQUESTS'] / $days;
//平均CPC
$total[$key]['AVG_COST_PER_CLICK'] = floatval($total[$key]['T_COST_PER_CLICK'] / $days);
//平均涵蓋率
$total[$key]['AVG_AD_REQUESTS_COVERAGE'] = floatval($total[$key]['T_AD_REQUESTS_COVERAGE'] / $days);
//平均CPM
$total[$key]['AVG_PAGE_VIEWS_RPM'] = floatval($total[$key]['T_PAGE_VIEWS_RPM'] / $days);
}
foreach ($total as $domain => $data) {
$separator = str_repeat('=', 10) . PHP_EOL;
$tday = $data['TODAY'];
$str = "網域:" . $domain . "({$tday['DATE']})" . PHP_EOL;
$str .= "今日收益:$" . $tday['EARNINGS'] . "USD" . PHP_EOL;
$str .= "今日瀏覽:" . $tday['PAGE_VIEWS'] . PHP_EOL;
$str .= "今日廣告曝光量:" . $tday['MATCHED_AD_REQUESTS'] . PHP_EOL;
$str .= "今日點擊:" . $tday['CLICKS'] . PHP_EOL;
$str .= "今日CPC:$" . $tday['COST_PER_CLICK'] . PHP_EOL;
$str .= "今日CPM:$" . $tday['PAGE_VIEWS_RPM'] . PHP_EOL;
$str .= "今日覆蓋率:" . ($tday['AD_REQUESTS_COVERAGE'] * 100) . "%" . PHP_EOL;
$str .= $separator . $FMD . " -> " . $TODAY . PHP_EOL . $separator;
//累計收入
$str .= "累計收入:$" . $data['T_EARNINGS'];
$str .= "(平均:$" . round($data['AVG_EARNINGS'], 2) . ")USD" . PHP_EOL;
//累計瀏覽數
$str .= "累計瀏覽數:" . $data['T_PAGE_VIEWS'];
$str .= "(平均:" . round($data['AVG_PAGE_VIEWS'], 2) . ")" . PHP_EOL;
//累計廣告曝光量
$str .= "累計廣告曝光量:" . $data['T_MATCHED_AD_REQUESTS'];
$str .= "(平均:" . round($data['AVG_MATCHED_AD_REQUESTS'], 2) . ")" . PHP_EOL;
//累計點擊數
$str .= "累計點擊數:" . $data['T_CLICKS'];
$str .= "(平均:" . round($data['AVG_CLICKS'], 2) . ")" . PHP_EOL;
//平均CPC
// $str .= "平均CPC:".$data['T_COST_PER_CLICK'];
$str .= "平均CPC:$" . round($data['AVG_COST_PER_CLICK'], 2) . " USD" . PHP_EOL;
$str .= "平均CPM:$" . round($data['AVG_PAGE_VIEWS_RPM'], 2) . " USD" . PHP_EOL;
//累計涵蓋率
// $str .= "".$data['T_AD_REQUESTS_COVERAGE'];
$str .= "平均涵蓋率:" . round($data['AVG_AD_REQUESTS_COVERAGE'], 2) * 100 . "%" . PHP_EOL;
$d = new DateTime(date('Y-m-d H:i:s'));
$d->setTimeZone(new DateTimeZone('Asia/Taipei'));
//台灣時間1,10,13,19,23才通知
if (intval($d->format('H')) == 1 ||
intval($d->format('H')) == 10 ||
intval($d->format('H')) == 13 ||
intval($d->format('H')) == 19 ||
intval($d->format('H')) == 23) {
line_notify($str);
}
}
function line_notify($msg) {
if ($msg == "") {
return;
}
$body = array(
'message' => PHP_EOL . $msg,
);
$headers = array(
'Content-Type: application/x-www-form-urlencoded',
'Authorization: Bearer YOUR_LINE_NOTIFY_TOKEN',
);
$url = 'https://notify-api.line.me/api/notify';
$ch = curl_init();
$params = array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => TRUE,
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13',
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => http_build_query($body),
);
curl_setopt_array($ch, $params);
if (!$result = curl_exec($ch)) {
if ($errno = curl_errno($ch)) {
$error_message = curl_strerror($errno);
error_log("cURL error ({$errno}):\n {$error_message}");
curl_close($ch);
return FALSE;
}
} else {
curl_close($ch);
}
}
Gist: Link
修改程式使用只要注意環境(PHP 版本大於 5.2 ,有 cURL 模組、安裝 Google Client 客戶端函式庫的路徑)與對應程式內參數的調整(改成自己的),大概不會有什麼問題。
後記
也多虧之前為了要串 Google 分析的資料研究過( [WordPress] 從 Google Analytics 匯入網站人氣的外掛組合技)所以這次很快就上手。
另外相對 AdSense 簡單報表需要的資料也不多,真的有很複雜的需求還是得打開網頁版查詢。
上述程式只有第一次授權的情況需要打開瀏覽器,等取得授權後就可以設定主機的 Cron job ,來定時執行。
主機上我設定每小時執行一次,讓他的 Token 固定有更新。然後把傳送通知的方法寫固定某幾個小時才通知。
*/60 * * * * /usr/bin/curl "https://www.mxp.tw/adsense/callback.php" > /dev/null 2>&1
要來翻一下去年的講義抗抗怎ㄇ弄ㄌ