Skip to content

Commit 6efa183

Browse files
committed
fix(instagram): fallback profile bootstrap on rate limits
1 parent 7e29647 commit 6efa183

5 files changed

Lines changed: 123 additions & 4 deletions

File tree

Binary file not shown.

connector-index.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,43 @@
495495
"iconKey": "instagram",
496496
"defaultScope": "instagram.profile"
497497
}
498+
},
499+
{
500+
"connectorId": "instagram-api-playwright",
501+
"company": "meta",
502+
"version": "2.0.3",
503+
"name": "Instagram (API)",
504+
"status": "experimental",
505+
"description": "Exports your Instagram profile, posts, followers, following, and ad interests using API-first network replay (no DOM scraping for data).",
506+
"sourceFiles": {
507+
"script": "meta/instagram-api-playwright.js",
508+
"metadata": "meta/instagram-api-playwright.json"
509+
},
510+
"publishedAt": "2026-04-15T00:00:00Z",
511+
"gitRef": "fix/instagram-api-compat",
512+
"pageApiVersion": 1,
513+
"manifestSha256": "sha256:6044251dec7f7e8aef8462b740cff9f1863b1d608761a817cd95c6692e01433d",
514+
"scriptSha256": "sha256:22d2e3ac4636fd87bd0724550c12fa69bf35f4af08ace5daad7e4418517320c9",
515+
"artifactSha256": "sha256:b0b1d5a2a353be166194f7a28e70fcec6af0d7ff6164032227349c50e01052e0",
516+
"artifactPath": "artifacts/instagram-api-playwright/instagram-api-playwright-2.0.3.tgz",
517+
"artifactUrl": "https://raw.githubusercontent.com/vana-com/data-connectors/fix/instagram-api-compat/artifacts/instagram-api-playwright/instagram-api-playwright-2.0.3.tgz",
518+
"scopes": [
519+
"instagram.profile",
520+
"instagram.posts",
521+
"instagram.followers",
522+
"instagram.following",
523+
"instagram.ads"
524+
],
525+
"consumerMetadata": {
526+
"sourceId": "instagram",
527+
"displayName": "Instagram (API)",
528+
"brandDomain": "instagram.com",
529+
"aliases": [
530+
"meta"
531+
],
532+
"iconKey": "instagram",
533+
"defaultScope": "instagram.profile"
534+
}
498535
}
499536
],
500537
"instagram-playwright": [

connectors/meta/instagram-api-playwright.js

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
const IG_APP_ID = '936619743392459';
1818
const PLATFORM = 'instagram';
19-
const VERSION = '2.0.2-api-playwright';
19+
const VERSION = '2.0.3-api-playwright';
2020
const CANONICAL_SCOPES = [
2121
'instagram.profile',
2222
'instagram.posts',
@@ -29,6 +29,8 @@ const FRIENDSHIP_PAGE_SIZE = 50;
2929
const REQUEST_DELAY_MS = 800;
3030
const AUTH_SETTLE_DELAY_MS = 2500;
3131
const RATE_LIMIT_BACKOFF_MS = [3000, 7000, 15000];
32+
const PROFILE_CAPTURE_WAIT_MS = 3000;
33+
const PROFILE_CAPTURE_MAX_ATTEMPTS = 15;
3234
const MAX_POSTS_PAGES = 50;
3335
const MAX_FRIENDSHIP_PAGES = 2000;
3436
const DISCOVERY_TIMEOUT_MS = 20000;
@@ -951,10 +953,90 @@ const mapProfile = (u, fallbackUsername) => {
951953
};
952954
};
953955

956+
const normalizeCapturedProfileUser = (user) => ({
957+
...user,
958+
biography: user.biography ?? user.bio ?? null,
959+
profile_pic_url_hd:
960+
user.profile_pic_url_hd ?? user.hd_profile_pic_url_info?.url ?? null,
961+
edge_followed_by:
962+
user.edge_followed_by ??
963+
(user.follower_count != null ? { count: user.follower_count } : null),
964+
edge_follow:
965+
user.edge_follow ??
966+
(user.following_count != null ? { count: user.following_count } : null),
967+
edge_owner_to_timeline_media:
968+
user.edge_owner_to_timeline_media ??
969+
(user.media_count != null ? { count: user.media_count } : null),
970+
is_business_account:
971+
user.is_business_account ?? user.is_business ?? null,
972+
followed_by_viewer:
973+
user.followed_by_viewer ?? user.viewer_data?.followed_by_viewer ?? null,
974+
follows_viewer:
975+
user.follows_viewer ?? user.viewer_data?.follows_viewer ?? null,
976+
requested_by_viewer:
977+
user.requested_by_viewer ?? user.viewer_data?.requested_by_viewer ?? null,
978+
has_requested_viewer:
979+
user.has_requested_viewer ?? user.viewer_data?.has_requested_viewer ?? null,
980+
blocked_by_viewer:
981+
user.blocked_by_viewer ?? user.viewer_data?.blocked_by_viewer ?? null,
982+
has_blocked_viewer:
983+
user.has_blocked_viewer ?? user.viewer_data?.has_blocked_viewer ?? null,
984+
restricted_by_viewer:
985+
user.restricted_by_viewer ?? user.viewer_data?.restricted_by_viewer ?? null,
986+
is_guardian_of_viewer:
987+
user.is_guardian_of_viewer ?? user.viewer_data?.is_guardian_of_viewer ?? null,
988+
is_supervised_by_viewer:
989+
user.is_supervised_by_viewer ?? user.viewer_data?.is_supervised_by_viewer ?? null,
990+
});
991+
992+
const collectProfileViaPageCapture = async (username) => {
993+
await page.clearNetworkCaptures();
994+
await page.captureNetwork({
995+
urlPattern: '/graphql',
996+
bodyPattern:
997+
'PolarisProfilePageContentQuery|ProfilePageQuery|UserByUsernameQuery',
998+
key: 'instagramProfileResponse',
999+
});
1000+
1001+
await safeGoto(
1002+
'https://www.instagram.com/' + encodeURIComponent(username) + '/',
1003+
);
1004+
await page.sleep(PROFILE_CAPTURE_WAIT_MS);
1005+
1006+
for (let attempt = 0; attempt < PROFILE_CAPTURE_MAX_ATTEMPTS; attempt++) {
1007+
const response = await page.getCapturedResponse('instagramProfileResponse');
1008+
const user =
1009+
response?.data?.data?.user ??
1010+
response?.data?.user ??
1011+
response?.user ??
1012+
null;
1013+
1014+
if (user) {
1015+
return mapProfile(normalizeCapturedProfileUser(user), username);
1016+
}
1017+
1018+
await page.sleep(1000);
1019+
}
1020+
1021+
throw new Error(
1022+
'profile fallback capture did not yield a usable user payload for ' +
1023+
username,
1024+
);
1025+
};
1026+
9541027
const collectProfile = async (username) => {
9551028
const url = 'https://www.instagram.com/api/v1/users/web_profile_info/?username=' + encodeURIComponent(username);
9561029
const res = await fetchApi(url);
957-
if (res._error) throw new Error('profile fetch failed: ' + res._error);
1030+
if (res._error) {
1031+
if (isRateLimitError(res)) {
1032+
await page.setData(
1033+
'status',
1034+
'Instagram profile API rate limited; falling back to profile page capture...',
1035+
);
1036+
return collectProfileViaPageCapture(username);
1037+
}
1038+
throw new Error('profile fetch failed: ' + res._error);
1039+
}
9581040
const user = res.data && res.data.data && res.data.data.user;
9591041
if (!user) throw new Error('profile: no user in response for ' + username);
9601042
return mapProfile(user, username);

connectors/meta/instagram-api-playwright.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"manifest_version": "1.0",
33
"connector_id": "instagram-api-playwright",
44
"source_id": "instagram",
5-
"version": "2.0.2",
5+
"version": "2.0.3",
66
"name": "Instagram (API)",
77
"company": "Meta",
88
"description": "Exports your Instagram profile, posts, followers, following, and ad interests using API-first network replay (no DOM scraping for data).",

registry.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
{
8282
"id": "instagram-api-playwright",
8383
"company": "meta",
84-
"version": "2.0.2",
84+
"version": "2.0.3",
8585
"name": "Instagram (API)",
8686
"status": "experimental",
8787
"description": "Exports your Instagram profile, posts, followers, following, and ad interests using API-first network replay (no DOM scraping for data).",

0 commit comments

Comments
 (0)