Initial Commit
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import { test } from '@playwright/test';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
test.use({ storageState: path.resolve(__dirname, '.auth', 'ig-session.json') });
|
||||
|
||||
function deepFind(obj, targetKey) {
|
||||
if (obj === null || typeof obj !== 'object') return null;
|
||||
if (targetKey in obj) return obj[targetKey];
|
||||
for (const value of Object.values(obj)) {
|
||||
const found = deepFind(value, targetKey);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractStoryContent(node) {
|
||||
return {
|
||||
user_id: node.id,
|
||||
username: node.user?.username,
|
||||
profile_pic_url: node.user?.profile_pic_url || null,
|
||||
seen: node.seen,
|
||||
latest_reel_media: node.latest_reel_media,
|
||||
muted: node.muted,
|
||||
items: (node.items || []).map((item) => ({
|
||||
id: item.pk,
|
||||
code: item.code,
|
||||
is_video: item.media_type === 2,
|
||||
taken_at: item.taken_at,
|
||||
expiring_at: item.expiring_at,
|
||||
image_url: item.image_versions2?.candidates?.[0]?.url || null,
|
||||
video_url: item.video_versions?.[0]?.url || null,
|
||||
accessibility_caption: item.accessibility_caption || null,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
test('fetch all story content directly', async ({ page }) => {
|
||||
test.setTimeout(60_000);
|
||||
|
||||
// Load the page first so we have valid csrf/session context in cookies
|
||||
await page.goto('https://www.instagram.com/');
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Pull the tray's user IDs the same way as your existing stories scrape
|
||||
const reelIds = await page.evaluate(() => {
|
||||
function deepFind(obj, targetKey) {
|
||||
if (obj === null || typeof obj !== 'object') return null;
|
||||
if (targetKey in obj) return obj[targetKey];
|
||||
for (const value of Object.values(obj)) {
|
||||
const found = deepFind(value, targetKey);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const scripts = document.querySelectorAll(
|
||||
'script[type="application/json"]',
|
||||
);
|
||||
for (const script of scripts) {
|
||||
if (script.textContent.includes('xdt_api__v1__feed__reels_tray')) {
|
||||
const json = JSON.parse(script.textContent);
|
||||
const reelsTray = deepFind(
|
||||
json,
|
||||
'xdt_api__v1__feed__reels_tray',
|
||||
);
|
||||
return (reelsTray?.tray || []).map((item) => item.id);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
if (reelIds.length === 0) {
|
||||
console.log('No reel IDs found in tray - nothing to fetch');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the csrf token from cookies (needed as a header for the POST)
|
||||
const cookies = await page.context().cookies();
|
||||
const csrfToken = cookies.find((c) => c.name === 'csrftoken')?.value;
|
||||
|
||||
const variables = {
|
||||
initial_reel_id: reelIds[0],
|
||||
reel_ids: reelIds,
|
||||
first: reelIds.length,
|
||||
last: 0,
|
||||
__relay_internal__pv__PolarisCommunityNoteStoriesLabelEnabledrelayprovider: true,
|
||||
__relay_internal__pv__PolarisAIGMMediaWebLabelEnabledrelayprovider: false,
|
||||
};
|
||||
|
||||
const body = new URLSearchParams({
|
||||
fb_api_req_friendly_name: 'PolarisStoriesV3ReelPageGalleryQuery',
|
||||
variables: JSON.stringify(variables),
|
||||
doc_id: '27768924676024297',
|
||||
server_timestamps: 'true',
|
||||
});
|
||||
|
||||
const response = await page.request.post(
|
||||
'https://www.instagram.com/graphql/query',
|
||||
{
|
||||
headers: {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
'x-csrftoken': csrfToken,
|
||||
'x-ig-app-id': '936619743392459',
|
||||
},
|
||||
data: body.toString(),
|
||||
},
|
||||
);
|
||||
|
||||
const json = await response.json();
|
||||
const edges =
|
||||
json?.data?.xdt_api__v1__feed__reels_media__connection?.edges || [];
|
||||
const storyContent = edges.map((edge) => extractStoryContent(edge.node));
|
||||
|
||||
const storyItems = storyContent.flatMap((story) =>
|
||||
story.items.map((item) => ({
|
||||
id: item.id,
|
||||
user_id: story.user_id,
|
||||
username: story.username,
|
||||
profile_pic_url: story.profile_pic_url,
|
||||
code: item.code,
|
||||
is_video: item.is_video,
|
||||
taken_at: item.taken_at,
|
||||
expiring_at: item.expiring_at,
|
||||
image_url: item.image_url,
|
||||
video_url: item.video_url,
|
||||
accessibility_caption: item.accessibility_caption,
|
||||
muted: story.muted,
|
||||
})),
|
||||
);
|
||||
|
||||
const outputPath = path.resolve(__dirname, 'output', 'scrape-stories.json');
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
fs.writeFileSync(outputPath, JSON.stringify(storyItems));
|
||||
});
|
||||
Reference in New Issue
Block a user