初めての本格的n8nワークフロー ⑤

n8nによる「本格的ワークフロー」のハンズオン第5回です。
今回はLoop処理内で処理中のカレントデータを、Loop内で共用できるレコードのように扱っていく方法を見ていきましょう。

前回までで「記事ごとにページにアクセスして情報を取得する」を取得できるところまで来ました。ここまでのワークフローの構築で、既に「欲しい情報」は全て取得できるようになっています。

  • ブログサイトにリクエストして、サムネイルページのHTMLを取得
  • 記事一覧のHTMLを解析し、記事群のメタ情報(タイトル・URL・公開日)を配列に取得
  • ループを回して記事ごとに本文ページにアクセス
  • ループ内で記事ごとにHTMLを解析し、本文・カテゴリ・執筆者を取得

ここまでで作成したワークフローは下図のようになっているはずです。

ここからは、取得できている情報を、活用していく方法を見ていきます。
今回は、情報をより取り扱いやすくするために、Make Fieldsノードを用いて、Loop内でデータを「レコード」として扱っていく方法を見ていきます。

Edit Fieldsノードを使う理由

このハンズオンでは、ここまでのところ、データは「ノードの処理結果」としてノードに紐づいているデータとして、入力値・出力値を取り扱うのみでした。n8nでは、処理済みノードの出力値に $('前処理ノード名') でアクセスできるため、基本的にはそれで本質的に困ることはありません。

ただ、細かく情報処理をしていくにあたっては、ノード単位ではなく「記事」という単位で情報を集約したくなります。各データにノードを介してアクセスはできるものの、現状のワークフローでは、記事の情報が下記のように「散っている」状況です。

■Loop外

  • タイトル・URL・公開日:『Code arrange field』の出力値として配列保有
  • 本文・カテゴリ・執筆者:『Loop Over Items』の出力値として配列保有

■Loop内(記事ごと)

  • タイトル・URL・公開日:『Loop Over Items』のloop側に出力
  • 本文・カテゴリ・執筆者:『HTML Parse-each』の出力値として保有

このままでも、データを扱うことは可能ですが、「記事」の単位で情報を扱っていきたい場面では、見通しが悪く取り扱いにくい格好になります。物理的なデータアクセスは可能ですが、プログラミングの世界に慣れてきているエンジニアとしては、概念として「記事」単位のレコードが欲しくなるところです。

そこで、「記事」の塊としてデータをセットするために、Edit Fieldsノードを活用します。 

 

10. Edit Fieldsノード:記事データの集約

Edit Fields ノードを設置してみましょう。

Loop Over Items の loop側に渡される、『タイトル・URL・公開日』をもらってレコードを作成するために、Loop Over Items と HTTP Req-each の間にノードを設置していきます。

例によって設定ダイアログが開くので、Add Field をクリックして下記のように設定していきましょう。

  • name:articleTitle
  • type:String
  • {‍{ $json.articleTitle }}
  • name:articleURL
  • type:String
  • {‍{ $json.articleURL }}
  • name:articleDate
  • type:String
  • {‍{ $json.articleDate }}
  • name:objDate
  • type:String
  • {‍{ $json.objDate }}

ここでは、loopから入力された値を、そのまま同じ名前で引き継いでいるだけですが、記事単体のデータを「ここで一度集めてレコードっぽくしている」という「概念」をもたせることができて、ワークフローの見通しを良くすることができます。

試運転してみて、下記のように、右辺に記事の情報が引き継がれていればOKです。

ここまでで、ワークフローは以下のようになりました。

Loop Over Itms と HTTP Req-each の間に Make Fieldを挟みましたが、Make Fieldではloopの入力値をそのまま出力値として引き継いでいるので、HTTP Req-each 側は特に変更すること無くこれまでどおり動作します。

11. 2つめのEdit Fieldsノード:記事データの集約2

Loop内での各記事の情報取得で得られたカテゴリ・執筆者・本文を、上記で設置した「概念上の記事レコード」にさらに集約していってみましょう。

Edit Fieldをもう1つ、Loop内の最後、HTML Parse-eachにつなげる形で設置します。

このEdit Fieldは下記の様に設定していきましょう。

  • name:articleTitle
  • type:String
  • {‍{ $('Make Field').item.json.articleTitle }}
  • name:articleURL
  • type:String
  • {‍{ $('Make Field').item.json.articleURL}}
  • name:articleDate
  • type:String
  • {‍{ $('Make Field').item.json.articleDate }}
  • name:objDate
  • type:String
  • {‍{ $('Make Field').item.json.objDate }}
  • name:ArticleAuther
  • type:String
  • {‍{ $json.ArticleAuther }}
  • name:ArticleCategory
  • type:String
  • {‍{ $json.ArticleCategory }}
  • name:ArticleContent
  • type:String
  • {‍{ $json.ArticleContent }}

