時計、収集カード、成功判定の分岐、積み重なったストレージがつながり、ワークフローを構成している図。
信頼性の高いワークフローでは、行を保存する前に実行結果を確認します。

このガイドでは、ローカル環境で構築し、テストするための参考手順を紹介します。目的は、繰り返し実行できるデータ取り込みワークフローを整えることです。そのままインポートできる完成済みのワークフローや、導入全体の動作確認が済んだ構成を提供するものではありません。チェックポイントとは、実行の進み具合を保存した記録です。アップサートとは、新しい行を挿入し、一意キーが既存レコードと一致する場合はそのレコードを更新するデータベース操作です。

技術的な対象範囲と運用上の制限

n8nワークフローを設定する前に、このデータ抽出パイプラインが扱う処理と、その対象範囲を理解しておく必要があります。このシステムが行うのは、データ抽出の自動化と永続保存に限られます。スパムの除外、感情分析、顧客の意向のスコア化、自動返信、コンテンツのモデレーションは行いません。コメントを独自のカテゴリに分類する必要がある場合は、Facebookコメントによる顧客フィードバック分析のガイドを参照してください。サポートへの質問を稼働中の窓口に振り分ける必要がある場合は、Facebookの未回答コメント質問キューのチュートリアルを参照してください。

このチュートリアルでは、n8nの標準ノードとFacebook Comments ScraperのHTTP APIを使った技術的な実装を説明します。API(Application Programming Interface)とは、異なるソフトウェアがウェブを介して通信するための規則と接続先の集合です。スクレイパーには、事前に特定した公開投稿のURLを入力します。一般的なプロフィールのリンク、グループのURL、ブランドのチャンネルページは入力できません。実行すると、スクレイパーはJSON(JavaScript Object Notation)形式のフラットなデータレコードを返します。JSONは、構造化データを共有するための標準的なテキスト形式です。

出力の各行は、最上位のコメント、またはコメントに直接付いた返信のいずれかを表します。返されるデータには、commentIdやcontentIdなどの文字列の識別子が含まれます。authorIdとpublishedAtはnullになる場合がある点に注意してください。publishedAtとscrapedAtはタイムスタンプで、isReplyは文字列の識別子ではなく真偽値です。生のJSONオブジェクトには、parentCommentId、rootCommentId、isReply、text、およびnull値が保持されます。複数回の実行で生じる重複は、contentIdとcommentIdを組み合わせた複合一意制約を使い、パラメーター化クエリでデータベース側で処理します。

パイプラインを設定する際は、次の運用パラメーターを確認してください。

  • 対象リンク:公開投稿を明示的に指定するURLを、文字列の要素として配列に1〜100件含める必要があります。
  • 取得件数の上限:maxResultsPerPostパラメーターは、投稿ごとに収集するコメントと返信の行数の上限を指定します。25に設定すると、実行で取得する行数は投稿ごとに最大25行になります。
  • 返信の抽出:includeRepliesをtrueに設定すると、スクレイパーは親コメントとともに、その下に付いた返信も収集します。
  • 取得可能な全件の抽出:collectAllAvailableをtrueに設定すると、数値で指定した取得件数の上限を適用しなくなります。ただし、プラットフォーム上で閲覧できる範囲の制限と、計算リソースの利用料金は引き続き適用されます。
  • 並べ替えパラメーター:all、newest、most_relevantなどの並べ替えオプションは、抽出前にFacebookがコメントを並べる順序を指定します。取得件数の上限とは独立して機能します。

主要APIエンドポイント一覧

次の表に、このワークフローで使うエンドポイントと、URLの構造、想定されるレスポンスフィールドを示します。

主要APIエンドポイント一覧
エンドポイントURL説明想定されるレスポンスフィールド
https://api.apify.com/v2/acts/chronometrica~facebook-comments-scraper/runsスクレイパーの実行を開始するdata.id, data.defaultDatasetId, data.defaultKeyValueStoreId, data.status
https://api.apify.com/v2/actor-runs/RUN_ID実行状態を確認するdata.status
https://api.apify.com/v2/datasets/DATASET_ID/items?format=json&clean=false&offset=0&limit=100データセットの項目をページ単位で取得するコメントレコードのJSON配列
https://api.apify.com/v2/key-value-stores/STORE_ID/records/OUTPUT実行概要を保存したストアを確認するその実行に固有の概要レコード

注:RUN_ID、DATASET_ID、STORE_IDなどのプレースホルダーは、実際に保存した実行情報の値に置き換えてください。アクセストークンをURL文字列に直接含めてはいけません。

構成とノードの接続手順

このパイプラインをn8nで構築するには、組み込みノードを所定の順序で設定します。n8nのインストール、n8nのHeader Auth認証情報に保存したApify APIトークン(名前はAuthorization、値はBearer your_token)、およびPostgresなどのリレーショナルデータベースの認証情報が必要です。

