Instagram グラフ API で投稿画像(タイムライン)を一覧表示(埋め込み)

ホームページにインスタグラムのタイムライン(一覧)を埋め込む方法について。

Instagram グラフ API を使ってインスタグラムで投稿した画像やビデオの一覧(タイムライン)を Web ページに表示する方法の解説です。API を利用するための無期限のアクセストークンと Instagram ビジネスアカウント ID の取得方法や取得したデータをを使って PHP で投稿の画像やキャプション、いいねの数やコメント数を表示する方法など。

作成日:2021年11月28日

概要

インスタグラムで投稿した画像などのメディアを取得してウェブに表示するには「Facebook for Developers」で提供されている Web API を使います。

以下では Instagram グラフ(Graph)API を利用してインスタグラムの一覧をWebページに表示します。

Instagram グラフ API を利用するにはアクセストークンと Instagram ビジネスアカウント ID を取得する必要がありますが、この部分が多少手間がかかります。

この時点のグラフAPIバージョンは v12.0 です。

必要なもの

  • Instagram のアカウント
  • Facebook のアカウント
  • スマートフォンやタブレット(インスタグラムのアプリの操作)

Instagram グラフ API を利用するには Instagram のアカウントの他に Facebook のアカウントも必要になります。これは Instagram グラフ API が Facebook の開発者ツール「Facebook for Developers」に組み込まれているためです。

大まかな流れ

必要なアカウントをセットアップし、Facebook ページを作成して Instagram のアカウントをリンクさせます。そして Facebook 開発者ツール(Facebook for Developers)で Facebook アプリを作成し、アクセストークンや Instagram 表示に必要な ID を取得します。

取得したアクセストークンと ID を使って Instagram の Web API から画像などのデータを取得して Web ページに表示します。以下の例ではデータの取得や Web ページの表示には PHP を使います。

  1. Instagram プロアカウントへ切り替え(インスタグラムのアプリで実行)
  2. Facebook 開発者アカウントの作成(まだ作成していない場合)
  3. Facebook ページの作成
    • Facebook ページと Instagram プロアカウントをリンク(関連付け)
  4. Facebook アプリの作成(Facebook for Developers)
  5. Instagram グラフ API で使用するアクセストークンの取得(Facebook for Developers)
    • 短期アクセストークン(有効期限1時間)の取得
    • 長期アクセストークン(有効期限2ヶ月)の取得
    • 無期限のアクセストークンの取得
  6. Instagram ビジネスアカウント ID の取得
  7. Instagram グラフ API を使ってインスタグラムの一覧のデータを取得して表示(PHP)

無期限のアクセストークンの取得

Instagram グラフ API で使用する無期限(有効期限なし)のアクセストークンを取得するには段階的に取得する必要があります。

試してみたところ、グラフ API エクスプローラを使って短期アクセストークンを取得後、アクセストークンデバッガーを使って延長するのが比較的簡単でした(方法1)。

グラフ API エクスプローラでクエリを使って取得する方法(方法2)と両方記載しています。

参考ドキュメント(Facebook for Developers ドキュメント)

インスタグラムのアカウントをプロアカウントに

Instagram グラフ API を利用するには(個人用アカウントの場合は)プロアカウント(無料)に切り替える必要があります。この作業はスマートフォンやタブレットのインスタグラムのアプリで行います。

インスタグラムのアプリにログインし、「設定」→「アカウント」の順に移動し一番下の「プロアカウントに切り替える」を選択し、「OK」をタップします。

「クリエイター」か「ビジネス」の選択はどちらでも構いません。一度選択しても後からアカウントの画面の「アカウントタイプを切り替え」で変更可能です。

Facebook 開発者アカウントの作成

Instagram グラフ API は Facebook アプリを作成して利用します。

Facebook アプリの作成は Facebook の開発者ツール「Facebook for Developers」を使うので、開発者アカウント(無料)が必要です(Facebook アカウントを持っていることが前提)。

開発者アカウントを作成していない場合は Facebook for Developers にアクセスして作成します。

Facebook にログイン済みであれば、自分の Facebook アカウントが表示されるのでアカウントを作成するために「次へ」をクリックします。

電話番号(SMS)でアカウントの認証を行います(Facebook アカウントに電話番号を登録していない場合は電話番号の入力を求められます)。

続いてメールアドレスを確認します。

いずれか当てはまるものを選択すれば登録完了です。

Facebook ページの作成

Instagram グラフ API を利用するには Facebook ページと Instagram のアカウントをリンクさせる(紐付ける)必要があります。Facebook ページは非公開でも Instagram グラフ API の利用は可能なのでページを作成するだけでOKです。

Facebook にログインします(個人用アカウントで大丈夫です)。

ログイン後、左側メニューから「ページ」をクリックします。

「+新しいページを作成」をクリックします。

必須項目の「ページ名」と「カテゴリ」を入力し「Facebookページを作成」をクリックします。ページ名には使えない言葉など決まりがあります(どのようなページ名が認められますか)

「ページの設定」ではプロフィール写真やカバー写真などを設定できますが、ページを作成するだけで良いので「保存」をクリックします(必要に応じてページを設定します)。

「ページを管理」ではメニューの「設定」をクリックします。

