{"record":{"id":"dbc8334904deb09d","repo":"jackwener/OpenCLI","slug":"no-data-dbc833","errorCode":"NO_DATA","errorMessage":"Job ${jobId} is offline or removed","messagePattern":"Job (.+?) is offline or removed","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"warning","filePath":"clis/51job/detail.js","lineNumber":78,"sourceCode":"                description: (() => {\n                    const box = document.querySelector('.bmsg.job_msg') || document.querySelector('.job_msg');\n                    if (!box) return '';\n                    const clone = box.cloneNode(true);\n                    clone.querySelectorAll('.fp, .mt10, script, style').forEach(n => n.remove());\n                    return (clone.innerText || '').trim();\n                })(),\n                welfare: all('.t1 span, .jtag .t1 span'),\n                category: pick('职能类别'),\n                address: pick('上班地址'),\n                ageRequirement: pick('年龄要求'),\n                company: companyA?.innerText?.trim() || '',\n                companyUrl: companyA?.href || '',\n                companyTag: sel('.com_tag'),\n            };\n        })()`;\n        const data = await page.evaluate(script);\n        if (data.error === 'EXPIRED') {\n            throw new CliError('NO_DATA', `Job ${jobId} is offline or removed`);\n        }\n        if (!data.title) {\n            throw new CliError('NO_DATA', `Could not parse job detail for ${jobId}; page may have changed layout`);\n        }\n\n        // meta looks like \"北京-丰台区  |  3年及以上  |  本科\"\n        const [locRaw, workYear, degree] = (data.meta || '').split('|').map(s => s.trim());\n        // companyTag looks like \"国企\\n\\n150-500人\\n\\n电子技术/半导体/集成电路\"\n        const tagParts = (data.companyTag || '').split(/\\n+/).map(s => s.trim()).filter(Boolean);\n\n        return [{\n            jobId,\n            title: data.title,\n            salary: data.salary || '',\n            location: locRaw || '',\n            workYear: workYear || '',\n            degree: degree || '',\n            category: data.category || '',","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/51job/detail.js#L60-L96","documentation":"The job detail page loaded and the in-page script detected the site's expired/offline marker (data.error='EXPIRED'), so the library throws CliError('NO_DATA') indicating the job posting is offline or removed. The page exists but no longer serves job content.","triggerScenarios":"Calling the detail subcommand with a jobId whose posting has been taken down, expired, filled, or delisted by the employer — the in-page script returns error='EXPIRED' and clis/51job/detail.js:78 throws.","commonSituations":"Crawling old listings from a cache or search index; jobs that closed between listing and detail fetch; region-locked postings that render as expired from abroad; employers reposting under a new id after closing the old one.","solutions":["Treat the job as gone: remove it from your dataset or mark it expired","Re-fetch fresh ids from a current search/hot listing instead of cached ids","If the job should exist, search for the title/company to find a reposted id","Retry once in case of a transient geo/bot interstitial misread as expired"],"exampleFix":"// before\nconst detail = await cli.detail({ jobId: cachedJobId });\n// after\ntry {\n  const detail = await cli.detail({ jobId: cachedJobId });\n} catch (e) {\n  if (e.code === 'NO_DATA') return markExpired(cachedJobId);\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"if (!/^\\d{6,12}$/.test(String(jobId ?? ''))) throw new Error('bad jobId'); // only format is pre-checkable","typeGuard":"const isLiveDetail = (d) => d != null && typeof d.title === 'string' && d.title.length > 0;","tryCatchPattern":"try {\n  return await cli.detail({ jobId });\n} catch (e) {\n  if (e.code === 'NO_DATA') return markJobExpired(jobId); // expected for old listings\n  throw e;\n}","preventionTips":["Treat this as expected when crawling aged listings — handle, don't crash","Prefer ids freshly obtained from search/hot results","Track expiry rates per crawl batch to detect stale sources","Search for reposts under a new id when a job matters"],"tags":["scraping","no-data","expired-listing"],"backgroundTag":"scraped-listing-expired","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}