{"record":{"id":"7e8fbc6f42c96b6b","repo":"DIYgod/RSSHub","slug":"douban","errorCode":null,"errorMessage":"Douban 返回空数据，可能触发反爬或限频。请稍后重试。","messagePattern":"Douban 返回空数据，可能触发反爬或限频。请稍后重试。","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"lib/routes/douban/tv/coming.ts","lineNumber":191,"sourceCode":"                        Accept: 'application/json',\n                        'User-Agent': apiClientUa,\n                    },\n                });\n                return response.data as ComingSoonResponse;\n            } catch (error) {\n                throw buildFetchError(error);\n            }\n        },\n        config.cache.routeExpire,\n        false\n    )) as ComingSoonResponse;\n\n    if (!Array.isArray(data.subjects)) {\n        const details = data.msg || data.message || data.reason;\n        throw new Error(`Douban 返回数据结构异常，可能触发反爬或限频。${details ? `上游信息：${details}` : ''}`);\n    }\n    if (data.subjects.length === 0) {\n        throw new Error('Douban 返回空数据，可能触发反爬或限频。请稍后重试。');\n    }\n\n    const subscriptionCount = data.count ?? 0;\n    const total = data.total ?? 0;\n    const sortedSubjects = data.subjects.toSorted((a, b) => {\n        if (sortBy === 'time') {\n            const timeDiff = getSortTimestamp(a.pubdate) - getSortTimestamp(b.pubdate);\n            if (timeDiff !== 0) {\n                return timeDiff;\n            }\n            return getWishCount(b.wish_count) - getWishCount(a.wish_count);\n        }\n        const wishDiff = getWishCount(b.wish_count) - getWishCount(a.wish_count);\n        if (wishDiff !== 0) {\n            return wishDiff;\n        }\n        return 0;\n    });","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/douban/tv/coming.ts#L173-L209","documentation":"Thrown by the Douban TV coming-soon route when the API returns a valid response with `subjects` being an empty array. This typically indicates Douban's anti-crawler returned a 200 status with empty/filtered results rather than an explicit error. The message suggests possible rate limiting and advises retrying later.","triggerScenarios":"Douban returns {subjects: [], count: 0} in response to suspected automated traffic; temporary API quirk during high load; the coming-soon list is genuinely empty at that moment (unlikely for TV but possible).","commonSituations":"Repeated polling triggers soft rate limiting where Douban returns 200 but with no data; the Frodo API returns empty as a stealthy anti-bot measure rather than an error code.","solutions":["Retry after a delay — the cache will eventually be populated by a successful request.","Reduce polling frequency to avoid triggering Douban's soft rate limiter.","If persistent, investigate whether the apiKey has been partially restricted."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"function hasNonEmptySubjects(data: unknown): boolean {\n    return typeof data === 'object' && data !== null && Array.isArray((data as any).subjects) && (data as any).subjects.length > 0;\n}","tryCatchPattern":"try {\n    const feed = await fetch(`${rsshubUrl}/douban/tv/coming`);\n} catch (e) {\n    if (e.message.includes('返回空数据')) {\n        // Soft rate limit — wait longer and retry\n        await new Promise(r => setTimeout(r, 120000));\n        return fetch(`${rsshubUrl}/douban/tv/coming`);\n    }\n    throw e;\n}","preventionTips":["Reduce polling frequency to avoid Douban's soft rate limiter.","Cache aggressively — the route already caches via cache.tryGet.","If the empty response persists across retries, investigate whether the apiKey is partially restricted."],"tags":["douban","anti-crawl","rate-limiting","empty-response","api"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}