{"record":{"id":"20e46e0272903bff","repo":"dgtlmoon/changedetection.io","slug":"no-parsable-json-found-in-this-document","errorCode":null,"errorMessage":"No parsable JSON found in this document","messagePattern":"No parsable JSON found in this document","errorType":"exception","errorClass":"JSONNotFound","httpStatus":null,"severity":"error","filePath":"changedetectionio/html_tools.py","lineNumber":613,"sourceCode":"    bs_result += soup.find_all('body')\n\n    bs_jsons = []\n\n    for result in bs_result:\n        # result.text is how bs4 magically strips JSON from the body\n        content_start = result.text.lstrip(\"\\ufeff\").strip()[:100] if result.text else ''\n        # Skip empty tags, and things that dont even look like JSON\n        if not result.text or not (content_start[0] == '{' or content_start[0] == '['):\n            continue\n        try:\n            json_data = json.loads(result.text)\n            bs_jsons.append(json_data)\n        except json.JSONDecodeError:\n            # Skip objects which cannot be parsed\n            continue\n\n    if not bs_jsons:\n        raise JSONNotFound(\"No parsable JSON found in this document\")\n\n    for json_data in bs_jsons:\n        stripped_text_from_html = _parse_json(json_data, json_filter)\n\n        if ensure_is_ldjson_info_type:\n            # Could sometimes be list, string or something else random\n            if isinstance(json_data, dict):\n                # If it has LD JSON 'key' @type, and @type is 'product', and something was found for the search\n                # (Some sites have multiple of the same ld+json @type='product', but some have the review part, some have the 'price' part)\n                # @type could also be a list although non-standard (\"@type\": [\"Product\", \"SubType\"],)\n                # LD_JSON auto-extract also requires some content PLUS the ldjson to be present\n                # 1833 - could be either str or dict, should not be anything else\n\n                t = json_data.get('@type')\n                if t and stripped_text_from_html:\n\n                    if isinstance(t, str) and t.lower() == ensure_is_ldjson_info_type.lower():\n                        break","sourceCodeStart":595,"sourceCodeEnd":631,"githubUrl":"https://github.com/dgtlmoon/changedetection.io/blob/5d9c7c6da76340597243e8163c4f2439237fa0e8/changedetectionio/html_tools.py#L595-L631","documentation":"extract_json_blob_from_html parses <script type=\"application/ld+json\"> (and similar) blocks with json.loads; if every candidate block failed to parse (or none were found), it raises JSONNotFound. It signals the HTML contains no machine-readable JSON the extractor can use.","triggerScenarios":"Calling extract_json_as_string / extract_json_blob_from_html on HTML that has no ld+json script tags, or whose JSON script blocks are malformed (trailing commas, unescaped newlines, HTML entities inside JSON), so all candidates hit json.JSONDecodeError and are skipped, leaving bs_jsons empty.","commonSituations":"Scraping pages that changed their structured-data markup or serve different HTML to the fetcher; server-side templates injecting comments/HTML into JSON blocks; pages behind a consent wall returning no real content.","solutions":["Inspect the fetched HTML for <script type=\"application/ld+json\"> blocks and verify they are valid JSON (json.loads in a REPL)","Fix the fetch (headers, cookies, playwright rendering) so the real page content with JSON-LD is returned","Handle JSONNotFound and fall back to text-based extraction instead of structured JSON"],"exampleFix":"# before\ndata = extract_json_as_string(html, json_filter='...')\n# after\ntry:\n    data = extract_json_as_string(html, json_filter='...')\nexcept JSONNotFound:\n    data = None  # fall back to plain text diff","handlingStrategy":"try-catch","validationCode":"import json, re\nhas_json_ld = re.search(r'<script[^>]+ld\\+json', html or '')\nif has_json_ld:\n    blocks = re.findall(r'<script[^>]*type=\"application/ld\\+json\"[^>]*>(.*?)</script>', html, re.S)\n    parsable = any(_try_json(b) for b in blocks)  # json.loads in try/except","typeGuard":null,"tryCatchPattern":"from changedetectionio.html_tools import JSONNotFound\ntry:\n    data = extract_json_as_string(html, json_filter=flt)\nexcept JSONNotFound:\n    data = fallback_text_extraction(html)","preventionTips":["Verify the fetcher actually receives the page with JSON-LD (check raw HTML)","Fall back to text extraction when structured JSON is absent","Log the HTML size/content when extraction fails to detect consent walls"],"tags":["json","html-parsing","scraping","json-ld"],"backgroundTag":"json-parse-error","analyzedSha":"5d9c7c6da76340597243e8163c4f2439237fa0e8","analyzedAt":"2026-08-27T19:41:16.067Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}