カルテットコミュニケーションズで開発している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

掛け合わせ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つずつ登録されたデータになります。

掛け合わせ2

コードは最初とほとんど同じですが、次のようになります。

<?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お待ちしております!