「ページ設定」の下のメニューから「Instagram」を選択し、「アカウントをリンク」をクリックします。

1つの Instagram アカウントは、1つの Facebook ページにのみリンクが可能です。既に他の Facebook ページと連携している場合は解除するか、別の Instagram アカウントを選択する必要があります。

「Instagramメッセージ設定を選択」が表示されるので、受信箱で Instagram メッセージへのアクセスを許可するかどうかを選択し、「次へ」をクリックします。ここでは外しています(FacebookページからInstagramのメッセージにアクセスするには、どうすればよいですか)。

Instagram のログインが表示されるので、ID とパスワードを入力して「ログイン」をクリックします。

Instagram と Facebook ページの連携(リンク)が完了すると、リンクされた Instagram アカウント情報が記載された画面が表示されます。

公開範囲の設定

Facebook ページが非公開でも Instagram グラフ API の利用は可能なので、Instagram アカウントとリンクした Facebook ページを運営する予定がない場合は非公開にすることができます。

公開・非公開は「ページ設定」の「一般」を選択して「公開範囲」で設定できます。「ページは公開されていません」を選択して「変更を保存」をクリックすると非公開になります。

非公開にする場合、理由を問われますが、適当に選択して「非公開にする」をクリックします。

Facebook アプリの作成

Facebook アプリを作成するには Facebook for Developers (Facebook の開発者ツール)にアクセスして「マイアプリ」を選択します。

「アプリを作成」を選択します。。

すでに作成済みのアプリがあれば表示されます。

「アプリタイプを選択」では「ビジネス」を選択して「次へ」をクリックします。

表示名(アプリ名)を入力し、連絡先メールアドレスを入力(確認)して、アプリの目的(自分自身の Instagram を埋め込むか、クライアントの Instagram を埋め込むか)を選択します。

ビジネスアカウントは任意ですが、必要に応じてビジネスマネージャアカウントを選択します。

入力が完了したら「アプリを作成」ボタンをクリックします。

表示名(アプリ名)は任意の名前を付けられますが、Instagram や Insta などを含めるとエラーになります(日本語のインスタは大丈夫でした)。

パスワードの入力を求められるので入力して「送信」をクリックするとアプリが作成されます。

アプリ ID と app secret

作成した Facebook アプリの「アプリ ID」と「app secret」の値は Facebook アプリのページの「設定」→「ベーシック」で確認できます。

「設定」を展開して「ベーシック」を選択します。

app secret は「表示」をクリックするとパスワードの入力を求められるので入力して表示します。

プライバシーポリシーのURL

「設定」→「ベーシック」の「プライバシーポリシーのURL」には設置先のプライバシーポリシー(またはそれに該当するページ)の URL を入力して「変更を保存」で保存します。

この設定は後からでも可能ですが、設定していない場合、公開後「プラットフォーム規約に準拠するようにプライバシーポリシーをアップデートしてください」というような内容のメール(開発者アラート)が Facebook for Developer より届き設定することを求められます。

また、「プライバシーポリシーのURL」を設定していない場合、公開後以下のようにアラートがあることが表示されるので「受信箱」をクリックして内容を確認できます。

「プライバシーポリシーのURL」を設定すれば以下のように「解決済み」となります。

アクセストークンの取得(方法1)

Instagram グラフ API を利用するにはアクセストークを発行して取得する必要があります。

アクセストークンは Facebook for Developers で発行できますが、無期限のアクセストークンを取得するには、短期アクセストークン(有効期限1時間)を取得後、有効期限2ヶ月の長期アクセストークンを取得し、その後無期限のアクセストークンを取得します。

参考(Facebook for Developers ドキュメント):

グラフ API エクスプローラ

アクセストークンの取得にはグラフAPIエクスプローラを使います。

グラフAPIエクスプローラはグラフ API へのクエリを作成して実行し、その応答を表示することができるツールで、自分でブラウザから API にクエリを送信するより便利です。

グラフAPIエクスプローラは Facebook for Developers の上部にある「ツール」→「グラフAPIエクスプローラ」でアクセスできます。または、https://developers.facebook.com/tools/explorer/ からもアクセスできます。

アクセスすると以下のような画面が表示されます。

コンポーネント 概要
クエリ文字列フィールド クエリを入力して右横の「送信」ボタンをクリックして実行することができます
応答ウィンドウ 送信したクエリに対する応答がこのウィンドウに表示されます
アクセストークンフィールド アクセストークンを取得すると、このフィールドに表示されます
アプリドロップダウン 対象のアプリを選択します
アクセストークンドロップダウン 取得するトークンの種類を選択します

参考:グラフAPIエクスプローラガイド

短期ユーザーアクセストークンの取得

最初は有効期限1時間の短期ユーザーアクセストークンを取得します。Facebook for Developers 上部の「ツール」から「グラフAPIエクスプローラ」を選択してグラフAPIエクスプローラを開きます。

画面右側の「Facebookアプリ」は作成したアプリが選択されていることを確認します(選択されていなければ選択します)。

「トークンを取得」は、「ユーザーアクセストークンを取得」を選択します。

facebook ユーザーとしてログインする許可を求められます。「アクセス許可を見る」をクリックすると、リクエストされたアクセス許可を確認できます。「xxxxxとしてログイン」をクリックします。

