n8nでせどり商品選定の自動化④(完全版)

n8n

はじめに

今までの要素を組み合わせて、せどり商品選定の自動化ワークフローの完全版を作成します。
ワークフロー完全版の全体図は下記となります。

<ワークフロー全体図>

本ワークフローを実行すると、下記ようなCSVファイルが生成されます。

<CSVファイルのサンプル>

CSVファイルの中身は、ChatGPTが選定した商品をYahoo!ショッピング、楽天市場、eBayで検索し、その結果が記録されています。CSVファイルの各カラムには下記の情報が記載されています。

  • キーワード
    各ECサイトで検索時に使用した単語
  • 商品名
    検索でヒットした商品のタイトル
  • 店舗名
    出品店舗・ショップ名
  • 商品説明
    商品の短い説明文。説明が取得できない場合は空欄
  • 価格
    表示価格の数値。eBayはドル価格
  • 検索サイト
    どのECサイトの検索結果を示す(Yahoo/楽天/eBay)
  • 商品ページ
    商品詳細ページのURL。
  • 検索日時
    その行(商品)を取得した時刻(JST)

ワークフローの動作概要

作成するワークフローの動作概要は下記となります。

事前に用意しておく情報

各サイトのAPI

本手順でワークフローを作成する前に、下記の情報を用意しておく必要があります。

  1. eBayのアクセストークン、リフレッシュトークン、認証コード
    ※認証コードは、ClientIDとClientを組み合わせてBase64コード
  2. YahooショッピングのAPIキー
  3. 楽天市場のAPIキー

上記の情報の取得方法は下記を参照してください。

n8nでせどり商品選定の自動化③(各ECサイトのAPI取得) – リラックスした生活を過ごすために

ebayのアクセストークンを保存したCSVファイル

ebayのアクセストークンの有効期限は120分です。
アクセストークンはeBayのデベロッパーサイトにアクセスすれば取得可能ですが、ワークフローを実行する前に、毎回取得するのは手間が掛かります。

アクセストークンは、リフレッシュトークンを用いることで取得することも可能です。
ワークフロー内でebayのアクセストークンの有効期限を確認し、有効期限が切れそうな場合はリフレッシュトークンでアクセストークンを取得するようにしています。

アクセストークンの有効期限、アクセストークンをリフレッシュトークンで取得するための情報を下記のCSVファイルに保存します。

<CSVファイルのパス>

C:\せどり商品検索の自動化\アクセストークン\ebay_アクセストークン.csv

「ebay_アクセストークン.csv」には下記カラムで情報を記載してください。

  • access_token:取得したアクセストークン
  • expires_in:7200
  • token_type:User Access Token
  • token_acquired_at:取得した時刻
  • auth_basic:Basic 取得した認証コード
  • refresh_token:取得したリフレッシュトークン

「auth_basic」には認証コードを設定します。
認証コードを記入する際は「Basic VHN1~~」のように「Basic」を認証コードの前に付けてください。

<ebay_アクセストークン.csvのサンプル>

ワークフローの作成手順

「ebay_アクセストークン.csv」を「C:/せどり商品検索の自動化/アクセストークン/ebay_アクセストークン.csv」に配置した前提で進めます。

ワークフローの作成手順

ワークフローの作成手順は下記を参照してください。

n8nでせどり商品選定の自動化①(n8nの実行環境の構築) – リラックスした生活を過ごすために

「ebayトークン管理」の作成手順

1.「Add first step…」をクリックします

2.「Search nodes…」に「Read」を入力し、「Read/Write Files from Disk」をクリックします。

3.「Read Files(s) From Disk」をクリックする

4.追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:ebayアクセストークン読み込み1
  • File(s) Selector:C:/せどり商品検索の自動化/アクセストークン/ebay_アクセストークン.csv
    ※ファイルパスは「¥」ではなく「/」で区切ります。

5.「ebayアクセストークン1読み込み」の「+」をクリックし「Search nodes…」に「Extract」を入力し、「Extract from File」をクリックします。

6.「Extract from CSV」をクリックします。

7.追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:ebay アクセストークン抽出1
  • Add option:Header RowをOn

8.「ebay アクセストークン抽出1」の「+」をクリックし「Search nodes…」に「if」を入力し、「If」をクリックします。

9.Ifノードでは「ebay アクセストークンを取得した時刻から何分経ったか」を計算し、しきい値と比較してTrue/Falseで処理を分岐しています。

