カルテットコミュニケーションズで開発しているLisketには、アカウントCSV作成ツールというツールがあります。このツールを使うと、以下のようなことができます。
例えば、以下のようにある程度規則的な構成であれば、数万行規模のCSVでも非常に簡単に作成することができます。
- 1広告グループ1キーワード構成
- 各広告グループに広告をA/Bの2パターン作成
- 各広告文は、統一的な文言で、広告グループごとにキーワードを挿入する
このように、何らかの基礎データを元に、規則的にデータを生成する処理のコアとして、Haydnというエンジンを開発しました。Haydnは現在、OSS(二条項BSDライセンス)で公開しています。
今回は、Haydnを使うとどのようにデータの生成規則を記述して実行できるのかを、簡単な実例で解説します。
前提
例えば元となるデータとして、次のようなものがあったとします。
- 1つめのキーワード群:リスティング、リスティング広告、PPC、PPC広告
- 2つめのキーワード群:効率化、工数削減、自動化
- キーワードのマッチタイプの種類:部分一致、完全一致
- 全広告グループで配信する広告:「おまかせ!」「安心」
このようなキーワード検索広告を入札する時、どのような設定のパフォーマンスが良いのかを後から細かく確認したり、また、調整・改善するためには、とりたい情報がとれるように入札データを細かく区切っておく必要があります。そのために検索広告では「広告グループ」というまとまりが使われます。
余談ですが、このようなポイントをデータに仕込んでおいて改善に使うという活動は、プログラミングにおいて、(レガシーコード改善ガイドの言葉で)コードにさまざまな接合部を用意していくことと似ています。
例1では、1つめのキーワードと2つめのキーワードを掛け合わせたキーワード(例:リスティング 効率化
など)を個別に検証したいので、1つ1つが別の広告グループになるようにしています。各広告グループには、1つのキーワードと、2つの広告があるというデータを出力したいわけです。
例1
Haydnの基本機能であるproduct()
とunion()
以外に、SQLでいうJOIN的な処理を行うためのGroupingSet
を使います。GroupingSet
のコンストラクタは、次のように4つの引数をとります。
- 基準とするSet
- 基準とするSet1件ごとに出力するヘッダ行定義
- 基準とするSet1件ごとに実行する、Set生成演算定義。
- 基準とするSet1件ごとに出力するフッタ行定義
<?php
use Quartet\Haydn\IO\Source\SingleColumnArraySource;
use Quartet\Haydn\IO\Source\SingleRowSource;
use Quartet\Haydn\Set;
require_once __DIR__.'/vendor/autoload.php';
$words1 = ['リスティング', 'リスティング広告', 'PPC', 'PPC広告'];
$words2 = ['効率化', '工数削減', '自動化'];
$groupPatterns = ['部分一致', '完全一致'];
$adPatterns = ['おまかせ!', '安心'];
$words1Set = new Set(new SingleColumnArraySource('k1', $words1));
$words2Set = new Set(new SingleColumnArraySource('k2', $words2));
$groupPatternsSet = new Set(new SingleColumnArraySource('g', $groupPatterns));
$adPatternsSet = new Set(new SingleColumnArraySource('ad', $adPatterns));
$grouoAndWords = $groupPatternsSet->product($words1Set)->product($words2Set);
$all = new Set\GroupingSet($grouoAndWords,
function ($row) {
return [
'type' => 'adgroup',
'value' => $row
];
},
function ($row) use ($adPatternsSet) {
$rowSet = new Set(new SingleRowSource('k', $row));
$rowSet = $rowSet->select([function($row) {
return [
'type' => 'keyword',
'value' => $row
];
}]);
$adSet = $adPatternsSet->select([function($row) {
return [
'type' => 'ad',
'value' => $row
];
}]);
return $rowSet->union($adSet);
},
null
);
foreach ($all as $row) {
switch ($row['type']) {
case 'adgroup':
echo '広告グループ:' . $row['value']['k1'] . '-' . $row['value']['k2'] . 'Gr_' . $row['value']['g'] . PHP_EOL;
break;
case 'keyword':
echo ' キーワード:' . $row['value']['k1'] . ' ' . $row['value']['k2'] . PHP_EOL;
break;
case 'ad':
echo ' 広告:' . $row['value']['ad'] . PHP_EOL;
break;
}
}
Haydnでは、処理対象のデータの中身にはできるだけ立ち入らないという特徴があります。上のコードの23〜49行目の部分が、HaydnのGroupingSetを使ってデータの生成を定義しているところになりますが、どのデータとどのデータを掛けて、足して、という定義をしているのみになっています。
Haydn で定義したオブジェクトを foreach
などでイテレートしたときに、初めて内部の演算が実行されますが、GroupingSetで定義したデータは階層構造を持たずシンプルな1件のデータなので、52〜64行目のように単純なswitchにより出力の出し分けを記述できます。
実行すると、次のように出力されます。
広告グループ:リスティング-効率化Gr_部分一致
キーワード:リスティング 効率化
広告:おまかせ!
広告:安心
広告グループ:リスティング-工数削減Gr_部分一致
キーワード:リスティング 工数削減
広告:おまかせ!
広告:安心
広告グループ:リスティング-自動化Gr_部分一致
キーワード:リスティング 自動化
広告:おまかせ!
広告:安心
広告グループ:リスティング広告-効率化Gr_部分一致
キーワード:リスティング広告 効率化
広告:おまかせ!
広告:安心
広告グループ:リスティング広告-工数削減Gr_部分一致
キーワード:リスティング広告 工数削減
広告:おまかせ!
広告:安心
広告グループ:リスティング広告-自動化Gr_部分一致
キーワード:リスティング広告 自動化
広告:おまかせ!
広告:安心
広告グループ:PPC-効率化Gr_部分一致
キーワード:PPC 効率化
広告:おまかせ!
広告:安心
広告グループ:PPC-工数削減Gr_部分一致
キーワード:PPC 工数削減
広告:おまかせ!
広告:安心
広告グループ:PPC-自動化Gr_部分一致
キーワード:PPC 自動化
広告:おまかせ!
広告:安心
広告グループ:PPC広告-効率化Gr_部分一致
キーワード:PPC広告 効率化
広告:おまかせ!
広告:安心
広告グループ:PPC広告-工数削減Gr_部分一致
キーワード:PPC広告 工数削減
広告:おまかせ!
広告:安心
広告グループ:PPC広告-自動化Gr_部分一致
キーワード:PPC広告 自動化
広告:おまかせ!
広告:安心
広告グループ:リスティング-効率化Gr_完全一致
キーワード:リスティング 効率化
広告:おまかせ!
広告:安心
広告グループ:リスティング-工数削減Gr_完全一致
キーワード:リスティング 工数削減
広告:おまかせ!
広告:安心
広告グループ:リスティング-自動化Gr_完全一致
キーワード:リスティング 自動化
広告:おまかせ!
広告:安心
広告グループ:リスティング広告-効率化Gr_完全一致
キーワード:リスティング広告 効率化
広告:おまかせ!
広告:安心
広告グループ:リスティング広告-工数削減Gr_完全一致
キーワード:リスティング広告 工数削減
広告:おまかせ!
広告:安心
広告グループ:リスティング広告-自動化Gr_完全一致
キーワード:リスティング広告 自動化
広告:おまかせ!
広告:安心
広告グループ:PPC-効率化Gr_完全一致
キーワード:PPC 効率化
広告:おまかせ!
広告:安心
広告グループ:PPC-工数削減Gr_完全一致
キーワード:PPC 工数削減
広告:おまかせ!
広告:安心
広告グループ:PPC-自動化Gr_完全一致
キーワード:PPC 自動化
広告:おまかせ!
広告:安心
広告グループ:PPC広告-効率化Gr_完全一致
キーワード:PPC広告 効率化
広告:おまかせ!
広告:安心
広告グループ:PPC広告-工数削減Gr_完全一致
キーワード:PPC広告 工数削減
広告:おまかせ!
広告:安心
広告グループ:PPC広告-自動化Gr_完全一致
キーワード:PPC広告 自動化
広告:おまかせ!
広告:安心
例2
例2では、掛け合わせキーワードすべてを個別に検証するのではなく、主にキーワード1によるパフォーマンスの違いを検証したいため、キーワード1でのみ広告グループを作成し、各グループに掛け合わせキーワードが3つずつ登録されたデータになります。
コードは最初とほとんど同じですが、次のようになります。
<?php
use Quartet\Haydn\IO\Source\SingleColumnArraySource;
use Quartet\Haydn\IO\Source\SingleRowSource;
use Quartet\Haydn\Set;
require_once __DIR__.'/vendor/autoload.php';
$words1 = ['リスティング', 'リスティング広告', 'PPC', 'PPC広告'];
$words2 = ['効率化', '工数削減', '自動化'];
$groupPatterns = ['部分一致', '完全一致'];
$adPatterns = ['おまかせ!', '安心'];
$words1Set = new Set(new SingleColumnArraySource('k1', $words1));
$words2Set = new Set(new SingleColumnArraySource('k2', $words2));
$groupPatternsSet = new Set(new SingleColumnArraySource('g', $groupPatterns));
$adPatternsSet = new Set(new SingleColumnArraySource('ad', $adPatterns));
$grouoAndWords = $groupPatternsSet->product($words1Set);
$all = new Set\GroupingSet($grouoAndWords,
function ($row) {
return [
'type' => 'adgroup',
'value' => $row
];
},
function ($row) use ($words2Set, $adPatternsSet) {
$rowSet = new Set(new SingleRowSource('k', $row));
$rowSet = $rowSet->product($words2Set);
$rowSet = $rowSet->select([function($row) {
return [
'type' => 'keyword',
'value' => $row
];
}]);
$adSet = $adPatternsSet->select([function($row) {
return [
'type' => 'ad',
'value' => $row
];
}]);
return $rowSet->union($adSet);
},
null
);
foreach ($all as $row) {
switch ($row['type']) {
case 'adgroup':
echo '広告グループ:' . $row['value']['k1'] . 'Gr_' . $row['value']['g'] . PHP_EOL;
break;
case 'keyword':
echo ' キーワード:' . $row['value']['k1'] . ' ' . $row['value']['k2'] . PHP_EOL;
break;
case 'ad':
echo ' 広告:' . $row['value']['ad'] . PHP_EOL;
break;
}
}
実行すると、次のように出力されます。
広告グループ:リスティングGr_部分一致
キーワード:リスティング 効率化
キーワード:リスティング 工数削減
キーワード:リスティング 自動化
広告:おまかせ!
広告:安心
広告グループ:リスティング広告Gr_部分一致
キーワード:リスティング広告 効率化
キーワード:リスティング広告 工数削減
キーワード:リスティング広告 自動化
広告:おまかせ!
広告:安心
広告グループ:PPCGr_部分一致
キーワード:PPC 効率化
キーワード:PPC 工数削減
キーワード:PPC 自動化
広告:おまかせ!
広告:安心
広告グループ:PPC広告Gr_部分一致
キーワード:PPC広告 効率化
キーワード:PPC広告 工数削減
キーワード:PPC広告 自動化
広告:おまかせ!
広告:安心
広告グループ:リスティングGr_完全一致
キーワード:リスティング 効率化
キーワード:リスティング 工数削減
キーワード:リスティング 自動化
広告:おまかせ!
広告:安心
広告グループ:リスティング広告Gr_完全一致
キーワード:リスティング広告 効率化
キーワード:リスティング広告 工数削減
キーワード:リスティング広告 自動化
広告:おまかせ!
広告:安心
広告グループ:PPCGr_完全一致
キーワード:PPC 効率化
キーワード:PPC 工数削減
キーワード:PPC 自動化
広告:おまかせ!
広告:安心
広告グループ:PPC広告Gr_完全一致
キーワード:PPC広告 効率化
キーワード:PPC広告 工数削減
キーワード:PPC広告 自動化
広告:おまかせ!
広告:安心
おわりに
LisketのアカウントCSV作成ツールには、実際に現場で使える細やかなパターンを生成できるよう、もっと複雑な生成規則になっています。そのような複雑な生成規則でも、人間がルール化できる規則であれば、それとあまり変わらない複雑度で生成規則を記述できています。
単純にメモリに乗った配列をイテレートするだけなら、パフォーマンスの面で組み込みのforeach
等には勝てません。しかし、今回のように何らかの演算を行う場合、Haydnではイテレートと演算がオンデマンドで実行されるので、メモリにはある程度やさしい作りになります。
また、演算がオンデマンドで遅延実行されるので、HTTPレスポンスをStreamで返すような仕組みとの相性も良くなります。このあたりはまた次回、サンプルで解説したいと思います。
Haydn自体は今後、定義をもっとスッキリと記述するための改善や、Linq系ライブラリによくある操作のサポート、定義自体のデバッグやテストのサポートなど改良していく予定です。
使用レポートやissue/PRお待ちしております!