「○○○(アプリ名)にログインしてFacebookの情報を共有しました」というようなメールが Facebook から届きます。

「ユーザートークン」が選択されたら、「アクセス許可」の下の「許可を追加」をクリックして必要な許可を追加します。初期状態では「public_profile」のみが追加されています。

ユーザートークンに付与する許可を選択して追加します。

Events Groups Pages を展開
  • business_management
  • pages_manage_ads
  • pages_manage_metadata
  • pages_read_engagement
  • pages_read_user_content
  • pages_show_list
Other を展開
  • instagram_basic
  • instagram_manage_comments
  • instagram_manage_insights

以下はそれぞれの許可の概要です(詳細は:アクセス許可のリファレンス)。付与する許可は必要に応じて変更(追加・削除)します。

アクセス許可 説明
business_management ビジネスマネージャAPIを利用した読み取りや書き込みをアプリが行えるようになる
pages_manage_ads ページに関連付けられた広告をアプリが管理できるようになる
pages_manage_metadata アプリがページでのアクティビティに関するWebhooksを受け取るためにサブスクリプション登録したり、ページの設定をアップデートしたりできるようになる
pages_read_engagement ページに投稿されたコンテンツ(投稿、写真、動画、イベント)、フォロワーのデータやプロフィール写真、ページについてのメタデータやその他のインサイトをアプリが読み取れるようになる
pages_read_user_content アプリがページ上のユーザー作成コンテンツ(投稿、コメント、評価)を読み取り、ページ投稿のユーザーのコメントを削除できるようになる
pages_show_list 利用者が管理しているページのリストにアプリがアクセスできるようになる
instagram_basic アプリがInstagramアカウントのプロフィール情報やメディアを読み取れるようになる
instagram_manage_comments ページにリンクしたInstagramアカウントに代わって、アプリがコメントを作成する、削除する、非表示にすることができるようになる
instagram_manage_insights FacebookページにリンクされたInstagramアカウントのインサイトにアプリがアクセスできるようになる
public_profile (※追加は不要)自動的にすべてのアプリに付与されるアクセス許可

許可を選択して追加したら確認し、「Generate Access Token」をクリックして短期ユーザーアクセストークン(1時間有効)を発行します。

以下のようなウィンドウが表示されます。

「許可するアクセスを選択」をクリックすると許可するアクセスを確認・設定することができます。

問題がなければ「次へ」とクリックすると、短期ユーザーアクセストークンが発行されます。

「アクセストークン」の下に表示されている文字列が1回目のアクセストークンです。このアクセストークンは次のアクセストークンを取得するのに使用する可能性があるのでコピーしてテキストなどに保存しておきます。

1回目のアクセストークンの有効期間は1時間なので、1時間以内に2回目(有効期間2ヶ月)のアクセストークンを取得します。1時間以内に有効期間2ヶ月のアクセストークンを取得できない場合は再発行します。

有効期限の1時間を過ぎると以下のように表示され、再発行(更新)が必要になります。

取得したトークンの確認

表示されているアクセストークンの左にあるアイコン をクリックすると以下のような「アクセストークン情報」のウィンドウが表示されます。

アクセストークンデバッガーで延長

「アクセストークン情報」のウィンドウで「アクセストークンツールで開く」をクリックすると、アクセストークンデバッガーのページが表示されます。この画面ではトークンの詳細な情報を確認したり、アクセストークンを延長することができます。

以下の場合、トークンの有効期限が1時間以内であることが確認できます。

ページの下の方にある「アクセストークンを延長」をクリックしてトークンの有効期限を延長します。

以下のように画面の下の部分に「この長期アクセストークンはxxxx年xx月xx日に期限切れとなります」と表示されるので、「デバッグ」をクリックします。

有効期限が「約2ヶ月以内」となっていれば延長成功です。

延長されたアクセストークン(長期ユーザーアクセストークン)の値をコピーします。

ユーザーアクセストークンを無期限に延長

グラフ API エクスプローラに戻り、コピーした延長されたアクセストークンの値を「アクセストークン」のフィールドにペーストし、左にあるアイコン をクリックします。

「アクセストークン情報」のウィンドウが開くので「アクセストークンツールで開く」をクリックします。

アクセストークンデバッガーのページが表示され、タイプが「User」で有効期限が「受け取らない」となっていればこのユーザーアクセストークンを無期限に延長できました。

この無期限に延長されたユーザーアクセストークンを使用するのでコピーして保管します。

もし、無期限のページアクセストークンが必要な場合はこのトークンの値を使って「長期ページアクセストークンの取得」を実行します。

アクセストークンの取得(方法2)

以下の方法でも無期限のアクセストークンを取得できます。方法1で問題なく無期限のアクセストークンが取得できれば、この章は不要ですので Instagram ビジネスアカウント ID の取得へ進んでください。

但し、以下の方法で取得するのは無期限の長期ページアクセストークンになります。

短期ユーザーアクセストークンの取得方法は全く同じなので省略します。

長期ユーザーアクセストークンの取得

長期ユーザーアクセストークン(有効期限2ヶ月)を取得するには、短期ユーザーアクセストークンの取得で取得したアクセストークンと作成したアプリの アプリID と app secret が必要です。