1. トリガーと認証情報の設定

まず、n8nで新しいワークフローを作成します。ワークフローの設定を開き、現地のタイムゾーンを選択してください。各構成要素を作成し、テストしている間は、ワークフローを未公開のままにします。

処理を開始する入口を2つ作成します。

  • Schedule Trigger:タイムゾーンの設定に基づき、毎日または毎時の定期収集を自動的に開始します。
  • Manual Triggerと復旧用入力:運用担当者が使う、独立した手動の開始点です。手動トリガーは実行IDを直接受け取れないため、その後ろにEdit Fieldsノードを配置し、保存済みの既存の実行IDを担当者が入力できるようにします。これにより、新たなスクレイピングジョブを開始せずに、停止したままの実行を復旧できます。

2. スクレイパーの起動

スケジュールトリガーを、Launch Scraperという名前のHTTP Requestノードに接続します。メソッドをPOSTに設定し、ApifyのActor実行エンドポイントを指定します。Header Auth認証情報を関連付けてください。URLをそのまま文字列で指定し、次のような小規模で単純なJSONのリクエスト本文を設定します。

text
{
  "startUrls": [
    "https://www.facebook.com/story.php?story_fbid=123456789&id=987654321"
  ],
  "maxResultsPerPost": 25,
  "includeReplies": true,
  "collectAllAvailable": false,
  "commentSortMode": "all"
}
構成とノードの接続手順

このリクエストが成功すると、Apify APIは実行メタデータを格納したレスポンスを返します。ワークフローでは、data.id、data.defaultDatasetId、data.defaultKeyValueStoreId、data.statusの4つの値をすぐに取得して保存する必要があります。

実行IDをキーにして、これらの値をデータベース上の永続的なチェックポイントに保存します。試行回数と次のオフセットの初期値は、どちらも0にします。永続的とは、一時的なワークフローメモリの外にあるリレーショナルデータベースに、データを継続して保存することです。

重要な安全ルール:ネットワークのタイムアウトが発生し、起動用のPOSTリクエストが成功したか不明な場合は、絶対に自動再試行しないでください。最初のリクエストがサーバーに届いていた場合、確認せずに再試行すると、課金対象のスクレイピングがもう1回開始されます。起動できたか不明な場合は、新たに起動する前に、Apifyアカウントの実行一覧を手動で確認してください。

3. 復旧処理

実行が失敗した場合や進まなくなった場合は、手動復旧の分岐で、担当者が指定した実行IDを使って既存のチェックポイントレコードを選択します。この際、起動ノードは完全に飛ばします。

ワークフローでは、1つの実行IDにつき、同時に動作する処理を1つに制限します。復旧対象の実行について、データベースに保存された終了状態がすでにSUCCEEDEDであれば、定期的な状態確認を完全に飛ばし、直接ページの読み込みに進みます。保存された状態が失敗であれば、手動確認に進みます。それ以外の状態では、回数を制限した定期的な状態確認に進みます。復旧処理によって試行回数を自動的にリセットしてはいけません。

4. 定期的な状態確認と試行回数による分岐

状態を確認する前に、毎回データベースに問い合わせ、現在の試行回数を確認します。試行回数が30に達している場合は、無限ループを防ぐため、手動確認の分岐に進みます。