タイトル(articleTitle)、記事URL(articleURL)、公開日(articleDate, objDate)は、先程1つ目のEdit Fieldノード として作った 'Make Field'から値をもらってきます。
過去のノードの出力値なので、$('Make Field').item.json.**** で参照します。

執筆者(ArticleAuther)、カテゴリ(ArticleCategory)、記事本文(ArticleContent)は直前ノードで取得されているので、$jsonから取得できます。

この設定で試運転してみて、下記のように右辺に目的に値が出力されていればOKです。

各ノードの出力に散っていた値を、1つのレコードのようにまとめて、後続ノードに出力できていることが分かります。
この2つ目のEditノードは、Loop内処理の最後のノードであるため、この出力はLoopの出力配列にそのままPushされることになります。

ワークフローは以下のようになりました。

結果を見てみる

Loop over Items の出力先のCodeノードのプログラムを少し編集して、結果を見てみましょう。

const customOutput = [];

for (const item of $input.all()) {
  const cuOut = {};
  const cuIn = item.json;
  
  cuOut["articleTitle"] = cuIn["articleTitle"];
  cuOut["articleURL"] = cuIn["articleURL"];
  cuOut["articleDate"] = cuIn["articleDate"];

  cuOut["ArticleContentHead"] = cuIn["ArticleContent"].substr(0,100);
  cuOut["ArticleCategory"] = cuIn["ArticleCategory"];
  cuOut["ArticleAuther"] = cuIn["ArticleAuther"];

  customOutput.push(cuOut);
}

return customOutput;

前回までの内容だと、Loop内のHTML Parse-each の出力値(本文・カテゴリ・執筆者)しか扱えていませんでしたが、今回はEditFieldsにより値が集約されたので、Loopの出力値として、記事レコード概念のデータを全部(タイトル・URL・公開日・執筆者・カテゴリ・本文)取り扱えるようになっています。

試運転してみると、右辺に「記事の情報」が集約されて出力されていることが分かります。

もうひと工夫してみる

現状では『記事本文の頭出し』の100文字切り出しを、loop外のCodeノードで行っていますが、この処理もLoopの中に入れてみましょう。
同様にLoop内にCodeノードを設置してもよいのですが、簡単な内容であればEdit Fieldsの中で対応できてしまいます。

キャンバスでEdieFieldsをダブルクリックして設定ダイアログを開き、下記を追加(Add Field)してみましょう。

  • name:ArticleContentHeadsUp
  • type:String
  • {‍{ $json.ArticleContent.substr(0,100) }}

この内容で試運転してみると、下記のように「100文字分切り出した」新しいフィールドが追加されている事がわかります。

この状態で、先程のLoop外のCodeも、値自体の加工はせず出力対象の選択のみに絞ったシンプルなコードに変更します。

const customOutput = [];
const showTarget = [
  'articleTitle',
  'articleURL',
  'articleDate',
  'ArticleContentHeadsUp',
  'ArticleCategory',
  'ArticleAuther'
];
  
for (const item of $input.all()) {
  const cuOut = {};
  const cuIn = item.json;

  for(const target of showTarget){
    cuOut[target] = cuIn[target];
  }  

  customOutput.push(cuOut);
}

return customOutput;

結果を見てみると以下のようになります。

このように、各ノードの役割を明確にし、それぞれは複雑にしないこともポイントになります。

全体系での実行の様子は下記です。最終のCodeノードの出力がLogでも意図したとおりになっていることが見て取れます。

今回はここまでにしましょう。
次回はAIを用いた本文の要約を取り上げていきます。

  • トリガーの設置【Done】
  • 特定のURLから内容を取得する ▶ HTTP Request ノード【Done】
  • HTMLから要素を部会・取得する ▶ HTMLノード【Done】
  • 配列データの扱い方と暗黙的ループ処理【Done】
  • コードによる自由な処理 ▶ Codeノード【Done】
  • 明示的なループ処理 ▶ Loopノード【Done】
  • フィールド生成・制御 ▶ MakeFieldsノード【Done:今回解説】
  • AIノードへの情報連携 ▶ Basic LLM Chain → NEXT
  • 処理済みの任意ノードの値の活用する【Done:今回追加で解説】
  • Googleスプレッドシートへのレコード出力 ▶ Google Sheetノード
  • Google Sheet API への アクセス許可の手順 ▶ Google Sheets account 設定