長期ユーザーアクセストークンの取得に必要なもの
  • 有効な短期ユーザーアクセストークン
  • アプリID
  • app secret

ブラウザに以下の形式の URL でアクセスして(Web API にリクエストして)長期ユーザーアクセストークンを取得することができます。

https://graph.facebook.com/{graph-api-version}/oauth/access_token?grant_type=fb_exchange_token&client_id={app-id}&client_secret={app-secret}&fb_exchange_token={your-access-token}
{graph-api-version} Instagram グラフ APIのバージョン(例:v12.0)
{app-id} アプリID
{app-secret} app secret
{your-access-token} 取得した短期ユーザーアクセストークン

上記のようにブラウザを使って API にリクエストしてアクセストークンを取得することができますが、グラフ API エクスプローラのクエリ文字列フィールドを利用できます。

クエリ文字列フィールドを利用

上記 URL の oauth 以降の部分の文字列を用意して、グラフ API エクスプローラのクエリ文字列フィールドに入力して「送信」をクリックします。

oauth 以降の部分の文字列の生成

グラフ API エクスプローラの入力欄に指定する oauth 以降の部分の文字列を手動で作成するのが面倒な場合は、以下に値を入力して文字列を生成することができます。

oauth 以降の部分に指定する文字列

上記に値を入力後、以下の値をコピーしてグラフ API エクスプローラの入力欄(クエリ文字列フィールド)にペーストします。


          

アクセストークンの値

問題がなければ、下のペイン(応答ウィンドウ)に取得した長期ユーザーアクセストークンの値が表示されるので、コピーして保管しておきます(長い文字列なのでスクロールしないと全ては見えません)。

参考:長期アクセストークン(Facebook for Developers ドキュメント)

アクセストークンデバッガーで確認

取得したトークンをアクセストークンデバッガーを使って確認します。上部メニューで「ツール」から「アクセストークンデバッガー」を選択します。

入力欄に取得した長期ユーザーアクセストークンの値を入力(ペースト)して、「デバッグ」をクリックします。

有効期限が「約2ヶ月以内」となっていれば問題ありません。

ターミナルで取得

ターミナルで curl コマンドを使って取得することもできます。以下は v12.0 の場合の例で、xxxxxxxx の部分は実際のアプリ ID と app secret、取得した短期ユーザーアクセストークンを指定します。

$ curl -i -X GET "https://graph.facebook.com/v12.0/oauth/access_token?grant_type=fb_exchange_token&client_id=xxxxxxxx&client_secret=xxxxxxxx&fb_exchange_token=xxxxxxxx"return
//ターミナルのレスポンス(オプション -i の指定によるレスポンスヘッダの出力)  
HTTP/2 200 
content-type: application/json; charset=UTF-8
facebook-api-version: v12.0
access-control-allow-origin: *
strict-transport-security: max-age=15552000; preload
pragma: no-cache
cache-control: private, no-cache, no-store, must-revalidate
・・・中略・・・
priority: u=3,i
alt-svc: h3=":443"; ma=3600
//access_token と token_type(応答)
{"access_token":"EAADQW1oeQBkBAD・・・中略・・・KuWkEt9nyiV","token_type":"bearer"}

長期ページアクセストークンの取得

長期ページアクセストークンは有効期限のない無期限のアクセストークンで、前項で取得した有効期限が2ヶ月の長期ユーザーアクセストークンから生成することができます。

前項で取得した(またはアクセストークンデバッガーで延長した)長期ユーザーアクセストークンをコピーしておきます。

「ツール」→「グラフAPIエクスプローラ」でグラフ API エクスプローラを開き、アクセストークンの欄に長期ユーザーアクセストークンをペーストします。

クエリ文字列フィールド(入力欄)に「me/accounts」と入力して「送信」をクリックします。

"access_token" の右側が長期ページアクセストークンなのでコピーします。

「ツール」→「アクセストークンデバッガー」を選択してアクセストークンデバッガーを開き、入力欄にコピーした長期ページアクセストークンをペーストして「デバッグ」をクリックします。

「タイプ」が「Page」で「有効期限」が「受け取らない」となっていれば無期限のページアクセストークンが取得できました。

※ 取得した長期ページアクセストークンは、後で使用するのでコピーして保存しておきます。

Instagram ビジネスアカウント ID の取得

Instagram のメディアの表示に必要な Instagram ビジネスアカウント ID を取得します(ビジネスアカウントとはプロアカウントに切り替えた時点で発行されるアカウント)。

「ツール」→「グラフAPIエクスプローラ」でグラフ API エクスプローラを開き、クエリ文字列フィールド(入力欄)に「me?fields=accounts{instagram_business_account}」と入力して「送信」をクリックします。

"instagram_business_account" の下の "id" に Instagram ビジネスアカウント ID が表示されます。

※ この値も後で使用するのでコピーして保存しておきます。

インスタグラム投稿の一覧を出力

取得した Instagram ビジネスアカウント ID とアクセストークンを使って Instagram グラフ API からインスタグラムの投稿の一覧のデータを取得することができます。

グラフ API はブラウザー内で URL をリクエストして直接利用できます。