試行回数が30未満の場合は、データベースの試行回数を増やし、Waitノードで処理を10秒間停止します。その後、HTTP GETリクエストを使い、Apify API(https://api.apify.com/v2/actor-runs/RUN_ID)で実行状態を確認します。

定期的な状態確認中にネットワークリクエストのエラーが発生した場合は、POSTリクエストを再試行するのではなく、読み取りリクエストを回数制限付きで再試行します(待機時間10秒、試行回数は最大3回)。

ワークフローは、返された状態を次のように判定します。

  • SUCCEEDED:状態の値をそのままデータベースに保存し、データセットの取得に進みます。
  • READY / RUNNING / ABORTING / TIMING-OUT:試行回数の判定に戻り、待機してから再度確認します。
  • FAILED / ABORTED / TIMED-OUT:失敗状態を記録し、停止するか、手動確認に進みます。
  • 不明な状態:手動確認に進みます。

注:状態確認のループを停止しても、リモートで動作するApify Actorはキャンセルされません。n8nワークフローの待機を終了するだけです。状態確認のタイムアウトは、その投稿のコメントが0件だったことを意味しません。

5. ページ分割とページの読み込み

実行が成功したら、ワークフローはページ単位で取得するループに進みます。各ページの取得前に、データベースから現在のチェックポイントを読み直し、最新のオフセットとデータセットIDを取得します。

HTTP GETリクエストでデータセットの項目を取得します(https://api.apify.com/v2/datasets/DATASET_ID/items?format=json&clean=false&offset=0&limit=100のオフセットとデータセットIDを、現在のオフセットと保存済みのデータセットIDに置き換えます)。汎用的な「最新」を指す別名ではなく、対象の実行に固有のデータセットIDを指定する必要があります。リクエストの形式をJSONに設定し、clean=falseとlimit=100を指定してください。

HTTPノードがレスポンス本文全体を返すように設定します。分割用ノードにデータを渡す前に、受信した配列の要素数を記録してください。最初のページの配列が完全に空の場合は、分割処理を飛ばします。保存済みのストアIDを使い、専用エンドポイント(https://api.apify.com/v2/key-value-stores/STORE_ID/records/OUTPUT)からキーバリューストアの出力レコードを確認し、エラーが発生していないかを調べます。それ以降のページでは、空の配列は単に収集の終了を示します。

6. 分割とデータベースへのアップサート

分割先のフィールド名としてcommentを明示的に指定したSplit Outノードに、データセットの配列を渡します。

後続のデータベース保存ノードは、分割された各項目を受け取り、commentから各フィールドを直接対応付けます。空でない文字列の識別子と、有効なタイムスタンプがあることを検証します。そのうえで、複合一意キー制約(contentIdとcommentId)に対して、パラメーター化クエリを使ってアップサートを実行します。アップサートは、新しい行を挿入し、重複するレコードが見つかった場合は既存のレコードを更新する操作です。

古いデータが新しい更新内容を上書きしないように、入力データのscrapedAtタイムスタンプが保存済みのタイムスタンプ以降の場合だけ、入力されたテキストとメタデータを反映するよう、データベースの更新条件を設定します。これは、公開投稿について最後に観測した状態を表すものです。変更されない過去の監査ログや、削除の証拠を提供するものではありません。

7. チェックポイントを進める

現在のページにある全項目をデータベースに正常に書き込んだ後、データベース内のチェックポイントのオフセットに100を加えます。

重要なルール:コメントごとにチェックポイントのオフセットを進めてはいけません。ページ内の一括処理全体が成功した後に、そのページにつき必ず1回だけオフセットを更新します。ページの途中でデータベースへの書き込みが失敗した場合は、チェックポイントのオフセットを変更しません。これにより、レコードを重複させずに、そのページを安全に再処理できます。

分割処理とデータベース処理の間でも、現在のページの要素数をメタデータとして別途保持します。最後の判定では、最初のループの要素数ではなく、現在のページの要素数を使います。現在のページの要素数が100未満であれば、収集は完了し、ワークフローを終了します。要素数が100であれば、チェックポイントを読み直す処理に戻り、次の一括データを取得します。

読者が行うテストと検証の手順

ワークフローを公開して本番環境で自動実行する前に、体系的な確認項目を使い、ローカル環境でワークフローを構築してテストする必要があります。未検証のテンプレートに頼らないでください。

  1. 取り込み処理の単独テスト:内容を固定した検証用テストデータ{body: [25 comments]}を、Split Outノードに直接入力します。このノードはデータベースノードに接続し、その直後にSTOPするよう設定します。実際の運用で使うチェックポイントとページ集計のノードはすべて飛ばします。データベースに正確に25行が保存されることを確認してください。まったく同じテストデータをもう1度処理した場合、新たに挿入される行は0行となり、既存レコードが判定されても状態が古いものに戻らないことを確認します。異なる26件目のレコードを追加した場合は、新たに挿入される行が正確に1行になることを確認します。さらに、古いテストデータで新しいテキストを上書きできないことも確認してください。
  2. テストデータによるページ分割のテスト:チェックポイントをテスト用の代替データに置き換え、100件、100件、1件の3ページに分けた201件のレコードを使って、オフセットを進めるループをテストします。課金対象のActor呼び出しは行いません。
  3. 耐障害性と定期的な状態確認のテスト:状態確認の処理で、確認回数が上限に達したときに正しく手動確認へ進むことをテストします。途中のページでデータベースへの書き込み失敗を再現してください。チェックポイントのオフセットが変わらないこと、復旧時にスクレイパーを再起動しないこと、成功以外の状態ではデータセットの取得を正しく阻止することを確認します。
  4. 実データでの手動確認:実際の公開投稿のリンクを使い、手動テストを1回実行します。プラットフォームの制限、計算リソースの利用料金、実行ログを確認してください。

ローカル環境でのテストがすべて合格し、スケジュールのタイムゾーンが正しく設定されていることを確認してから、n8nワークフローを保存して公開してください。

出典と参考資料

著者 Chronometrica · 更新日

ページの先頭へ ↑