{"record":{"id":"d2931202e35365fa","repo":"DIYgod/RSSHub","slug":"failed-to-fetch-thread-data","errorCode":null,"errorMessage":"Failed to fetch thread data","messagePattern":"Failed to fetch thread data","errorType":"exception","errorClass":"Error","httpStatus":503,"severity":"error","filePath":"lib/routes/threads/index.ts","lineNumber":96,"sourceCode":"    let threadsData: ThreadItem[] | null = null;\n    for (const el of document.querySelectorAll('script[data-sjs]')) {\n        try {\n            const data = JSONPath({\n                path: '$..thread_items[0]',\n                json: JSON.parse(el.textContent || ''),\n            });\n\n            if (data?.length > 0) {\n                threadsData = data as ThreadItem[];\n                break;\n            }\n        } catch {\n            // Skip invalid JSON\n        }\n    }\n\n    if (!threadsData) {\n        throw new Error('Failed to fetch thread data');\n    }\n\n    debugJson.profileId = userId;\n    debugJson.response = { response: threadsData };\n\n    const userData: ThreadUser = threadsData[0]?.post?.user || { username: user, profile_pic_url: '' };\n\n    const items = threadsData\n        .filter((item) => user === item.post.user?.username)\n        .map((item) => ({\n            author: user,\n            title: buildContent(item, options).title,\n            description: buildContent(item, options).description,\n            pubDate: parseDate(item.post.taken_at, 'X'),\n            link: threadUrl(item.post.code),\n        }));\n\n    debugJson.items = items;","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/threads/index.ts#L78-L114","documentation":"The Threads route scrapes the profile HTML, parses each <script data-sjs> blob as JSON, and runs a JSONPath for $..thread_items[0]. If none of the script blobs yield thread_items, threadsData stays null and the route throws 'Failed to fetch thread data'. The cause is upstream: Threads did not embed the expected data (markup changed, login wall, rate limit, or empty/private profile).","triggerScenarios":"The Threads profile page no longer embeds thread_items in a data-sjs script; the request was served a login/consent page instead of the SPA shell; the profile is private or has no threads; the JSONPath expression no longer matches the new data shape.","commonSituations":"Threads changes its embedded JSON structure (frequent for anti-scraping sites); the hardcoded iOS-Safari User-Agent gets blocked; rate-limiting after polling; profile handle typo.","solutions":["Open profileUrl(user) in a browser with the same UA and confirm thread_items is present in the data-sjs scripts; if not, update the JSONPath to the new location.","If a login/consent page is served, rotate the egress IP or adjust headers to look less like a scraper.","Confirm the username is correct and the profile is public and non-empty.","Add a logged slice of the response when threadsData stays null so future markup changes are diagnosable."],"exampleFix":"// before\nif (!threadsData) {\n    throw new Error('Failed to fetch thread data');\n}\n\n// after: include diagnostic context\nif (!threadsData) {\n    const scriptCount = document.querySelectorAll('script[data-sjs]').length;\n    const looksLikeLogin = /login|challenge/i.test(response);\n    throw new Error(`Failed to fetch thread data for \"${user}\" (data-sjs scripts: ${scriptCount}, login-wall suspected: ${looksLikeLogin})`);\n}","handlingStrategy":"retry","validationCode":"function profileEmbedsThreads(html: string): boolean {\n    return /data-sjs/.test(html) && /thread_items/.test(html);\n}\n// fetch the profile HTML first; if !profileEmbedsThreads(html), back off or rotate IP before parsing","typeGuard":"function profileHtmlHasThreadData(html: string): boolean {\n    return /<script[^>]*data-sjs/.test(html) && /thread_items/.test(html);\n}","tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n    try {\n        return await buildThreadsFeed(user);\n    } catch (e) {\n        if (e instanceof Error && /Failed to fetch thread data/.test(e.message)) {\n            await sleep(10_000 * (attempt + 1)); // likely rate-limit/markup drift\n            continue;\n        }\n        throw e;\n    }\n}\nthrow new Error(`Threads feed unavailable for \"${user}\" after retries`);","preventionTips":["Keep the JSONPath ($..thread_items[0]) in sync with Threads' current embedded shape.","Rotate egress IPs and keep the iOS-Safari UA current to avoid login walls.","Confirm the username is correct and the profile is public before subscribing.","Log a slice of the HTML when thread_items is absent to detect markup changes fast."],"tags":["scraping","anti-crawler","threads","parsing","network"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}