例えば、以下のような URL を作成してブラウザでアクセス(HTTP メソッドの GET でリクエスト)するとインスタグラム投稿(メディア)の JSON データが4件取得できます。

https://graph.facebook.com/v12.0/{instagram_business_account}?fields=media.limit(4){caption,media_url,thumbnail_url,permalink,like_count,comments_count,media_type}&access_token={user_access_token}

https://graph.facebook.com/ はグラフ API の URL(ルートエンドポイント)で、その後に API のバージョンを指定し、{instagram_business_account} には取得したビジネスアカウント ID を指定します。

? 以降はクエリ部分で、fields パラメーターを使用して media を指定し、.limit(4) で取得する結果を4件に制限し、必要なフィールド(caption や media_url など)をリスティングしています。また、& で access_token パラメータにアクセストークンを指定します。

{user_access_token} の部分には取得したアクセストークンの値を指定します。

ブラウザには以下のような JSON 形式のレスポンス(応答)が表示されます。取得した media_url や permalink の URL にアクセスすると投稿された画像や投稿が表示されます。

この例の場合、.limit(4) で取得する結果の数を制限しているので、4件のデータの指定したフィールドが配列で取得されます。

上記 URL のリクエストに対するレスポンスの例
{
   "media": {  //fields パラメーターに指定した media
      "data": [
         {
            "caption": "Test04",  //キャプション
            "media_url": "https://scontent-nrt1-1・・・中略・・・B8", //画像の URL
            "permalink": "https://www.instagram.com/p/xxxxxxxx/",  //投稿の URL
            "like_count": 0,  //like のカウント数
            "comments_count": 0,  //コメントのカウント数
            "media_type": "IMAGE",  //メディアの種類
            "id": "xxxxxxxx"
         },
         {
            "caption": "Test03",
            "media_url": "https://scontent-nrt1-1・・・中略・・・25",
            "permalink": "https://www.instagram.com/p/xxxxxxxx/",
            "like_count": 0,
            "comments_count": 0,
            "media_type": "IMAGE",
            "id": "xxxxxxxx"
         },
         ・・・残り2件のデータ部分は省略・・・
      ],
      "paging": {
         "cursors": {
            "before": "QVFIUl・・・中略・・・bi1WNGR3",
            "after": "QVFIUlB・・・中略・・・abjNLb0xR"
         }
      }
   },
   "id": "xxxxxxxx"
}

参考(Facebook for Developers ドキュメント)

Web API の使い方に関しては developer.yahoo の「WebAPIの使い方(GETリクエスト)」にわかりやすい解説があります。

PHP で投稿一覧のデータを取得

ブラウザー内で URL をリクエストして API を利用できますが、Web ページに一覧を表示するには PHP や JavaScript で API にリクエストし、レスポンスの JSON データを使って出力します。

以下ではインスタグラムの投稿一覧のデータを Instagram グラフ API から取得して PHP で出力します。

API からのデータの取得は cURL 関数を使います。cURL は HTTP リクエストにより外部サイトの情報を取得することができる関数です。

cURL 関数の基本的な使用法は以下のようになります。

  1. curl_init() で cURL セッションを初期化
  2. curl_setopt() で転送時のオプションを設定
  3. curl_exec() で転送を実行
  4. curl_close() でセッションを終了

curl_exec() の戻り値はデフォルトでは結果を表す真偽値なので、curl_setopt() で CURLOPT_RETURNTRANSFER を設定して成功した場合に結果(データ)を取得するようにします。

クエリ部分はこの例では配列で作成して、http_build_query() で URL エンコードしてリクエストの URL に設定しています。

以下が一覧表示するコードです。

xxxxxx には取得した Instagram ビジネスアカウント ID とアクセストークンを指定します。取得するメディアの件数や API のバージョンは必要に応じて変更します。

また、エラーがある場合は「エラーが発生しました」とメッセージを表示するようにしています。もし API のエラーとエラーコードも表示する場合は、$show_api_error を true にします。

<?php
//Instagram ビジネスアカウント ID
$business_id = 'xxxxxx';
//アクセストークン
$token = 'xxxxxx'; 
//グラフAPI ホストURL(ルートエンドポイント)
$api = 'https://graph.facebook.com/';
//API のバージョン
$version = 'v12.0';
//取得するメディアの件数(0 または false を指定した場合は件数の制限なし)
$count = '4';  
//取得するフィールド
$field = 'caption,media_url,thumbnail_url,permalink,like_count,comments_count,media_type';
//クエリ(この場合、配列で記述して後で URL エンコード)
$query = [
    //取得件数の制限と取得するフィールドを指定($countの値が0やfalseの場合は件数の制限なし)
    'fields' => $count ? 'media.limit(' .$count. '){' .$field .'}' : 'media{' .$field .'}' ,
    //アクセストークンを指定
    'access_token' => $token
];
//URL を作成(クエリは http_build_query() で URL エンコード)
$url = $api .$version. '/' . $business_id . '?' . http_build_query($query); 

//cURL セッションを初期化
$ch = curl_init();
//取得する URL を指定
curl_setopt($ch, CURLOPT_URL, $url);
//GET メソッドを使用(省略可能)
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
//戻り値を文字列で返す(データを結果として取得)
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//結果を変数に代入(失敗すれば false、成功すれば取得結果が格納される)
$result = curl_exec($ch);
//cURL セッションを終了
curl_close($ch);
  