ebay アクセストークンの有効期限は120分です。現在の時刻とアクセストークンを取得した時刻の差分(取得からの経過時間)が110分以内であれば、Trueと判定しています。True側は後続処理に進みます。False側はリフレッシュトークンで新しいアクセストークン取得の処理に進みます。

追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:ebayアクセストークン取得日時の判定
  • fx:{{ Math.floor((Date.now() – Date.parse($json.token_acquired_at)) / 60000) }}
  • is less than or equal to
  • 110

10.「ebayアクセストークン取得日時の判定」の「false」の「+」をクリックし「Search nodes…」に「http」を入力し、「HTTP Request」をクリックします。

11.HTTP Requestノードでは「ebayのリフレッシュトークンを使って access_token を再発行する」を行います。

追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ebayアクセストークン取得
  • Method:POST
  • URL:https://api.ebay.com/identity/v1/oauth2/token
  • Authentication:None
  • Send Header:ON
  • Specify Headers:Using Fields Below
  • Header Parameters
    • Name:Content-Type
    • Value:application/x-www-form-urlencoded
      ※「Add Parameter」をクリックすると項目を追加できます
    • Name:Authorization
    • Value:{{ $json.auth_basic }}
  • Send Body:ON
  • Body Content Type:Form Urlencoded
  • Specify Body:Using Fields Below
  • Body Parameters
    • Name:grant_type
    • Value:refresh_token
      ※「Add Parameter」をクリックすると項目を追加できます
    • Name:refresh_token
    • Value:{{ $json.refresh_token }}

12.「ebayアクセストークン取得」の「+」をクリックし「Search nodes…」に「code」を入力し、「Code」をクリックします。

13.ebayアクセストークンの取得時刻を設定します。
追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:ebayアクセストークン取得時刻を追加
  • JavaScript:下記を記載
    clientId、clientSecret、refreshTokenは事前に取得しておいてください。
// === 設定 ==========================
const clientId     = 'XXXXXX-MySedori-PRD-XXXXX-XXXXXX';
const clientSecret = 'PRD-XXXXXX-XXXXX-XXXXXX-XXXXX-XXXXX';
const refreshToken = 'v^1.1#i^XXXXXXXXXXXXXXXXXX';
// ==================================

// 既存のデータ(HTTP Request のレスポンス)を取得
const inputData = items[0]?.json ?? {};

// JST時刻を付与(yyyy-MM-dd HH:mm:ss)
const jstTime = new Date(Date.now() + 9 * 60 * 60 * 1000)
  .toISOString().replace('T', ' ').substring(0, 19);

// Basic 認証文字列を生成("Basic xxxxxx")
const auth_basic = 'Basic ' + Buffer.from(`${clientId}:${clientSecret}`).toString('base64');

// 出力データを作成
const outputData = {
  ...inputData,                // access_token / expires_in など元の応答
  token_acquired_at: jstTime,  // 取得時刻(JST)
  auth_basic,                  // Basic 認証ヘッダ値
  refresh_token: refreshToken  // リフレッシュトークン
};

// 1件で返す
return [{ json: outputData }];

14.「ebayアクセストークン取得時刻を追加」の「+」をクリックし「Search nodes…」に「convert」を入力し、「Convert to File」をクリックします。

15.「Convert to CSV」をクリックします。

16.ebayアクセストークンと取得時刻をCSV形式に変換します。
追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:ebayアクセストークンのCSV形式に変換
  • Options:Add option」をクリックして下記を追加
    • Delimiter: ,

17.「ebayアクセストークンのCSV形式に変換」の「+」をクリックし「Search nodes…」に「write」を入力し、「Read/Write Files from Disk」をクリックします。

18.「Read File(s) From Disk」をクリックします。

19.CSV形式のデータを保存します。
追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:ebayアクセストークンの保存
  • Operationdw」で「Write File to Disk」を選択

20.「ebayアクセストークンの保存」の右「●」をドラックして「ebayアクセストークン読み込み1」の左「●」でドロップし、線を繋げます。

以上で「ebayトークン管理」の作成手順は完了です。

「商品検索候補の生成」の作成手順

1.AI Agentによる商品選定のフローを作成します。
作成方法は下記を参照してください。

n8nでせどり商品選定の自動化②(AI Agentによる商品選定) – リラックスした生活を過ごすために

「Yahooショッピングの商品検索」の作成手順

1.「JSON形式のデータを配列に変換」の右「●」をクリックし「Search nodes…」に「loop」を入力し、「Loop Over Items(Split in Batches)」をクリックします。

2.ループ設定の変更はありません。
追加したノードの「Back to canvas」をクリックします。

3.不要な「Replace Me」ノードをクリックし「ゴミ箱」アイコンをクリックして削除します。

