※ この記事にはアフィリエイトリンクが含まれています。リンク経由で購入しても読者の皆さんに追加費用は発生しません。収益は本サイトの運営費に充てています。
Instagram Graph APIではプロフィールの変更ができません。読み取りと投稿はできるのに、自己紹介文やアイコン画像の設定だけはAPI対象外。僕はこの壁をPlaywrightのモバイルエミュレーションで突破しました。この記事では、実際に動いたコードと5回失敗した過程を全部書きます。
こんな方に読んでほしい記事です:
- Instagram APIの「できること・できないこと」を体系的に知りたい
- Playwrightでモバイルサイトを操作する具体的なコードが欲しい
page.evaluate()でオーバーレイを突破する実装パターンを知りたい- 前回のアフィリリンク自動化の続きを追いたい
Instagram/Threads APIの「できること・できないこと」完全整理
まず、APIでどこまでやれるのかをハッキリさせておきます。公式ドキュメントを読んで整理した結果がこの表です。
| 操作 | Instagram Graph API | Threads API | Playwright |
|---|---|---|---|
| プロフィール情報の取得 | ✅ | ✅ | ✅ |
| プロフィール画像の変更 | ❌ | ❌ | ✅ |
| 自己紹介文の変更 | ❌ | ❌ | ✅ |
| プロフィールリンクの変更 | ❌ | ❌ | ✅ |
| フィード投稿(画像必須) | ✅ | — | ✅ |
| テキスト投稿 | ❌ | ✅ | ✅ |
| ストーリーズ投稿 | ✅ | — | ✅ |
| カルーセル投稿 | ✅ | ✅ | ✅ |
| コメント管理 | ✅ | ✅ | ✅ |
| インサイト取得 | ✅ | ✅ | △ |
| DM送信 | ❌ | ❌ | ✅ |
| フォロー/アンフォロー | ❌ | ❌ | ✅(非推奨) |
要するに、APIは「投稿」と「分析」に特化していて、「アカウント設定」は一切対象外です。プロフィール変更、DM、フォロー操作はPlaywrightでブラウザを操作するしかありません。
⚠️ Playwrightでの自動操作に関する注意事項
PlaywrightによるInstagramの自動操作は、利用規約に抵触する可能性があります。今回は「初期設定を1回だけ自動化する」用途です。自分のアカウント限定、操作頻度は最小限、自動いいね・自動フォローはやらない。日常的にPlaywrightでInstagramを操作し続けることは推奨しません。対象サービスの利用規約を都度確認してください。
Playwrightのモバイルエミュレーションでログインする
Instagramのデスクトップ版は、PlaywrightでアクセスしてもReactのSPAがうまくレンダリングされず、ログインフォームが表示されない問題があります。解決策はモバイルエミュレーションです。
contextの設定コード
const context = await browser.newContext({
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)...",
viewport: { width: 390, height: 844 },
isMobile: true,
hasTouch: true,
ignoreHTTPSErrors: true,
});
iPhoneとして接続するだけで、モバイル版のログインフォームがレンダリングされます。さらに重要なのが、デスクトップとモバイルでフォームのinput name属性が違う点です。
| 環境 | ユーザー名フィールド | パスワードフィールド |
|---|---|---|
| デスクトップUI | name="email" |
name="pass" |
| モバイルUI | name="username" |
name="password" |
公式ドキュメントにはこの違いは書かれていません。実際のDOMを確認するしかないのが現実です。Playwrightでログインフォームを操作するときは、まず全inputのname属性を出力するデバッグを入れるのが鉄則です。
ログイン実装のポイント
// フィールド名が環境で異なるので両方対応
await page.locator('input[name="username"], input[name="email"]')
.first().fill(username);
await page.locator('input[name="password"], input[name="pass"]')
.first().fill(password);
// ログインボタン(モバイルUIではdiv[role="button"]の場合あり)
await page.locator(
'button[type="submit"], div[role="button"]:has-text("ログイン")'
).first().click();
ログイン後は「情報を保存しますか?」「通知を許可しますか?」のダイアログが出るので、ループで「後で」ボタンを押して閉じます。
page.evaluate()でオーバーレイを突破する
ログインできたら/accounts/edit/に遷移して自己紹介を設定します。ここで最大のハマりポイントが登場します。
問題:オーバーレイがクリックをブロックする
Instagramのプロフィール編集ページでは、Cookie同意バナーやログイン確認ダイアログがオーバーレイとして表示されます。Playwrightのclick()は「要素は見えているが別の要素がポインターイベントを遮っている」とエラーになります。
解決策:page.evaluate()で直接DOM操作
前回のA8.net操作でも使ったpage.evaluate()パターンです。ブラウザ内のJavaScriptとして実行するので、オーバーレイの影響を受けません。
// テキストエリアに自己紹介文を入力
const bioField = page.locator("textarea").first();
await bioField.evaluate((el) => {
el.focus();
el.value = "";
});
await bioField.fill(bioText);
// 「送信する」ボタンをevaluateで押す
await page.evaluate(() => {
const buttons = document.querySelectorAll('div[role="button"]');
for (const btn of buttons) {
if (btn.textContent?.includes("送信")) {
btn.click();
return;
}
}
});
Playwrightのlocatorでは「要素を見つけて→クリックする」までに複数のチェックが入ります。しかしevaluate()はブラウザ内で直接実行されるので、オーバーレイの存在を無視できます。業務系WebアプリやSNSのようにUIが複雑なサイトでは、evaluate()が最も安定する操作方法です。
プロフィール画像の自動設定——5回失敗した記録
自己紹介文は安定して動きました。問題はプロフィール画像です。正直に書くと、5回試行錯誤して最終的に手動に切り替えました。
うまくいった部分
// 「写真を変更」ボタンをevaluateで押す
await page.evaluate(() => {
const allElements = document.querySelectorAll('div[role="button"], a, span');
for (const el of allElements) {
if (el.textContent?.trim() === "写真を変更") {
el.click();
return;
}
}
});
// ファイル選択(input[type="file"]が2つあるので.first()必須)
await page.locator('input[type="file"]').first()
.setInputFiles(avatarPath);
ファイル選択までは安定して動きます。画像のプレビューも表示される。
うまくいかなかった部分:フィルター画面が不安定
ファイル選択後にフィルター編集画面が出ますが、このUIが実行のたびに異なる挙動をします。
- 1回目:「閉じる | 保存する | Ludwig | Moon…」とフィルター画面のボタンが並ぶ
- 2回目:フィルター画面をスキップしてプロフィール編集ページに戻る
- 3回目:ボタンのテキストが「確定する」と「保存する」でブレる
全ボタンのテキストをpage.evaluate()で出力するデバッグを入れて調査しましたが、安定パターンが見つかりませんでした。5回試行して断念し、プロフィール画像だけは手動で設定しました。
この経験からの学び
Instagramの画像アップロード系UIはPlaywrightでの完全自動化が極めて難しい。フィルター画面やトリミング画面などの中間ステップが不安定で、毎回同じフローにならない。一方、テキスト入力系(自己紹介文)は安定して動く。「自動化できる部分」と「手動の方が早い部分」を見極めて、費用対効果の高いところだけ自動化するのが現実的です。
Threads/Instagram投稿APIの実装
プロフィール設定と並行して、投稿APIも実装しました。こちらはPlaywrightではなく正規のAPIです。
Threads APIのテキスト投稿(2ステップ必須)
// Step 1: メディアコンテナを作成
const createRes = await fetch(
`https://graph.threads.net/v1.0/${userId}/threads`, {
method: "POST",
body: JSON.stringify({
media_type: "TEXT",
text: "投稿テキスト",
access_token: token,
}),
});
const { id: containerId } = await createRes.json();
// Step 2: 公開する
await fetch(
`https://graph.threads.net/v1.0/${userId}/threads_publish`, {
method: "POST",
body: JSON.stringify({
creation_id: containerId,
access_token: token,
}),
});
1ステップで投稿できないのがやや面倒ですが、画像付き投稿の場合はコンテナ作成後に5秒ほど待ってから公開APIを叩く必要があります。画像処理が完了していないと公開エラーになるためです。
Instagram Graph APIの画像投稿
InstagramはThreadsと違い、テキストだけの投稿ができません。必ず画像URLが必要で、かつローカルファイルは直接アップロードできない仕様です。先にWordPressのメディアライブラリに画像をアップロードし、その公開URLを指定する流れになります。
実際にThreadsに初投稿した結果
npx tsx src/publish/threads.ts "投稿テキスト"の1コマンドで投稿完了。構築した投稿APIで初投稿しました。
DALL-E 3でアイコン画像を自動生成した過程
プロフィール用のキャラクターアイコンはDALL-E 3 APIで生成しました。
人物→動物→フクロウに絞り込んだ判断基準
最初に人物イラスト3パターンを生成。しかしAI副業系のInstagramアカウントを20件以上調査すると、人物イラストのアイコンは飽和状態でした。差別化のために動物キャラクターに方向転換。タヌキ2パターン、フクロウ2パターンの計4パターンを追加生成し、「メガネをかけたフクロウがラップトップに向かっている」デザインに決定。
アイコン画像の生成で気をつけたのは以下の点です。
- 1024x1024px正方形:丸くトリミングされるので中央にキャラ配置
- quality: “hd”:standardより料金は倍だが、1回しか作らないので許容
- 「No text」が効かない問題:4パターン中1パターンでテキスト混入。ぶっちゃけDALL-E 3のテキスト制御はまだ不完全なので目視確認は必須
技術スタック・まとめ
- 言語:TypeScript(
tsxで直接実行) - ブラウザ自動操作:Playwright(モバイルエミュレーション + evaluate()パターン)
- SNS API:Threads API(テキスト+画像投稿)、Instagram Graph API(画像+カルーセル投稿)
- 画像生成:DALL-E 3 API
- ホスティング:ConoHa WINGの料金プランを確認する
関連書籍
- 『入門 Webフロントエンド E2E テスト』を楽天で見る——Playwrightのlocatorやevaluate()の基礎はこの本で学べます
- 『Web API設計実践入門』を楽天で見る——Instagram Graph APIやThreads APIの2ステップ投稿パターンなど、REST APIの設計思想が分かります
今回の学びは、APIでできることとできないことの境界を知り、できない部分をPlaywrightで補完するアプローチ。ただし全部を自動化できたわけではなく、画像アップロードのUI操作は手動に切り替えました。「自動化できる部分だけ自動化する」が現実的な落としどころです。
進展があればまたこのブログで報告します。



コメント