Instagram profile lane (--ig-creators) parses to empty items: _parse_items doesn't unwrap the media envelope

Author: rafagiraldelli-hueCreated Aug 8, 2026Updated Sep 12, 2026

Summary

_user_reels() (the --ig-creators profile lane) returns items that the ScrapeCreators /v1/instagram/user/reels endpoint wraps in a media envelope, but _parse_items() reads every field off the top level. Result: the fetch succeeds, the log prints -> 12 reels from @handle, and then every parsed item is empty (url="", text="", date=None, views/likes/comments=0) and is dropped downstream.

The profile lane is the only reliable Instagram path (keyword search is flaky by design — see the 500-retry-with-hashtag-form comment at instagram.py:313-317), so in practice this makes Instagram return near-zero on every run that relies on --ig-creators.

Impact

Three consecutive runs on Windows / v3.18.4, all with --ig-creators and valid SCRAPECREATORS_API_KEY:

run profile lane fetched items in report
brand topic, default depth 12 + 12 reels 2 (both from keyword search)
two brands, --deep --search instagram 12 + 12 reels 0 (source marked errored)
short topic, --deep --search instagram 12 + 12 reels 3 (all off-topic keyword noise)

Fetching the same two handles directly and unwrapping media yields 24 usable reels, all within the 30-day window, with captions, engagement and comments.

Root cause

scripts/lib/instagram.py

_user_reels() returns the raw list unchanged:

raw_items = data.get("items") or data.get("reels") or data.get("data") or []
_log(f"  -> {len(raw_items)} reels from @{handle}")
return raw_items

_parse_items() then reads fields at the top level (instagram.py:182-221):

reel_pk    = str(raw.get("id", raw.get("pk", "")))
shortcode  = raw.get("shortcode", raw.get("code", ""))
caption_obj= raw.get("caption", "")
play_count = raw.get("video_play_count") or raw.get("video_view_count") or raw.get("play_count") or 0

But /v1/instagram/user/reels returns:

{
  "success": true,
  "items": [
    { "media": { "pk": "...", "code": "...", "taken_at": 1785596409,
                 "caption": {"text": "..."}, "play_count": 0,
                 "like_count": 0, "comment_count": 0, "user": {...} } }
  ],
  "paging_info": {...}
}

Every lookup misses, so each item parses to zeros/empties. The search lane (/v2/instagram/reels/search) returns unwrapped objects, which is why keyword results parse fine and only the profile lane is affected.

Suggested fix

Unwrap in _user_reels() so _parse_items() keeps one contract:

raw_items = data.get("items") or data.get("reels") or data.get("data") or []
raw_items = [(it.get("media") or it) for it in raw_items]

Note taken_at is a unix timestamp on this shape, so the date path may need the same treatment as the search shape for the hard date filter at instagram.py:346 to work.

Secondary issue (same investigation)

When one search variant 404s, the whole Instagram source is marked failed and items that other lanes already returned are discarded. Observed with topic Guday e Gummy Original no Instagram:

[Instagram]   -> 12 reels from @guday.br
[Instagram]   -> 12 reels from @gummy
[Instagram] ScrapeCreators error: HTTP 404: Not Found     <- variant "... reaction edit"
...
Source Coverage
- Instagram: 0 items (error: HTTPError: HTTP 404: Not Found)

A per-lane failure probably should degrade to partial rather than zero a source whose other lanes succeeded.

Environment

  • last30days v3.18.4 (Claude Code plugin, user scope)
  • Windows 11, Python 3.13.14
  • INCLUDE_SOURCES=tiktok,instagram,youtube_comments,tiktok_comments,instagram_comments

Source: mvanhorn/last30days-skill