{"record":{"id":"79e0b08119e7928f","repo":"fish2018/pansou","slug":"s-data-key","errorCode":null,"errorMessage":"[%s] 未能在首页找到 data-key","messagePattern":"\\[(.+?)\\] 未能在首页找到 data-key","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/jsnoteclub/jsnoteclub.go","lineNumber":258,"sourceCode":"\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"[%s] 首页返回状态码: %d\", p.Name(), resp.StatusCode)\n\t}\n\n\tdoc, err := goquery.NewDocumentFromReader(resp.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"[%s] 解析首页失败: %w\", p.Name(), err)\n\t}\n\n\tvar htmlBuilder strings.Builder\n\tdoc.Find(\"script\").Each(func(_ int, s *goquery.Selection) {\n\t\tif html, err := goquery.OuterHtml(s); err == nil {\n\t\t\thtmlBuilder.WriteString(html)\n\t\t}\n\t})\n\n\tmatch := dataKeyRegex.FindStringSubmatch(htmlBuilder.String())\n\tif len(match) < 2 {\n\t\treturn \"\", fmt.Errorf(\"[%s] 未能在首页找到 data-key\", p.Name())\n\t}\n\n\treturn match[1], nil\n}\n\nfunc (p *JsNoteClubPlugin) fetchPosts(client *http.Client, dataKey string) ([]ghostPost, error) {\n\tparams := url.Values{}\n\tparams.Set(\"key\", dataKey)\n\tparams.Set(\"limit\", \"10000\")\n\tparams.Set(\"fields\", \"id,slug,title,excerpt,url,updated_at,visibility\")\n\tparams.Set(\"order\", \"updated_at DESC\")\n\n\treqURL := fmt.Sprintf(\"https://jsnoteclub.com/ghost/api/content/posts/?%s\", params.Encode())\n\n\tctx, cancel := context.WithTimeout(context.Background(), requestTimeout)\n\tdefer cancel()\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/jsnoteclub/jsnoteclub.go#L240-L276","documentation":"Returned when the homepage HTML parsed successfully but no <script> tag content matches dataKeyRegex, so the plugin cannot extract the data-key value needed to call the Ghost Content API. This indicates the site's page structure changed or the served page no longer embeds the expected data-key attribute.","triggerScenarios":"The regex finds no match (len(match) < 2) after scanning the outer HTML of every <script> element in the homepage document — i.e. no script tag contains the data-key pattern the regex expects.","commonSituations":"jsnoteclub.com redesigned its frontend and the data-key is no longer in a <script> tag; the Ghost site switched themes so the key moved to a different attribute; the plugin scrapes a JS-rendered shell where content loads via XHR; site now serves a maintenance or consent page instead of the app HTML.","solutions":["Open https://jsnoteclub.com/ in a browser, view source, and locate where data-key now appears","Update dataKeyRegex to match the new attribute/markup pattern","If the key moved out of script tags, search the full document HTML (doc.Find(\"[data-key]\")) instead of only script elements","Check whether the Ghost Content API key is published in a JS bundle or meta tag now and adjust extraction","Fallback: configure the data-key statically via plugin config instead of scraping"],"exampleFix":"// before\nmatch := dataKeyRegex.FindStringSubmatch(htmlBuilder.String())\nif len(match) < 2 {\n    return \"\", fmt.Errorf(\"[%s] 未能在首页找到 data-key\", p.Name())\n}\n// after\nmatch := dataKeyRegex.FindStringSubmatch(htmlBuilder.String())\nif len(match) < 2 {\n    // fallback: look for data-key attribute anywhere in the document\n    if sel := doc.Find(\"[data-key]\"); sel.Length() > 0 {\n        if key, ok := sel.Attr(\"data-key\"); ok && key != \"\" {\n            return key, nil\n        }\n    }\n    return \"\", fmt.Errorf(\"[%s] 未能在首页找到 data-key\", p.Name())\n}","handlingStrategy":"fallback","validationCode":"// check the site still embeds the key before running the pipeline\nresp, err := http.Get(\"https://jsnoteclub.com/\")\nif err != nil { /* network problem, different error */ }\nbody, _ := io.ReadAll(resp.Body)\nif !strings.Contains(string(body), \"data-key\") && !dataKeyRegex.MatchString(string(body)) {\n    // upstream layout changed: alert/maintain config-supplied key instead\n}","typeGuard":null,"tryCatchPattern":"key, err := plugin.fetchDataKey(client)\nif err != nil {\n    if strings.Contains(err.Error(), \"未能\") {\n        // selector rot: fall back to a configured static key\n        return useConfiguredDataKey()\n    }\n    return err\n}","preventionTips":["Add a CI smoke test that scrapes the homepage and asserts the regex still matches","Pin a configurable fallback data-key so scraping failures are non-fatal","Alert on this error — it almost always means the upstream site changed its markup","Extract the key from data-key attributes anywhere in the document, not only <script> tags"],"tags":["scraping","selector","upstream-change","ghost"],"backgroundTag":"unexpected-response-shape","analyzedSha":"beaa56133755a548ebc51b090b3816e2ae044aa6","analyzedAt":"2026-09-07T00:31:18.025Z","contentChangedAt":"2026-09-07T00:31:18.025Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}