jackwener/OpenCLI · error · CommandExecutionError

Zhihu column download returned malformed article fields

Error message

Zhihu column download returned malformed article fields

What it means

During `zhihu download` of a column article, requireArticle validates the shape of the data returned by the in-page extraction (via unwrapEvaluateResult): it must be a non-array object with string `title`, string `contentHtml`, and an array of string `imageUrls`. CommandExecutionError (code COMMAND_EXEC) is thrown when any field is missing or of the wrong type, guarding downstream export code from malformed page data.

Source

Thrown at clis/zhihu/download-helpers.js:116

        for (const name of ['data-original', 'data-actualsrc', 'data-src']) img.removeAttribute(name);
        if (!src) img.removeAttribute('src');
        else {
            img.setAttribute('src', src);
            if (!seen.has(src)) {
                seen.add(src);
                imageUrls.push(src);
            }
        }
    });
    return { contentHtml: root.innerHTML, imageUrls };
}

function requireArticle(raw) {
    const data = unwrapEvaluateResult(raw);
    if (!data || typeof data !== 'object' || Array.isArray(data)
        || typeof data.title !== 'string' || typeof data.contentHtml !== 'string'
        || !Array.isArray(data.imageUrls) || !data.imageUrls.every((url) => typeof url === 'string')) {
        throw new CommandExecutionError('Zhihu column download returned malformed article fields');
    }
    if (!data.contentHtml.trim()) {
        throw new EmptyResultError('zhihu download', 'The Zhihu column article had no exportable content.');
    }
    return data;
}

export async function extractColumnArticle(page, target) {
    await page.goto(target.url);
    await page.wait(3);
    const normalize = `(${normalizeContentImages.toString()})`;
    const raw = await page.evaluate(`
      (() => {
        const content = document.querySelector('.Post-RichTextContainer, .RichText, .ArticleContent');
        const normalized = ${normalize}(content?.innerHTML || '');
        return {
          title: document.querySelector('.Post-Title, h1.ContentItem-title, .ArticleTitle')?.textContent?.trim() || 'untitled',
          author: document.querySelector('.AuthorInfo-name, .UserLink-link')?.textContent?.trim() || '',

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log the raw value returned by the in-page extraction (before requireArticle) to see which field is missing/mistyped.
  2. Update the extraction script's selectors to the current Zhihu column article DOM so title/contentHtml/imageUrls are populated.
  3. Ensure the page is fully loaded before extraction (wait for the article content node) so fields are not undefined.
  4. Confirm unwrapEvaluateResult is receiving the data payload, not an error/exception envelope from the evaluate call.

Example fix

// before: assuming extraction always returns full shape
const data = await page.evaluate(extractScript);
exportArticle(data);
// after: pre-check before calling requireArticle
const data = await page.evaluate(extractScript);
if (!data || typeof data.contentHtml !== 'string') {
  await page.wait(3); // content may not have mounted yet
}
const article = requireArticle(await page.evaluate(extractScript));
Defensive patterns

Strategy: type-guard

Validate before calling

const raw = await page.evaluate(extractScript);
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('Extraction returned no article object');

Type guard

function isArticleData(v) {
  return v != null && typeof v === 'object' && !Array.isArray(v)
    && typeof v.title === 'string'
    && typeof v.contentHtml === 'string'
    && Array.isArray(v.imageUrls) && v.imageUrls.every((u) => typeof u === 'string');
}

Try / catch

try {
  const article = requireArticle(await page.evaluate(extractScript));
} catch (e) {
  if (e.code === 'COMMAND_EXEC' && /malformed article fields/i.test(e.message)) {
    console.error('Zhihu DOM may have changed — dump raw extraction for inspection');
  }
  throw e;
}

Prevention

When it happens

Trigger: The page.evaluate/extract script returns undefined, null, a non-serializable value, or an object whose title/contentHtml/imageUrls do not match the expected types — typically because Zhihu changed the column article DOM so the extraction selectors return undefined fields.

Common situations: Zhihu column pages redesigned so `contentHtml` comes back undefined; the extraction returning an array instead of an object; a lazy-loaded page where the script runs before content mounts; unwrapping a browser evaluate result that was an exception payload rather than data.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/ee4073244bf75716. Report an issue: GitHub.