【PHP】Yahoo!広告APIでオフラインコンバージョンをアップロードしたい！

#はじめに

運用担当の方から、

**「Yahoo広告で、おふらいんこんばーじょんを自動でアップロードしたい」**

と言われました。

**おふらいんこんばーじょんって何ぞ！！？**

そういやちょっと前にGoogleでもなんかやったな？
スプレッドシートに出力するってやつ。
※詳細は[【PHP】Google Sheets APIをサンプルプログラム無しで使いたい！！](https://qiita.com/azasaz/items/051f2cc1646fe13a8710)参照

たしかあのときはgclidのついたものを抽出してました。
今回はyclidのついたものを取り出すんだそうです。
ふんふん、ぐーぐるだからGでやふーだからYなのね。。。くらいしか分からん。

慌ててググって解説などを読みました。
参考：[Google 広告のオフラインコンバージョントラッキングの概要と使い方](https://anagrams.jp/blog/listing_knowhow/offline-conversion-tracking-for-google-ads/)

コンバージョンに結びつくユーザーの回遊行動を記録し、
分析に役立てるための仕組みらしい。
まあ要は、一意になるIDが付与されてるから、それを取ってアップすれば、
GoogleやYahooが細かい分析までしてくれるんだね。
大変便利。（わかったようなわかってないような）

ということで、今回はYahoo検索広告APIでオフラインコンバージョンのアップロードに挑戦！

2021/5/20現在、Yahooディスプレイ広告のAPIは
オフラインコンバージョンのアップロードに対応してないっぽい？

#オフラインコンバージョンデータを扱うOfflineConversionService

オフラインコンバージョンを操作するために、ちゃんとリソースが用意されています。
それが、OfflineConversionServiceです。

[リファレンスはこちら](https://ads-developers.yahoo.co.jp/reference/ads-search-api/v4/OfflineConversionService/)

アップロードするのはuploadってやつかなあ。
ざっと見た感じ、そんなトリッキーなことは必要なさそうだから簡単そう・・・かなあ。

##ヘッダー情報をセット

ではまず、
Yahoo検索広告でAPIを使うためには、
まず２段階認証でアクセストークンを取ってくるところから。
詳細は[過去記事](https://qiita.com/azasaz/items/0f31f81aa8525acfc5bd#%E3%83%87%E3%83%BC%E3%82%BF%E5%8F%96%E5%BE%97oauth2%E8%AA%8D%E8%A8%BC)参照のこと

アクセストークンが取得できたら、ヘッダーにセットします。

ここで注意する点はContent-Type。
レポートデータなどの取得の際はJSON形式でデータを受け取ってたけど、
今回はmultipartであるという点。

ドキュメントにそのように書いてありますの。。。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566860/6b9e7d14-45fb-0495-c276-46c4c972a152.png)

ということで、言われたとおりにマルチパートでセット。

```
//ヘッダー情報をセット
$_header = [
    "Content-Type: multipart/form-data;"
    "Accept: application/json",
    "Authorization: Bearer $access_token", 
];
```

さて、それでは肝心の、アップロードするファイルの中身。

##通常パターン
テンプレートのCSVファイルを編集したものを、
プログラム内から読み込んでAPIにアップロードします。

テンプレートは、管理画面のメニュー「ツール」→「コンバージョン測定」からの
「オフラインコンバージョンのインポート」の画面でダウンロードできます。

これが必要な項目。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566860/9c223cf1-6644-3a28-de5e-fc0aa783399d.png)

ヘッダー情報は消さないこと。

このファイルに必要な内容を追加します。全項目必須です。

例えばこんなかんじに値を入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566860/b8f600ce-05f3-4878-9f65-70e86f926ea2.png)

発生日時はフォーマットに注意。

これをプログラムから参照できる任意の場所に置きます。
ファイル名は何でもいい。今回はtest_template.csvとします。

```
//CSVファイルを文字列として取得する
//CSVファイルなのでこれの文字列はSJIS
$_paramstr = file_get_contents("test_template.csv");
```


いままでやってきたレポート関係の[ReportDefinitionService](https://ads-developers.yahoo.co.jp/reference/ads-search-api/v4/ReportDefinitionService/)や
マスタ系の[CampaignService](https://ads-developers.yahoo.co.jp/reference/ads-search-api/v4/CampaignService/)やら[AdGroupService](https://ads-developers.yahoo.co.jp/reference/ads-search-api/v4/AdGroupService/)やらは
パラメータなしでリクエストボディだけ入れてCURLしてたんだけど、
今回のOfflineConversionService.upload()は**パラメータもリクエストボディも必要**とのこと。
※OfflineConversionServiceの中でもget()やdownload()はリクエストボディだけ。
　upload()だけ両方必要です

ということで、両方指定してみます。。
2021/5/20現在の最新バージョンはv4。

```
//API用パラメータ配列
//CSVファイル名が必要なので、今回は[YmdHis]_test.csvとする
$_timestamps = date("YmdHis");
$param_upload = [
    "accountId" => $accountid,
    "uploadType" => "NEW",
    "uploadFileName" => $_timestamps."_test.csv",
];

//add用URL
$_url_upload = "https://ads-search.yahooapis.jp/api/v4/OfflineConversionService/upload?".http_build_query($param_upload);

//curl START
$_curl = curl_init();

//OPTIONをセット
curl_setopt_array($_curl, [
    CURLOPT_URL => $_url_upload,
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => $_header,
    CURLOPT_POSTFIELDS => ["file" => $paramstr],
]);

//curl EXEC(文字列で取得)
$_resp_upload = curl_exec($_curl);

//エラーハンドリング用
$_errno = curl_errno($_curl);

//curl END
curl_close($_curl);

//エラーハンドリング
if ($_errno !== CURLE_OK) {
	//エラー処理
}

$_result_upload = json_decode($_resp_upload, true);   //json文字列を連想配列化
$_return = is_array($_result_upload["rval"]["values"]) ? $_result_upload["rval"]["values"] : [];
```

アップロードが完了すると、このようなレスポンスが返ってくるはず。。

>["errors"]=> NULL 
>["offlineConversion"]=> 
>array(7) { 
>　["accountId"]=> int(9999999) 
>　["uploadId"]=> int(123456) 
>　["uploadFileName"]=> string(23) "20210518144945_test.csv" 
>　["uploadType"]=> string(3) "NEW" 
>　["uploadedDate"]=> string(19) "2021/05/18 14:49:45" 
>　["uploadSourceType"]=> string(3) "API" 
>　["processStatus"]=> string(10) "PROCESSING"
>} 
>["operationSucceeded"]=> bool(true) 

管理画面を確認してみると、ちゃんとアップはされています。
ファイルの内容はエラーになってるけど。。。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566860/d1dbbf09-13c3-e979-316f-39879287d1d7.png)

レスポンスで返ってくるoperationSucceededは、
API的に無事アップロードできたかどうかのフラグなので、
リクエストに問題なくてファイルをアップロードができれば
基本的にすべてTRUEで返ってくるみたい。

アップロードしたファイルがチェックでOKかNGかは別問題なので、
アップロードが終わったら管理画面できちんと確認しましょう。

※レスポンスではprocessStatusがPROCESSINGとなっているけど、
すぐにチェックが走るので、
プログラム実行してから管理画面で見ると処理は終わってる。。

またはAPIでOfflineConversionService.get()でもファイル状況が確認できます。

ちなみに、
get()は**アップロードしたファイル一覧を取得するものであって、
ファイルの中身を見ることはできません**。
ファイルの中身を見たい場合は、download()を使います。
その場合、アップロードIDというのが必要になります。
アップロードIDはget()でデータを取ると見ることができます。

なので、APIからファイルの中身を確認する場合は

>OfflineConversionService.get()でアップロードIDを確認
>↓
>アップロードIDを指定してOfflineConversionService.download()

という手順が必要になります。

管理画面をすぐ見れる環境にあるなら、管理画面見たほうが早いかも。


##CSVファイルの中身をプログラム内で書きたい場合

上の例はCSVファイルをプログラム内に取り込みましたが、
私がやりたいのは**CSVファイルの中身自体をプログラムの中で書きたい。。**ってことなので、
そちらのやり方も書いておきます。

```
//これがCSVファイルの中身とする
//ヘッダー忘れずにつける
$_csvarray = [
	["YCLID", "コンバージョン名", "コンバージョン発生日時","1コンバージョンあたりの価値","通貨コード"],
	["YSS.1234567890.Abcdefghijklmnop","test1","20210501 021000","8000","JPY"],
	["YSS.1234567890.Abcdefghijklmnopqrstuvwxyz","test2","20210501 072100","10000","JPY"],
	["YSS.1234567899.Ghijklmnop","test3","20210501 073800","10000","JPY"]
];

//CSVファイルはSJISなのでエンコードする
$_paramstr = "";
foreach ($_csvarray as $_lineval) {
	if (!is_array($_lineval)) continue;
	$_linestr = implode(",", $_lineval);
	$_paramstr .= mb_convert_encoding($_linestr, 'SJIS', 'UTF-8')."\r\n";//改行コードを一緒にエンコードすると認識されなくなるので注意！
}
```

あとは上と同じようにCURLで渡してやればOK。

はじめ、文字列編集したものを一気にSJISにエンコードしていたため、
ファイルの中身を認識してもらえなくて
悲しいエラーメッセージが続いて
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566860/b75bc48d-9064-a047-4eb4-0687c50e8401.png)

２日ほど繰り返してポッキリ心が折れたわけで、
運用担当の方からYahoo担当の方に問い合わせをお願いしてみたのだけど、
悲しい返事が返ってきて（まあ予想通りだったけど）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566860/4a651adc-ccac-9946-cc8c-fc0a274f683c.png)