//レスポンスを格納する変数の初期化
$insta_response = null;
//API のエラーを表示するかどうか
$show_api_error = false;
  
if ( $result ) {
  //取得結果(JSON)をデコードしてレスポンスを格納する変数に代入
  $insta_response = json_decode( $result );
  if ( isset( $insta_response->error ) ) {
    //取得結果にエラーがあればメッセージを表示
    echo 'エラーが発生しました ';
    if ( $show_api_error ) {
      //$show_api_error が true なら API のエラーコードとメッセージを表示
      $error = $insta_response->error;
      echo 'Error Code ' . $error->code . ' : ' . $error->message;
    }
    //レスポンスを格納する変数 $insta_response を false に
    $insta_response = false;
  }elseif( !isset( $insta_response->media )){
    // 投稿(メディア)が存在しない場合
    $insta_response = false;
  }
}
?>
<div class="container">
  <h2>Instagram 一覧表示テスト</h2>
  <div class="row">
    <?php
    //取得結果にエラーがなく投稿が存在すれば一覧を出力
    if ( $insta_response ) {
      //レスポンスのデータは配列なので foreach でループ処理
      foreach ( $insta_response->media->data as $val ) {
        //メディアタイプがビデオの場合
        if ( $val->media_type === 'VIDEO' ) {
          $src = $val->thumbnail_url;
          //ビデオの場合は以下の HTML でアイコンを表示
          $video = '<span class="video_icon"></span>';
        }
        //メディアタイプが画像の場合
        else {
          $src = $val->media_url;
          $video = '';
        }
    ?>
    <div class="insta_media col-lg-3 col-md-6 px-0"> 
      <a href="<?php echo $val->permalink; ?>" target="_blank"> 
        <img src="<?php echo $src; ?>" alt="<?php echo isset($val->caption) ? $val->caption: ''; ?>">
        <?php echo $video ? $video : ''; //ビデオの場合は追加の span 要素を出力?>
        <span class="like_count"><?php echo $val->like_count; ?></span> 
        <span class="comments_count"><?php echo $val->comments_count; ?></span> 
      </a> 
    </div>
    <?php
      }
    }
    ?>
  </div>
</div>

上記のコードでは投稿のメディア表示するために caption, media_url, thumbnail_url, permalink, like_count, comments_count, media_type というフィールドを取得しています。

$field = 'caption,media_url,thumbnail_url,permalink,like_count,comments_count,media_type'; 

指定できるフィールドには以下のようなものがあります。詳細は「IGメディア」に記載されています。

フィールド 説明
caption キャプション
comments_count メディアに付けられたコメントの数(コメントへの返信を含みます)
like_count メディアに対する「いいね!」の数
media_type メディアタイプ。CAROUSEL_ALBUM、IMAGE、または VIDEO
media_url メディア(画像など)の URL
thumbnail_url メディア(ビデオ)のサムネイルのURL。VIDEO でのみ利用可能
permalink メディア(投稿)を指す URL
timestamp ISO 8601形式のUTCでの作成日付(デフォルトはUTC ±00:00)

この例の場合、curl_exec() の戻り値($result)が false でなければ、$result には JSON 形式の取得結果が入っているので、json_decode() で PHP で処理できるように変換して変数 $insta_response に格納しています。

取得結果にエラープロパティが設定されていれば、エラーメッセージを表示し、変数 $insta_response に false を代入して一覧表示の処理は行わないようにしています。また、投稿(メディア)が存在しない場合も同様に一覧表示の処理は行いません。

取得結果にエラーがなく投稿が存在すれば、レスポンスから foreach でそれぞれの投稿のデータを使って画像を出力します。

その際、media_type が VIDEO の場合は img 要素の src 属性には thumbnail_url フィールド(プロパティ)の値を指定し、そうでなければ media_url の値を指定します。

この例ではビデオの場合は、video_icon というクラスを指定した span 要素を出力して CSS でビデオ用のアイコンを表示するようにしていますが、不要であればこの部分(43行目)は削除します。そのままでも32行目で定義した span 要素が出力されるだけなので、CSS で何も指定しなければ問題ありません。

また、表示する画像にはその画像の投稿へのリンクを設定(href 属性に permalink フィールドの値を指定)し、画像の alt 属性には caption フィールドの値が設定されていればその値を設定しています。

そして span 要素で like_count と comments_count の値を使って「いいね!」の数とコメントの数を出力しています。この部分(44〜45行目)も不要であれば削除します。