4.不要なループを削除します。
「Loop Over Items」の下から出ているループにカーソルを合わせて「ゴミ箱」をクリックします。

5.Yahooショッピングで商品検索を実行するためのノードを作成します。
「Loop Over Items」の下「+」をクリックし「Search nodes…」に「http」を入力し、「HTTP
Request」をクリックします。

6.YahooショッピングのAPI検索設定を行います。
追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:Yahoo API検索
  • URLに下記を入力します。
https://shopping.yahooapis.jp/ShoppingWebService/V3/itemSearch?appid=【取得したYahoo API】&query={{ encodeURIComponent($json.キーワード) }}&results={{ $json.検索数 }}

※Yahoo APIの取得方法は前回の記事を参照してください。

以上で「Yahooショッピング」の商品検索の作成手順は完了です。

「楽天市場の商品検索」の作成手順

1.楽天市場で商品検索を実行するためのノードを作成します。
「Loop Over Items」の下「+」をクリックし「Search nodes…」に「code」を入力し、「Code」をクリックします。

2.楽天市場でAPI検索を行うための前処理を行います。
楽天市場のAPI検索で「iphone 12」を検索すると失敗します。

「iphone 12」を単語単位で分解すると「キーワード」+「スペース」+「数字」の形式になっています。この「キーワード」+「スペース」+「数字」の形式だと、楽天市場のAPI検索は失敗してしまいます。

楽天市場のAPI検索をする際は「スペース」を削除した「キーワード」+「数字」に変換する必要があります。

<失敗するキーワードの例>

  • iphone 12
  • Mx Anywhere 3

<成功するキーワードの例>

  • iphone12
  • MxAnywhere3

追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:楽天API検索用キーワード生成
  • JavaScriptに下記を入力します。
// 楽天API用:「スペース+数字」問題の解決

const originalKeyword = $json.キーワード;

// 「アルファベット/カタカナ + スペース + 数字」を「アルファベット/カタカナ + 数字」に変換
const fixedKeyword = originalKeyword
  .replace(/([a-zA-Z\u30A0-\u30FF])\s+(\d)/g, '$1$2')  // Master 3 → Master3
  .replace(/\s+/g, ' ')                                 // 余分な空白を整理
  .trim();

return items.map(item => ({
  json: {
    ...item.json,
    fixedKeyword: fixedKeyword
  }
}));
// 楽天API用:「スペース+数字」問題の解決

const originalKeyword = $json.キーワード;

// 「アルファベット/カタカナ + スペース + 数字」を「アルファベット/カタカナ + 数字」に変換
const fixedKeyword = originalKeyword
  .replace(/([a-zA-Z\u30A0-\u30FF])\s+(\d)/g, '$1$2')  // Master 3 → Master3
  .replace(/\s+/g, ' ')                                 // 余分な空白を整理
  .trim();

return items.map(item => ({
  json: {
    ...item.json,
    fixedKeyword: fixedKeyword
  }
}));

3.楽天市場で商品検索を実行するためのノードを作成します。
「楽天API検索用キーワード生成」の「+」をクリックし「Search nodes…」に「http」を入力し、「HTTP Request」をクリックします。

4.楽天市場のAPI検索設定を行います。
追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:楽天API検索
  • URLに下記を入力します。
https://app.rakuten.co.jp/services/api/IchibaItem/Search/20220601?format=json&keyword={{ encodeURIComponent($json.fixedKeyword) }}&hits={{ $json.検索数 }}&applicationId=【取得した楽天API】/

※楽天APIの取得方法は前回の記事を参照してください。

以上で「楽天市場の商品検索」の作成手順は完了です。

「ebayの商品検索」の作成手順

1.ebayでAPI検索を行うための前処理を行います。
「Loop Over Items」の下「+」をクリックし「Search nodes…」に「read」を入力し、「Read/write Files from Disk」をクリックします。

2.「Read Files(s) From Disk」をクリックします。

3.追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:ebayアクセストークン読み込み2
  • File(s) Selector:C:/せどり商品検索の自動化/アクセストークン/ebay_アクセストークン.csv

4.「ebayアクセストークン読み込み2」の「+」をクリックし「Search nodes…」に「Extract」を入力し「Extract from File」をクリックします。

5.「Extract from CSV」をクリックします。

6.追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:ebay アクセストークン抽出2
  • Add option:Header Row」を選択

7.ebayAPI検索で必要なキーワードとAPI情報を結合します。
「Loop Over Items」の下「+」をクリックし「Search nodes…」に「merge」を入力し、「Merge」をクリックします。