まあそうだよねそうだとは思ってたよ知ってた！！！！

と気を取り直して、心を無にしてやってみたら出来たのでした。。
まあ改行を認識してくれてないんだなってのはなんとなく想像できてたから
全く手がかりがなかったわけでもなかったしね。

できてよかった。。。

なお、ここからさらに私は、
アップロードしたい内容をスプレッドシートに出力し、
スプレッドシートの情報を読み込んでアップロードするという
エキストリームフロー（いうほどそうでもない）に突入しました。

GoogleAPIを使ってYahooAPIにアップするとか
今いったい何やってるんだろうと思ったような思わないような。

まあでもアップする形式さえ理解できればもう怖くないっすね。

#おわりに

今回のOfflineConversionServiceを使うに当たりググってみたところ、
PHPではなかったけど使用例を上げて頂いてるページがありまして。

参考：[Yahoo検索広告APIを使ってJavaでオフラインコンバージョンのアップロードをしてみる](https://note.com/saitopass/n/n0ff930d52cc6)

他に参考がまったくない状態だったのでとてもとても助かったのですが。

へぇ～
バウンダリ入れなきゃいけないんだ？
ヘッダーとは別でBodyにContent-Disposition入れなきゃいけないんだ？？
バイト文字列にしなきゃいけないんだ？？？

とか思ってなるほどなるほどとプログラムを作ってみて、
思いっきりエラーになりまくってたわけで。。

どうもバージョンが違ったせいかな？
仕様がかわったのかもしれません。

v4ではBodyにはCSVファイルの中身だけを入れればいいし、
バウンダリも必要ないし、SJISにエンコードさえすれば文字変換もしなくていい。

APIってものは仕様が変わってくのが常なので、
バージョンが新しくなったら影響ないか逐次確認が必要ですね。

とはいえ使ったこともないリソースを検索情報もなしに手探りで使うのはツライので、
上のドキュメントには本当に助けられました。感謝！！

特にAPI関係はググってもあまり情報が出てこないことが多いので、
自分の知りえた情報をなるべくアップしていくのが大切だなあと思いました。

API利用者の技術者のみなさん、ともに頑張りましょう。