前述のコードの一部抜粋
if ( $result ) {
  //取得結果(JSON)をデコードしてレスポンスを格納する変数に代入
  $insta_response = json_decode( $result );
  if ( isset( $insta_response->error ) ) {
    //取得結果にエラーがあればメッセージを表示
    echo 'エラーが発生しました ';
    if ( $show_api_error ) {
      //$show_api_error が true なら API のエラーコードとメッセージを表示
      $error = $insta_response->error;
      echo 'Error Code ' . $error->code . ' : ' . $error->message;
    }
    //レスポンスを格納する変数 $insta_response を false に
    $insta_response = false;
  }elseif( !isset( $insta_response->media )){
    // 投稿(メディア)がない場合
    $insta_response = false;
  }
}
?>
<div class="container">
  <h2>Instagram 一覧表示テスト</h2>
  <div class="row">
    <?php
    //取得結果にエラーがなく投稿が存在すれば一覧を出力
    if ( $insta_response ) {
      //レスポンスのデータは配列なので foreach でループ処理
      foreach ( $insta_response->media->data as $val ) {
        //メディアタイプがビデオの場合
        if ( $val->media_type === 'VIDEO' ) {
          $src = $val->thumbnail_url;
          //ビデオの場合は以下の HTML でアイコンを表示
          $video = '<span class="video_icon"></span>';
        }
        //メディアタイプが画像の場合
        else {
          $src = $val->media_url;
          $video = '';
        }
    ?>
    <div class="insta_media col-lg-3 col-md-6 px-0"> 
      <a href="<?php echo $val->permalink; ?>" target="_blank"> 
        <img src="<?php echo $src; ?>" alt="<?php echo isset($val->caption) ? $val->caption: ''; ?>">
        <?php echo $video ? $video : ''; //ビデオの場合は追加の span 要素を出力?>
        <span class="like_count"><?php echo $val->like_count; ?></span> 
        <span class="comments_count"><?php echo $val->comments_count; ?></span> 
      </a> 
    </div>
    <?php
      }
    }
    ?>
  </div>
</div>

上記の場合、例えば以下のような HTML が出力されます。

<div class="container">
  <h2>Instagram 一覧表示テスト</h2>
  <div class="row">
    <div class="insta_media col-lg-3 col-md-6 px-0"> 
      <a href="https://www.instagram.com/p/xxxxxx/" target="_blank"> 
        <img src="https://scontent-nrt1-1.cdninstagram.com/...CA9" alt="Test04">
        <span class="video_icon"></span>
        <span class="like_count">0</span> 
        <span class="comments_count">0</span> 
      </a> 
    </div>
    <div class="insta_media col-lg-3 col-md-6 px-0"> 
      <a href="https://www.instagram.com/p/xxxxxx/" target="_blank"> 
        <img src="https://scontent-nrt1-1.cdninstagram.com...FF8" alt="Test03">
         <span class="like_count">0</span> 
        <span class="comments_count">0</span> 
      </a> 
    </div>
    <div class="insta_media col-lg-3 col-md-6 px-0"> 
      <a href="https://www.instagram.com/p/xxxxxx/" target="_blank"> 
        <img src="https://scontent-nrt1-1.cdninstagram.com/...365" alt="Test02">
        <span class="like_count">0</span> 
        <span class="comments_count">0</span> 
      </a> 
    </div>
    <div class="insta_media col-lg-3 col-md-6 px-0"> 
      <a href="https://www.instagram.com/p/xxxxxx/" target="_blank"> 
        <img src="https://scontent-nrt1-1.cdninstagram.com/...026" alt="Test 01">
        <span class="like_count">0</span> 
        <span class="comments_count">0</span> 
      </a> 
    </div>
  </div>
</div>

以下は上記のスクリーンショットです(但し、上記のコードと異なり、最初のサムネイルはビデオではなく写真です)。

この例では、レイアウトは Bootstrap を利用しています。以下は上記の CSS です(適当です)。