8.追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:ebayAPI検索用の情報生成
  • Mode:Combine
  • Combine by:Position

9.抽出したebayアクセストークンとキーワードを結合します。
「ebay アクセストークン抽出2」の「●」をドラックし「ebayAPI検索用の情報生成」の「Input2」にドロップします。

10.ebayで商品検索を実行するためのノードを作成します。
「ebayAPI検索用の情報生成」の「+」をクリックし「Search nodes…」に「http」を入力し、「HTTP Request」をクリックします。

11.ebayのAPI検索設定を行います。
追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:ebayAPI検索
  • URLに下記を入力します。
https://api.ebay.com/buy/browse/v1/item_summary/search
  • Send Query Parameters:On
    • Name:q
    • Value:{{ $json[‘keyword’] }}
  • 「Add Parameter」をクリック
    • Name:limit
    • Value:{{ $json[‘検索数’] }}
  • Send Headers:On
    • Name:Authorization
    • Value:Bearer {{ $json[‘access_token’] }}

※ebayAPIの取得方法は前回の記事を参照してください。

以上で「ebayの商品検索」の作成手順は完了です。

3サイトの検索結果の結合

1.各サイトのAPI検索で取得した情報をを結合します。
「Loop Over Items」の下「+」をクリックし「Search nodes…」に「merge」を入力し、「Merge」をクリックします。

2.追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:各サイトの検索結果のまとめ
  • Number of Inputs:4

3.「Yahoo API検索」「楽天API検索」「ebayAPI検索」の「●」をドラックし「各サイトの検索結果のまとめ」のInput2~4に順々にドロップします。

4.取得した各サイトの検索結果を下記のフォーマットに整理するノードを作成します。

  • キーワード、商品名、店舗名、商品説明、価格、価格サイト、商品ページ、検索日時

「各サイトの検索結果のまとめ」の「+」をクリックし「Search nodes…」に「code」を入力し、「Code」をクリックします。

5.追加したノードに下記を適用して「Back to canvas」をクリックします。

  • コード名:取得情報の整理
  • 「JavaScript」に下記を設定
const results = [];

// ---------- キーワード抽出ユーティリティ ----------
function pickKeywordFromJson(j) {
  const str = v => (typeof v === 'string' ? v.trim() : null);
  const cands = [
    j['キーワード'],
    j.keyword,
    j.request?.query,              // Yahoo等
    j.request?.q,                  // eBay等
    j.request?.params?.keyword,    // 楽天等
    j.request?.params?.q,
    j.query,
    j.q,
    j.input?.keyword,
    j.input?.['キーワード'],
  ];
  for (const v of cands) {
    const s = str(v);
    if (s) return s;
  }
  // request が配列の場合の保険
  if (Array.isArray(j.request)) {
    for (const r of j.request) {
      const s = str(r?.query || r?.q || r?.params?.keyword || r?.params?.q);
      if (s) return s;
    }
  }
  return null;
}

// 全アイテムを走査して最初に見つかったキーワードを採用
let keyword = null;
for (let i = items.length - 1; i >= 0; i--) { // 後ろから探す
  const k = pickKeywordFromJson(items[i].json || {});
  if (k) {
    keyword = k;
    console.log('Keyword found at index', i, ':', k);
    break;
  }
}
if (!keyword) keyword = 'N/A';

// ---------- 日本時間 ----------
const searchDatetime = new Date().toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' });

// ---------- 各APIの結果処理 ----------
let yahooProcessed = false;
let rakutenProcessed = false;
let ebayProcessed = false;

