{"record":{"id":"dc34d398fa89299e","repo":"sansan0/TrendRadar","slug":"file-parse-error","errorCode":"FILE_PARSE_ERROR","errorMessage":"解析文件 {config_path} 失败: 配置文件不存在","messagePattern":"解析文件 (.+?) 失败: 配置文件不存在","errorType":"error_code","errorClass":"FileParseError","httpStatus":null,"severity":"error","filePath":"mcp_server/services/parser_service.py","lineNumber":367,"sourceCode":"        \"\"\"\n        解析YAML配置文件\n\n        Args:\n            config_path: 配置文件路径，默认为 config/config.yaml\n\n        Returns:\n            配置字典\n\n        Raises:\n            FileParseError: 配置文件解析错误\n        \"\"\"\n        if config_path is None:\n            config_path = self.project_root / \"config\" / \"config.yaml\"\n        else:\n            config_path = Path(config_path)\n\n        if not config_path.exists():\n            raise FileParseError(str(config_path), \"配置文件不存在\")\n\n        try:\n            with open(config_path, \"r\", encoding=\"utf-8\") as f:\n                config_data = yaml.safe_load(f)\n            return config_data\n        except Exception as e:\n            raise FileParseError(str(config_path), str(e))\n\n    def parse_frequency_words(self, words_file: str = None) -> List[Dict]:\n        \"\"\"\n        解析关键词配置文件（带 mtime 缓存）\n\n        仅当 frequency_words.txt 被修改时才重新解析，避免循环内重复 IO。\n\n        复用 trendradar.core.frequency 的解析逻辑，支持：\n        - # 开头的注释行\n        - 空行分隔词组\n        - [组别名] 作为词组第一行，给整组指定别名","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/sansan0/TrendRadar/blob/8ee26026ba6c11dec41a95fb3895a7162876caa1/mcp_server/services/parser_service.py#L349-L385","documentation":"Raised inside RSSParser._parse_json_feed when the content was detected as a JSON Feed (by _is_json_feed) but json.loads fails. Note the detection only sniffs JSON shape, so content that looks JSON-ish (starts like an object) yet contains syntax errors reaches this decoder and raises ValueError wrapping the JSONDecodeError with position info.","triggerScenarios":"Calling parse() with content that _is_json_feed classifies as JSON but is malformed: truncated response body (cut mid-download), BOM prefixed to the body (\\ufeff{...}) which json.loads rejects, single quotes / trailing commas from a hand-edited file, or content-type sniffing wrong on an HTML fragment that happens to start with '{'.","commonSituations":"Fetching a JSON feed over an unstable proxy that truncates the body; a server sending UTF-8-BOM; copying feed samples through tools that smart-quote characters; upstream switching from XML to JSON or vice versa so the sniffer picks the wrong branch on partially migrated endpoints.","solutions":["Read the wrapped JSONDecodeError position from the message and inspect content around that offset — 'Expecting value' at line 1 col 1 usually means BOM/HTML, mid-string errors mean truncation.","Strip a leading BOM before parsing: content = content.lstrip('\\ufeff').","Re-fetch the feed and check Content-Length vs len(content) to detect truncation; retry on mismatch.","Verify the URL actually serves a JSON Feed (version_url / top-level 'version' key) and that no HTML error page is being passed in."],"exampleFix":"# before\ndata = json.loads(content)  # raises on BOM/truncated body\n\n# after (defensive caller)\ncontent = content.lstrip('\\ufeff')\ntry:\n    items = parser.parse(content, feed_url)\nexcept ValueError as e:\n    if len(content) < 200:\n        logger.warning(\"feed %s returned suspiciously short body\", feed_url)\n    raise","handlingStrategy":"try-catch","validationCode":"def json_feed_probe(content: str) -> bool:\n    \"\"\"Verify JSON-decodability before handing to the parser.\"\"\"\n    try:\n        json.loads(content.lstrip(\"\\ufeff\"))\n        return True\n    except json.JSONDecodeError:\n        return False","typeGuard":null,"tryCatchPattern":"try:\n    items = parser.parse(content, feed_url)\nexcept ValueError as e:\n    if \"JSON Feed\" in str(e):\n        logger.warning(\"feed %s sent malformed JSON; refetching once\", feed_url)\n        content = refetch(feed_url)\n        items = parser.parse(content, feed_url)  # single retry, not a loop","preventionTips":["Strip a UTF-8 BOM (content.lstrip('\\ufeff')) before parsing — the most common avoidable JSONDecodeError here.","Compare received body length against Content-Length to detect truncation before parsing.","Never hand-edit saved feed bodies with tools that smart-quote or allow trailing commas."],"tags":["rss","json","parsing","encoding"],"backgroundTag":null,"analyzedSha":"8ee26026ba6c11dec41a95fb3895a7162876caa1","analyzedAt":"2026-08-15T01:42:18.084Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}