.insta_media img {
  max-width: 100%;
}  
.like_count, .comments_count {
  position: absolute;
  bottom: 10px;
  color: #fff;
}
.like_count {
  right: 25%;
}
.comments_count {
  right: 10%;
}
.like_count::before {
  content: "";
  display: inline-block;
  height: 18px;
  width: 18px;
  vertical-align: -3px;
  margin-right: 5px;
  background-repeat: no-repeat;
  /*アイコンのSVG画像*/
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23ffffff' viewBox='0 0 16 16'%3E  %3Cpath d='M8.864.046C7.908-.193 7.02.53 6.956 1.466c-.072 1.051-.23 2.016-.428 2.59-.125.36-.479 1.013-1.04 1.639-.557.623-1.282 1.178-2.131 1.41C2.685 7.288 2 7.87 2 8.72v4.001c0 .845.682 1.464 1.448 1.545 1.07.114 1.564.415 2.068.723l.048.03c.272.165.578.348.97.484.397.136.861.217 1.466.217h3.5c.937 0 1.599-.477 1.934-1.064a1.86 1.86 0 0 0 .254-.912c0-.152-.023-.312-.077-.464.201-.263.38-.578.488-.901.11-.33.172-.762.004-1.149.069-.13.12-.269.159-.403.077-.27.113-.568.113-.857 0-.288-.036-.585-.113-.856a2.144 2.144 0 0 0-.138-.362 1.9 1.9 0 0 0 .234-1.734c-.206-.592-.682-1.1-1.2-1.272-.847-.282-1.803-.276-2.516-.211a9.84 9.84 0 0 0-.443.05 9.365 9.365 0 0 0-.062-4.509A1.38 1.38 0 0 0 9.125.111L8.864.046zM11.5 14.721H8c-.51 0-.863-.069-1.14-.164-.281-.097-.506-.228-.776-.393l-.04-.024c-.555-.339-1.198-.731-2.49-.868-.333-.036-.554-.29-.554-.55V8.72c0-.254.226-.543.62-.65 1.095-.3 1.977-.996 2.614-1.708.635-.71 1.064-1.475 1.238-1.978.243-.7.407-1.768.482-2.85.025-.362.36-.594.667-.518l.262.066c.16.04.258.143.288.255a8.34 8.34 0 0 1-.145 4.725.5.5 0 0 0 .595.644l.003-.001.014-.003.058-.014a8.908 8.908 0 0 1 1.036-.157c.663-.06 1.457-.054 2.11.164.175.058.45.3.57.65.107.308.087.67-.266 1.022l-.353.353.353.354c.043.043.105.141.154.315.048.167.075.37.075.581 0 .212-.027.414-.075.582-.05.174-.111.272-.154.315l-.353.353.353.354c.047.047.109.177.005.488a2.224 2.224 0 0 1-.505.805l-.353.353.353.354c.006.005.041.05.041.17a.866.866 0 0 1-.121.416c-.165.288-.503.56-1.066.56z'/%3E%3C/svg%3E");
}
.comments_count::before {
  content: "";
  display: inline-block;
  height: 18px;
  width: 18px;
  vertical-align: -3px;
  margin-right: 5px;
  background-repeat: no-repeat;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23ffffff' viewBox='0 0 16 16'%3E  %3Cpath d='M2.678 11.894a1 1 0 0 1 .287.801 10.97 10.97 0 0 1-.398 2c1.395-.323 2.247-.697 2.634-.893a1 1 0 0 1 .71-.074A8.06 8.06 0 0 0 8 14c3.996 0 7-2.807 7-6 0-3.192-3.004-6-7-6S1 4.808 1 8c0 1.468.617 2.83 1.678 3.894zm-.493 3.905a21.682 21.682 0 0 1-.713.129c-.2.032-.352-.176-.273-.362a9.68 9.68 0 0 0 .244-.637l.003-.01c.248-.72.45-1.548.524-2.319C.743 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7-3.582 7-8 7a9.06 9.06 0 0 1-2.347-.306c-.52.263-1.639.742-3.468 1.105z'/%3E%3C/svg%3E");
}
.video_icon {
  position: absolute;
  top: 3px;
  right: 5px;
}
.video_icon::before {
  content: "";
  display: inline-block;
  height: 18px;
  width: 18px;
  vertical-align: -3px;
  background-repeat: no-repeat;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23ffffff' viewBox='0 0 16 16'%3E  %3Cpath fill-rule='evenodd' d='M0 5a2 2 0 0 1 2-2h7.5a2 2 0 0 1 1.983 1.738l3.11-1.382A1 1 0 0 1 16 4.269v7.462a1 1 0 0 1-1.406.913l-3.111-1.382A2 2 0 0 1 9.5 13H2a2 2 0 0 1-2-2V5zm11.5 5.175 3.5 1.556V4.269l-3.5 1.556v4.35zM2 4a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h7.5a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H2z'/%3E%3C/svg%3E");
}

アイコンは Bootstrap Icon の SVG を CSS で使って表示しています。

関連ページ:CSS で svg 要素を表示

ID やトークンを別ファイルに保存

前述の例ではビジネスアカウント ID やアクセストークンの値をファイルに直接記述していますが、別ファイルに記述して別途読み込むこともできます。

例えば、パブリックからアクセスできない場所に配置するか .htaccess でアクセス制御すれば安全です。

以下は insta_vars.php というファイルにビジネスアカウント ID とアクセストークンの値を記述しておき、それらの値を require で読み込む例です。

libs というディレクトリを作成し、その中に insta_vars.php を保存します。

├── libs
│   ├── .htaccess  //libs ディレクトリへのアクセスを制御
│   └── insta_vars.php //ビジネスアカウント ID とアクセストークンの値を記述
└── insta_sample.php //インスタグラム投稿の一覧を出力するファイル

この例では、insta_vars.php を配置するディレクトリ(libs)に以下のような内容の .htaccess を配置してこのディレクトリにアクセスできないようにします。

.htaccess
deny from all

insta_vars.php ではビジネスアカウント ID とアクセストークンの値を define() で定数として定義しておきます。

insta_vars.php
<?php
// Instagram ビジネスアカウント ID
define('IGBA_ID', '123456789');
// Instagram ユーザーアクセストークン
define('INSTA_TOKEN', 'EAAFPcGBdBr0BAPuJxgo..........Y3kT4ZBf');

require でビジネスアカウント ID とアクセストークンの値を読み込みます。

insta_sample.php(PHP の冒頭部分一部抜粋)
<?php
//ビジネスアカウント ID とアクセストークンを記述したファイルの読み込み
require 'libs/insta_vars.php';
//Instagram ビジネスアカウント ID
$business_id = IGBA_ID;
// ユーザーアクセストークン
$token = INSTA_TOKEN; 
//グラフAPI ホストURL(ルートエンドポイント)
$api = 'https://graph.facebook.com/';
//API のバージョン
$version = 'v12.0';
//取得するメディアの件数(0 または false を指定した場合は件数の制限なし)
$count = '4';

・・・以下は前述の例と同じなので省略・・・