見出し画像

【実務ユースケース有】自動車価格情報収集ツール作ってみた

初めに

Hikaruです~ ゾス。 株式会社Pineal(https://pineal.co.jp)という会社でインターンをしています。
開発した業務効率化ツールをnoteやXにて発信していますので、気になる方は是非ご覧下さい!


筆者プロフィール
氏名:Hikaru  (X : @Hikaru_pineal)
年齢:23歳
所属:早稲田大学大学院創造理工学研究科1年
専門分野:資源工学×機械学習
長期インターン:株式会社pineal  (http://pineal.co.jp)
職種:生成AIアプリケーション開発 & DXリスキリング研修


今回は、大手メーカーさんから委託され、情報収集ツールを作ってみました。実際に業務内で使われているようです。

作成時間:1 hour
作成難易度: 1.5/5.0


スプレッドシート操作感

概要

自動車の販売価格が掲載された以下の2つのサイト(プロモーションサイト、標準販売価格サイト)から、必要な情報らを抜き出し、スプレッドシートに出力します。

抜き出す情報
・プロモーション価格(車名:価格)
・標準販売価格(車名:価格)

Promotion site
Normal price site

効果

・慣例的な情報収集&整理作業を効率化できます。
・情報収集が簡易化されるので、情報漏れが無くなります。


ツールフロー

  1. 2つのサイト情報をスクレイピング(情報抜き出し)し、必要情報を抜き出すワークフローをDifyで作ります。

  2. 作ったDifyのワークフローと、スプレッドシートを連携するために、GASを使います。

    • ワークフローのAPIを用いる(スプシからDifyに入力渡す→Dify出力受け取る→スプシに出力する)

      • スプレッドシートのセル上に置いてあるURLをDifyの入力変数として渡します。

      • Difyワークフローの出力を受け取り、出力情報を適材適所でスプシに書き出します。

Difyでワークフローを作る

Difyワークフロー

このワークフローは、2つのURL(「プロモーション情報」と「標準価格情報」)を入力として受け取り、

  1. Jinaのツールでウェブページテキストを取得

  2. OpenAI(gpt-4o-mini)モデルを使って、ページ内にある価格情報バリアント情報を構造化データ(JSON)として抽出

  3. JSON形式の結果を最終ノードに渡して終了

という流れです。

プロモーション情報では「月々の支払例」や「プロモーションで得られるメリット金額」などを抽出、
標準価格情報では「車種カテゴリ」「モデル名」「バリアント名」「価格」の組み合わせをすべて取得するような指示がされています。

一応、LLMブロックのプロンプトも載せておきます。

・プロモーションサイト

#役割
あなたは日本自自動車メーカーの営業職に所属しています。競合企業がアップデートする最新の情報を収集し、社内に共有する役割があります。

#前提情報
{{プロモーションサイトスクレイピングの出力変数}}は、TOYOTA Malasisが提供するプロモーションサイトです。

#タスク
プロモーション内容から、自動車モデルとそれに対応する価格情報を全て抽出します。

#注意事項
価格は、Monthly instalments as low asとPromo worth up toの両方を抽出してください。どちらかが欠けている場合はNoneと出力してください。
価格を抽出する際は、数字のみを抽出してください。
すべての自動車モデルと、それに対応する価格情報を取得するように心がけてください。

#出力形式の例
JSON形式で出力してください。
{
  "車名": "Toyota Corolla",
  "Monthly instalments as low as": "¥25,000",
  "Promo worth up to": "¥150,000"
}

・標準販売価格サイト

#役割
あなたは日本自動車メーカーの営業職に所属しています。競合企業(TOYOTA Malaysia)がウェブサイト上で公開しているすべての車種カテゴリ、モデル、バリアント、価格情報を抽出して社内で共有してくます。

#前提情報
{{標準販売価格サイトスクレイピングの出力変数}}は、TOYOTA Malasisが販売する自動車の価格情報が記載されたサイト内容です。このページには、上位カテゴリ(車種カテゴリ)ごとにモデルが並び、そのモデルには一つ以上のバリアント(グレード)があり、それぞれ固有の価格が設定されています。

#タスク
{{標準販売価格サイトスクレイピングの出力変数}}に含まれる自動車モデルおよび価格情報を、以下のルールに従って全て抽出してください。

#注意事項
価格を抽出する際は、価格の数値のみを取得し、"RM""From 

RM"など価格に付随する文字は削除してください。

抽出時は階層構造を考慮し、以下のように区分してください。
「上位カテゴリ」:車種カテゴリ名(例:GR & GR-S、ELECTRIFIED、SUV、SEDAN、HATCHBACK、MPV、PICKUP & VANなど)

「モデル名」:該当セクションで明示されているモデル名(例:GR SUPRA, GR86, GR COROLLA, COROLLA CROSS HEV GR SPORT, HILUX GR SPORT, COROLLA GR SPORTなど)

「バリアント名」:モデルごとに設定されているグレード名・仕様名で、価格の左側に記載されている車名・グレード・燃料種別などを合わせたもの(例:GR Supra M/T Petrol, GR Corolla Petrol, Corolla Cross HEV GR Sport Hybrid Electricなど)。
価格が記載されている行はすべてバリアントとして抽出対象です。
絶対に全てのバリアントを抽出してください。
重要:一つも漏らさず、全てのバリアントを抽出してください。価格の行ごとにバリアントが存在する想定で、テキスト中に価格が記されている箇所は必ず対応するバリアントがあるはずです。

「価格」:RMやFrom RMといった文字列を削除し、数値のみを残します。(例:RM645,000.00645,000.00)

全ての該当する「上位カテゴリ」「モデル名」「バリアント名」「価格」を抽出してください。


#出力形式の例
JSON形式で出力してください。
[
  {
    "上位カテゴリ": "上位カテゴリ名",
    "モデル名": "モデル名",
    "バリアント名": "バリアント名",
    "価格": "数値のみの価格"
  },
  ...
]

##条件:
上位カテゴリ、モデル名、バリアント名、価格の全組み合わせを漏れなく抽出してください。

「バリアント名」は、価格情報の直前に記載されているグレード名・燃料種別などを全て含めたフルネームで記載してください。
ページ内に存在する全てのバリアントと価格を取得し、一つも漏らさないでください。

「価格」:"RM""From RM"を取り除き、価格の数値のみを出力してください。(例:「RM645,000.00」→「645,000.00」)


GASでワークフローとスプシを連携

  • A2セルに「Promotion site url」を、

  • A3セルに「Normal site url」を入力しておき、

  • このコードを実行するとDifyワークフローが呼び出され、抽出結果がスプレッドシートの7行目以降に出力される仕組みです。

  • 処理内容は主に以下のフローで構成されています。

    1. スプレッドシートからURLを読み取る

    2. DifyワークフローAPIを呼び出す(トークン付与)

    3. JSON形式のレスポンスを受け取り、余計なjsonなどを削除したうえでパース

    4. パースした配列データを順次スプレッドシートへ出力

####GASコード全体####

function callDifyAPI_Malaysia() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();


  // APIキーとエンドポイン(Dify ワークフローのURL)を設定
  const apiKey = 'API Key'; // Dify APIのAPIキーを入力
  const apiUrl = 'Workflow Url'; // DifyワークフローのエンドポイントURLを入力

  // A2セルとA3セルの内容を取得
  const promotionInput = sheet.getRange('A2').getValue();
  const normalInput = sheet.getRange('A3').getValue();
  Logger.log('A2セルの入力値 (Promotion): ' + promotionInput);
  Logger.log('A3セルの入力値 (Normal): ' + normalInput);

  if (!promotionInput || !normalInput) {
    Logger.log('エラー: A2またはA3セルが空です。');
    SpreadsheetApp.getUi().alert('A2またはA3セルが空です。入力してください。');
    return;
  }

  // APIリクエストのペイロードを作成
  const payload = JSON.stringify({
    inputs: {
      Promotion: promotionInput,
      Normal: normalInput
    },
    response_mode: "blocking", // 必須: 応答モードを指定
    user: "default_user" // 必須: ユーザー識別子を指定(適切な値に置き換えてください)
  });
  Logger.log('APIリクエストペイロード: ' + payload);

  // HTTPリクエストのオプションを設定
  const options = {
    method: 'post',
    contentType: 'application/json',
    headers: {
      'Authorization': `Bearer ${apiKey}`
    },
    payload: payload
  };

  try {
    // APIリクエストを送信
    Logger.log('APIリクエストを送信中...');
    const response = UrlFetchApp.fetch(apiUrl, options);
    Logger.log('APIレスポンスを受信しました。');

    // レスポンスデータをパース
    const responseText = response.getContentText();
    const responseData = JSON.parse(responseText);
    Logger.log('APIレスポンスの内容: ' + JSON.stringify(responseData));

    // 出力変数を取得し、不要な "```json" と "```" を削除
    const promotionData = JSON.parse(responseData.data.outputs.Promotion.replace(/```json|```/g, '') || '[]');
    const normalData = JSON.parse(responseData.data.outputs.Normal.replace(/```json|```/g, '') || '[]');

    // Promotionデータをスプレッドシートに出力
    promotionData.forEach((item, index) => {
      const row = 7 + index;
      sheet.getRange(row, 1).setValue(item["車名"] || '');
      sheet.getRange(row, 2).setValue(item["Monthly instalments as low as"] || '');
      sheet.getRange(row, 3).setValue(item["Promo worth up to"] || '');
    });

    // Normalデータをスプレッドシートに出力
    normalData.forEach((item, index) => {
      const row = 7 + index;
      sheet.getRange(row, 5).setValue(item["上位カテゴリ"] || '');
      sheet.getRange(row, 6).setValue(item["モデル名"] || '');
      sheet.getRange(row, 7).setValue(item["バリアント名"] || '');
      sheet.getRange(row, 8).setValue(item["価格"] || '');
    });

    SpreadsheetApp.getUi().alert('Difyワークフローの実行が完了しました。データをスプレッドシートに出力しました。');
    Logger.log('スクリプト実行完了。');
  } catch (error) {
    Logger.log('エラーが発生しました: ' + error.message);
    Logger.log('スタックトレース: ' + error.stack);
    SpreadsheetApp.getUi().alert('API呼び出し中にエラーが発生しました。詳細はログを確認してください。');
  }
}


1. スプレッドシートの準備

const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  • アクティブなスプレッドシートのアクティブシートを取得します。


2. APIキーとエンドポイントの設定

const apiKey = 'API Key'; // Dify APIのAPIキーを入力
const apiUrl = 'Workflow Url'; // DifyワークフローのエンドポイントURLを入力
  • ここにDifyで発行されたAPIキーと、Difyのワークフロー実行用エンドポイントURLを設定します。


3. スプレッドシートから入力値を取得

const promotionInput = sheet.getRange('A2').getValue();
const normalInput = sheet.getRange('A3').getValue();
  • シートのA2セル・A3セルから、それぞれプロモーション情報用URLと標準価格情報用URL(またはテキスト)を取得します。

if (!promotionInput || !normalInput) {
  // いずれかのセルが空の場合はエラーを通知して処理を終了
}
  • 必須入力が空の場合、処理を止めてユーザーに通知します。


4. APIリクエスト用のペイロード作成

const payload = JSON.stringify({ inputs: { Promotion: promotionInput, Normal: normalInput }, response_mode: "blocking", // 応答モード user: "default_user" // 任意のユーザー名やID });
  • Difyワークフローが要求するリクエストボディ(JSON)を組み立てます。

  • PromotionとNormalはワークフロー内の変数名に対応しており、それぞれにA2・A3の値を設定しています。


5. HTTPリクエストオプションの設定

const options = { method: 'post', contentType: 'application/json', headers: { 'Authorization': `Bearer ${apiKey}` }, payload: payload };
  • methodを'post'に指定し、JSONを送信できるようにしています。

  • AuthorizationヘッダにBearerトークンとしてapiKeyを指定。

  • payloadには前項で作成したリクエストボディを文字列として指定しています。


6. DifyワークフローのAPI呼び出し

const response = UrlFetchApp.fetch(apiUrl, options);
  • UrlFetchApp.fetch() を使ってHTTPリクエストを実行します。

  • ここでDifyのワークフローが呼び出され、ツールの実行やLLM処理などを経て結果が返されます。

const responseText = response.getContentText(); const responseData = JSON.parse(responseText);
  • レスポンスの本文を文字列として取得し、JSONパースしてJavaScriptオブジェクトとして扱いやすい形にしています。


7. レスポンスデータの解析

const promotionData = JSON.parse(responseData.data.outputs.Promotion.replace(/```json|```/g, '') || '[]'); const normalData = JSON.parse(responseData.data.outputs.Normal.replace(/```json|```/g, '') ||
  • ワークフロー内で指定されている出力変数(Promotion と Normal)の値を取り出し、JSON化しています。

  • 回答が「json ... 」で囲われている場合のために、不要なマークダウン( ```json と `````)を正規表現で削除しています。


8. 取得したデータをスプレッドシートに書き込み

promotionData.forEach((item, index) => { const row = 7 + index; sheet.getRange(row, 1).setValue(item["車名"] || ''); sheet.getRange(row, 2).setValue(item["Monthly instalments as low as"] || ''); sheet.getRange(row, 3).setValue(item["Promo worth up to"] || ''); });
  • promotionData配列をループし、1列目(車名)2列目(毎月の支払例)3列目(プロモ金額) の形でシートに出力します。

  • row = 7 + index; で7行目から下に順次データを書き込みます。

normalData.forEach((item, index) => { const row = 7 + index; sheet.getRange(row, 5).setValue(item["上位カテゴリ"] || ''); sheet.getRange(row, 6).setValue(item["モデル名"] || ''); sheet.getRange(row, 7).setValue(item["バリアント名"] || ''); sheet.getRange(row, 8).setValue(item["価格"] || ''); });
  • 同様に、normalDataの配列をループし、5列目(上位カテゴリ)6列目(モデル名)7列目(バリアント名)8列目(価格) を書き込みます。


9. 完了メッセージとエラーハンドリング

SpreadsheetApp.getUi().alert('Difyワークフローの実行が完了しました。データをスプレッドシートに出力しました。');
  • 成功時にはアラートを表示し、ログに「完了」メッセージを残します。

} catch (error) { Logger.log('エラーが発生しました: ' + error.message); // ... SpreadsheetApp.getUi().alert('API呼び出し中にエラーが発生しました。詳細はログを確認してください。'); }
  • 例外が発生した場合はログおよびUIにメッセージを表示し、デバッグしやすいようにしています。

おわり

以上でワークフローは終了です。
基本的な情報収集ツールですので、応用して自分用に作ってみてください。
また、「こんなツールを自由自在に作れるようになって、自分の業務効率を効率化したい!」という方は連絡ください。
研修サービスをご提案しますし、ツールの作り方や学び方をお教えします。
(もちろん無料です)

いいなと思ったら応援しよう!