for (const item of items) {
  const json = item.json || {};

  // Yahoo
  if (Array.isArray(json.hits)) {
    console.log('Processing Yahoo data, hits:', json.hits.length);
    yahooProcessed = true;

    if (json.hits.length > 0) {
      for (const hit of json.hits) {
        results.push({
          キーワード: keyword,
          商品名: hit.name || '',
          店舗名: hit.seller?.name || '',
          商品説明: (hit.description || '').replace(/
/g, ' ').replace(/<[^>]*>/g, ''),
          価格: hit.price ?? '',
          検索サイト: 'Yahoo',
          商品ページ: hit.url || '',
          検索日時: searchDatetime,
        });
      }
    } else {
      results.push({
        キーワード: keyword,
        商品名: '',
        店舗名: '',
        商品説明: '',
        価格: '',
        検索サイト: 'Yahoo',
        商品ページ: '',
        検索日時: searchDatetime,
      });
    }
    continue;
  }

  // 楽天
  if (Array.isArray(json.Items)) {
    console.log('Processing Rakuten data, items:', json.Items.length);
    rakutenProcessed = true;

    if (json.Items.length > 0) {
      for (const wrapper of json.Items) {
        const hit = wrapper.Item || {};
        results.push({
          キーワード: keyword,
          商品名: hit.itemName || '',
          店舗名: hit.shopName || '',
          商品説明: (hit.itemCaption || '').replace(/
/g, ' ').replace(/<[^>]*>/g, ''),
          価格: hit.itemPrice ?? '',
          検索サイト: '楽天',
          商品ページ: hit.itemUrl || '',
          検索日時: searchDatetime,
        });
      }
    } else {
      results.push({
        キーワード: keyword,
        商品名: '',
        店舗名: '',
        商品説明: '',
        価格: '',
        検索サイト: '楽天',
        商品ページ: '',
        検索日時: searchDatetime,
      });
    }
    continue;
  }

  // eBay
  if (Array.isArray(json.itemSummaries)) {
    console.log('Processing eBay data, items:', json.itemSummaries.length);
    ebayProcessed = true;

    if (json.itemSummaries.length > 0) {
      for (const hit of json.itemSummaries) {
        console.log('eBay item:', hit.title, hit.seller?.username, hit.price?.value);
        results.push({
          キーワード: keyword,
          商品名: hit.title || '',
          店舗名: hit.seller?.username || '',
          商品説明: '', // eBay は説明未取得
          価格: hit.price?.value ?? '',
          検索サイト: 'eBay',
          商品ページ: hit.itemWebUrl || '',
          検索日時: searchDatetime,
        });
      }
    } else {
      results.push({
        キーワード: keyword,
        商品名: '',
        店舗名: '',
        商品説明: '',
        価格: '',
        検索サイト: 'eBay',
        商品ページ: '',
        検索日時: searchDatetime,
      });
    }
    continue;
  }

  // 不明な構造のログ(参考)
  if (Object.keys(json).length > 0) {
    console.log('Unknown data structure keys:', Object.keys(json));
  }
}

// どのAPIも処理されなかった場合のフォールバック
if (!yahooProcessed && !rakutenProcessed && !ebayProcessed) {
  console.log('No API data found, create empty records.');
  for (const site of ['Yahoo', '楽天', 'eBay']) {
    results.push({
      キーワード: keyword,
      商品名: '',
      店舗名: '',
      商品説明: '',
      価格: '',
      検索サイト: site,
      商品ページ: '',
      検索日時: searchDatetime,
    });
  }
}

console.log('Total results generated:', results.length);
if (results[0]) console.log('Sample result:', results[0]);

return results.map(r => ({ json: r }));

6.各サイトに負荷を掛けないように待ち時間を設定します。
「取得情報の整理」の「+」をクリックし「Search nodes…」に「wait」を入力し、「Wait」をクリックします。

7.追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:待ち
  • Wait Amount:2

8.検索条件の内容で繰り返し検索するようにします。
「待ち」の「●」をドラックし「Loop Over Items」の左「■」にドロップします。

以上で3サイトの検索結果の結合は完了です。

取得情報のCSVファイルへ保存

1.取得情報をCSVファイルに保存します。
「Loop Over Items」の上「+」をクリックし「Search nodes…」に「convert」を入力し「Convert to File」をクリックします。

2.「Convert to CSV」をクリックします。

3.追加したノードに下記を適用して「Back to canvas」をクリックします。

  • コード名:取得情報をCSV形式に変換
  • Add option:Delimiter

4.「取得情報をCSV形式に変換」の「+」をクリックし「Search nodes…」に「write」を入力し「Read/write Files from Disk」をクリックします。

5.「Write File to Disk」をクリックします。

6.追加したノードに下記を適用して「Back to canvas」をクリックします。

  • ノード名:取得情報をCSV形式で保存
  • 「File Path and Name」に下記を設定
C:/せどり商品検索の自動化/検索結果/せどり_分析_{{ $now.plus({hours: 13}).format('yyyy-MM-dd_HHmmss') }}.csv

以上で取得情報のCSVファイルへ保存は完了です。

ワークフローの実行

「Excute workflow」をクリックすると作成したワークフローを実行します。
実行後にエラーが発生しているノードが無いことを確認します。

実行が完了すると検索結果が下記ファイルに保存されます。

C:\せどり商品検索の自動化\検索結果\せどり_分析_yyyy_mm_dd_hhMMss.csv

<せどり_分析のサンプル>

以上です。

コメント

タイトルとURLをコピーしました