You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Google Sheets App Script拆分HTML文章列的问题修复请求

问题:Google Apps Script拆分HTML文章时的两个问题

需求说明

通过App Script拆分HTML文章,规则如下:

  • 首列存放<h1>内容
  • 第二列起依次对应第一个<h2>及其下方所有<p>、第二个<h2>及其下方所有<p>……直至第七个<h2>
  • 若<h2>数量超过7个,剩余文章内容全部放入最后一列

当前存在的问题

用现有代码实现后,碰到两个问题:

  1. 最后一个<h2>(比如<h2>Conclusion</h2>)后面的<p>标签内容无法写入最后一列;
  2. 部分文章会触发错误:
TypeError: Cannot read properties of null (reading 'match')
(anonymous) @ Code.gs:44
(anonymous) @ Code.gs:43
myFunction  @ Code.gs:41

现有代码

var ss = SpreadsheetApp.getActiveSpreadsheet();
var pasteSheet = [ {name: "Run Script", functionName: "myFunction"}];
ss.addMenu("Break Down Article", pasteSheet);
}

function myFunction() {
  const sheetName = "Sheet1"; // Please set the sheet name.

  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
  const range = sheet.getRange("A2:A" + sheet.getLastRow());
  const temp1 = range.getValues().map(([a]) =>
    a.split("\n").reduce((o, e, i, a) => {
      if (e) {
        let tagMatch = e.match(/^&lt;(.*?)&gt;/);
        let tag = tagMatch ? tagMatch[1].toUpperCase() : "";
        if (tag != "P") {
          if (o.temp.length > 0) {
            o.res.push(o.temp);
            o.temp = [];
          }
          o.res.push([e]);
        } else if (i == a.length - 1) {
          if (tag == "P") {
            o.temp.push(e);
            o.res.push(o.temp);
          } else {
            o.res.push([e]);
          }
        } else {
          o.temp.push(e);
        }
      }
      return o;
    }, { res: [], temp: [] }).res
  ).map(e => e.map(f => f.join("\n")));
  const maxLen = Math.max(...temp1.map(r => r.length));

  // I modified below script.
  const values = temp1.map(r => [...r, ...Array(maxLen - r.length).fill(null)]);
  const res = values.map(r => {
    let n = 0;
    const idx = r.map(e => {
      let tagMatch = e.match(/^&lt;(.*?)&gt;/);
      let tag = tagMatch ? tagMatch[1].toUpperCase() : "";
      return tag && tag.includes("H2") ? ++n : 0;
    }).indexOf(7);
    return [...r.splice(0, idx), r.join("\n")];
  });
  range.offset(0, 2, res.length, res[0].length).setValues(res);
} 

解决方案

问题原因分析

  1. TypeError错误:部分内容为空或不符合标签格式,导致match返回null,直接访问tagMatch[1]触发报错;
  2. 最后一个H2后的P丢失:原代码仅在处理到最后一行时才会把缓存的P加入结果,且拆分逻辑只截取到第7个H2的位置,后续的P未被正确合并到剩余内容中。

修改后的完整代码

function onOpen() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var pasteSheet = [{name: "Run Script", functionName: "myFunction"}];
  ss.addMenu("Break Down Article", pasteSheet);
}

function myFunction() {
  const sheetName = "Sheet1";
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
  const range = sheet.getRange("A2:A" + sheet.getLastRow());
  const temp1 = range.getValues().map(([a]) => {
    if (!a) return []; // 处理空单元格
    return a.split("\n").reduce((o, e) => {
      const trimmed = e.trim();
      if (!trimmed) return o; // 跳过空行
      
      const tagMatch = trimmed.match(/^&lt;(.*?)&gt;/);
      const tag = tagMatch ? tagMatch[1].toUpperCase() : "";
      
      if (tag === "H1" || tag === "H2") {
        // 遇到标题标签,先把之前缓存的P内容加入结果
        if (o.temp.length > 0) {
          o.res.push(o.temp.join("\n"));
          o.temp = [];
        }
        o.res.push(trimmed); // 加入当前标题内容
      } else if (tag === "P") {
        o.temp.push(trimmed); // 缓存段落内容
      }
      return o;
    }, { res: [], temp: [] }).res;
  }).map(grouped => {
    // 处理最后剩余的段落内容
    const processed = grouped.reduce((acc, item) => {
      if (item.startsWith("&lt;H")) {
        acc.push([item]);
      } else {
        acc.length > 0 ? acc[acc.length - 1].push(item) : acc.push([item]);
      }
      return acc;
    }, []).map(arr => arr.join("\n"));
    
    // 检查是否有未处理的缓存段落
    const temp = grouped.reduce((o, e) => {
      const trimmed = e.trim();
      if (!trimmed) return o;
      const tagMatch = trimmed.match(/^&lt;(.*?)&gt;/);
      const tag = tagMatch ? tagMatch[1].toUpperCase() : "";
      if (tag === "P") o.push(trimmed);
      return o;
    }, []);
    if (temp.length > 0 && (!processed.length || !processed[processed.length - 1].startsWith("&lt;P"))) {
      processed.push(temp.join("\n"));
    }
    return processed;
  });

  // 按需求拆分内容到对应列
  const res = temp1.map(row => {
    let h2Count = 0;
    let splitPos = -1;
    // 找到第7个H2的位置
    row.forEach((item, idx) => {
      if (item?.startsWith("&lt;H2")) {
        h2Count++;
        if (h2Count === 7) splitPos = idx;
      }
    });

    let result;
    if (splitPos !== -1) {
      // 截取前7个H2相关内容,剩余合并
      const firstPart = row.slice(0, splitPos + 1);
      const remaining = row.slice(splitPos + 1).join("\n");
      result = [...firstPart, remaining];
    } else {
      result = [...row];
    }

    // 确保首列是H1内容
    const h1Idx = row.findIndex(item => item?.startsWith("&lt;H1"));
    if (h1Idx !== -1) {
      const h1Content = row[h1Idx];
      const rest = row.filter((_, i) => i !== h1Idx);
      result = [h1Content, ...rest];
    }

    // 补全到8列(H1 + 7个H2组 + 剩余)
    while (result.length < 8) {
      result.push("");
    }
    return result;
  });

  // 写入结果到C列开始的区域
  range.offset(0, 2, res.length, res[0].length).setValues(res);
}

关键修改点

  1. 空值防护:增加对空单元格、空行的判断,避免match返回null时触发错误;
  2. 分组逻辑优化:每次遇到H1/H2时就把缓存的P内容归组到上一个条目,确保每个H2后面的P都被正确关联;
  3. 剩余内容合并:准确找到第7个H2的位置,之后的所有内容直接合并到最后一列,包括最后一个H2后的P;
  4. 列结构对齐:强制首列为H1内容,不足8列时自动补空,保证输出格式统一。

内容的提问来源于stack exchange,提问作者user8702720

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 23:42:11