{"record":{"id":"fa3b9933031e8fd9","repo":"DIYgod/RSSHub","slug":"cannot-get-link","errorCode":null,"errorMessage":"Cannot get link","messagePattern":"Cannot get link","errorType":"exception","errorClass":"Error","httpStatus":503,"severity":"error","filePath":"lib/routes/whu/rsgis.ts","lineNumber":112,"sourceCode":" * @returns Whether or not weixin post\n */\nfunction checkExternal(link: string): boolean {\n    const matchWeixin = link.match(/^((http:\\/\\/)|(https:\\/\\/))?([\\dA-Za-z]([\\dA-Za-z-]{0,61}[\\dA-Za-z])?\\.)+[A-Za-z]{2,6}(\\/)/);\n    return !!matchWeixin?.length;\n}\n\n/**\n * Get information from a list of paired link and date.\n *\n * @param element\n * @returns A list of RSS meta node.\n */\nfunction parseListLinkDateItem(element: Cheerio<Element>, currentUrl: string) {\n    const linkElement = element.find('a');\n    const title = linkElement.text();\n    const href = linkElement.attr('href');\n    if (href === undefined) {\n        throw new Error('Cannot get link');\n    }\n    const external = checkExternal(href);\n    const link = external ? href : new URL(href, currentUrl).href;\n    const pubDate = element.find('div.date1').text();\n    return {\n        title,\n        link,\n        pubDate: timezone(parseDate(pubDate, 'YYYY-MM-DD'), 8),\n        description: title,\n        external,\n    };\n}\n\nasync function getDetail(item: Post): Promise<DataItem | any> {\n    const link = item.link;\n    return link\n        ? await cache.tryGet(`whu:rsgis:${link}`, async () => {\n              if (item.external) {","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/whu/rsgis.ts#L94-L130","documentation":"Inside parseListLinkDateItem the code reads element.find('a').attr('href'); if the anchor has no href attribute the scraper cannot build a link and aborts. It indicates the rsgis.whu.edu.cn list HTML no longer matches the expected <li><a href=...> shape.","triggerScenarios":"Any of the list selectors in handleIndex/handlePostList yields an <li> whose <a> lacks href — e.g. a placeholder item, a date-only row, or a site redesign that dropped/renamed the anchor.","commonSituations":"The school site template changed; a list item is a non-link divider/header; HTML was partially loaded due to network truncation.","solutions":["Open rsgis.whu.edu.cn and confirm the list markup still uses <a href> inside the queried <li> containers; update the selector if the structure changed.","Filter out items where find('a').attr('href') is undefined before calling parseListLinkDateItem rather than throwing.","Report the page to the route maintainer (HPDell) with the offending URL."],"exampleFix":"// defensive skip instead of throw\nconst href = linkElement.attr('href');\nif (href === undefined) {\n    return null;   // filtered out by a subsequent .filter(Boolean)\n}","handlingStrategy":"validation","validationCode":"// Inside parseListLinkDateItem, skip link-less rows instead of throwing\nconst linkElement = element.find('a');\nconst href = linkElement.attr('href');\nif (href === undefined) return null;   // caller filters nulls","typeGuard":"function hasHref(el: Cheerio<Element>): boolean {\n    return el.attr('href') !== undefined;\n}","tryCatchPattern":"const posts = list.toArray()\n    .map((item) => {\n        try { return parseListLinkDateItem($(item), baseUrl); }\n        catch { return null; }\n    })\n    .filter((x): x is Post => x !== null);","preventionTips":["Treat scraped HTML as untrusted — never throw on a missing optional attribute; skip the row.","Add structure assertions in tests against a saved fixture of the live page.","Log when skip rates climb so a silent site redesign is detected early."],"tags":["whu","scraping","cheerio","html-structure"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}