chore(scripts): update Instagram e2e scraper script

This commit is contained in:
Anish Sarkar 2026-07-11 02:21:26 +05:30
parent ba70ae1f45
commit 949cc1b29f

View file

@ -14,11 +14,14 @@ It:
approach is invalid later steps are skipped. approach is invalid later steps are skipped.
Step 1 scrape a profile's posts. Step 1 scrape a profile's posts.
Step 2 scrape a profile's reels. Step 2 scrape a profile's reels.
Step 3 fetch comments for a discovered post URL. Step 3 anonymous single-post extraction for a discovered ``/p/`` URL.
Step 4 fetch profile / hashtag / place details. Step 4 fetch profile details.
Step 5 run a discovery search. Step 5 run a profile discovery search (Google-backed).
Step 6 dump trimmed, PII-anonymized raw fixtures into Step 6 dump trimmed, PII-anonymized raw fixtures into
tests/unit/platforms/instagram/fixtures/ for the offline parser tests. tests/unit/platforms/instagram/fixtures/ for the offline parser tests.
Anonymous-only: hashtag/place feeds and comment threads are login-walled and are
not part of the scraper, so there are no steps for them.
""" """
import asyncio import asyncio
@ -41,15 +44,15 @@ from app.proprietary.platforms.instagram import ( # noqa: E402
scrape_instagram, scrape_instagram,
) )
from app.proprietary.platforms.instagram.fetch import ( # noqa: E402 from app.proprietary.platforms.instagram.fetch import ( # noqa: E402
fetch_html,
fetch_json, fetch_json,
proxy_session, proxy_session,
warm_session, warm_session,
) )
from app.proprietary.platforms.instagram.url_resolver import resolve_url # noqa: E402
_PROFILE = "natgeo" _PROFILE = "natgeo"
_HASHTAG = "https://www.instagram.com/explore/tags/travel/" _SEARCH_TERM = "national geographic"
_PLACE = "https://www.instagram.com/explore/locations/213385402/new-york-new-york/"
_SEARCH_TERM = "coffee"
_FIXTURE_DIR = ( _FIXTURE_DIR = (
_BACKEND_ROOT / "tests" / "unit" / "platforms" / "instagram" / "fixtures" _BACKEND_ROOT / "tests" / "unit" / "platforms" / "instagram" / "fixtures"
@ -129,30 +132,27 @@ async def step2_reels() -> bool:
return _check("reels returned items", len(items) >= 0, f"{len(items)} reels") return _check("reels returned items", len(items) >= 0, f"{len(items)} reels")
async def step3_comments(post_url: str | None) -> bool: async def step3_single_post(post_url: str | None) -> bool:
_hr("STEP 3 — comments for a post") _hr("STEP 3 — single-post extraction for a /p/ URL")
if not post_url: if not post_url:
return _check("had a post URL", False, "step 1 found no post") return _check("had a post URL", False, "step 1 found no post")
items = await scrape_instagram( items = await scrape_instagram(
InstagramScrapeInput( InstagramScrapeInput(
resultsType="comments", directUrls=[post_url], resultsLimit=10 resultsType="posts", directUrls=[post_url], resultsLimit=1
), ),
limit=10, limit=1,
) )
print(f" {len(items)} comments for {post_url}") got = items[0] if items else {}
return _check("comments returned", len(items) >= 0, f"{len(items)} comments") print(f" {len(items)} item for {post_url} | owner={got.get('ownerUsername')}")
return _check("single post returned an item", len(items) > 0, post_url)
async def step4_details() -> bool: async def step4_details() -> bool:
_hr("STEP 4 — profile / hashtag / place details") _hr("STEP 4 — profile details")
items = await scrape_instagram( items = await scrape_instagram(
InstagramScrapeInput( InstagramScrapeInput(
resultsType="details", resultsType="details",
directUrls=[ directUrls=[f"https://www.instagram.com/{_PROFILE}/"],
f"https://www.instagram.com/{_PROFILE}/",
_HASHTAG,
_PLACE,
],
), ),
limit=10, limit=10,
) )
@ -162,12 +162,12 @@ async def step4_details() -> bool:
async def step5_search() -> bool: async def step5_search() -> bool:
_hr("STEP 5 — discovery search") _hr("STEP 5 — profile discovery search (Google-backed)")
items = await scrape_instagram( items = await scrape_instagram(
InstagramScrapeInput( InstagramScrapeInput(
resultsType="posts", resultsType="posts",
search=_SEARCH_TERM, search=_SEARCH_TERM,
searchType="hashtag", searchType="profile",
searchLimit=3, searchLimit=3,
resultsLimit=3, resultsLimit=3,
), ),
@ -177,7 +177,7 @@ async def step5_search() -> bool:
return _check("search returned results", len(items) >= 0, f"{len(items)} items") return _check("search returned results", len(items) >= 0, f"{len(items)} items")
async def step6_dump_fixtures() -> bool: async def step6_dump_fixtures(post_url: str | None) -> bool:
_hr("STEP 6 — dump trimmed, anonymized fixtures for offline tests") _hr("STEP 6 — dump trimmed, anonymized fixtures for offline tests")
profile = await fetch_json( profile = await fetch_json(
"api/v1/users/web_profile_info/", {"username": _PROFILE} "api/v1/users/web_profile_info/", {"username": _PROFILE}
@ -189,6 +189,17 @@ async def step6_dump_fixtures() -> bool:
json.dumps(_anonymize(profile)), encoding="utf-8" json.dumps(_anonymize(profile)), encoding="utf-8"
) )
wrote.append("profile.json") wrote.append("profile.json")
resolved = resolve_url(post_url) if post_url else None
if resolved is not None and resolved.kind in ("post", "reel"):
html = await fetch_html(f"p/{resolved.value}/")
if html:
(_FIXTURE_DIR / "post.json").write_text(
json.dumps(
{"url": post_url, "shortcode": resolved.value, "html": html}
),
encoding="utf-8",
)
wrote.append("post.json")
return _check("dumped fixtures", bool(wrote), f"{wrote} -> {_FIXTURE_DIR}") return _check("dumped fixtures", bool(wrote), f"{wrote} -> {_FIXTURE_DIR}")
@ -214,10 +225,10 @@ async def main() -> int:
results.append(await step1_posts()) results.append(await step1_posts())
results.append(await step2_reels()) results.append(await step2_reels())
post_url = await _first_post_url() post_url = await _first_post_url()
results.append(await step3_comments(post_url)) results.append(await step3_single_post(post_url))
results.append(await step4_details()) results.append(await step4_details())
results.append(await step5_search()) results.append(await step5_search())
results.append(await step6_dump_fixtures()) results.append(await step6_dump_fixtures(post_url))
_hr("SUMMARY") _hr("SUMMARY")
print(f" {sum(results)}/{len(results)} steps passed") print(f" {sum(results)}/{len(results)} steps passed")
return 0 if all(results) else 1 return 0 if all(results) else 1