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

Gmail批量邮件解析脚本数据截断问题及优化咨询

问题描述

我用Google Apps Script开发了一个脚本,用于解析已发送邮件箱、提取To/CC中的唯一邮箱地址。处理数百封邮件时脚本运行正常,但在每日发送80+邮件的大容量邮箱中,脚本无法完成全量查询——数据被截断(仅拉取20天数据,而非设定的30天),且无任何报错提示。需求是实现90天内唯一收件人数据提取,输出至包含「名、姓、邮箱、日期」列的表格,寻求适配大容量邮箱的优化方案。

原脚本代码:

function exportSentEmails() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  sheet.clear(); // Clear any existing content in the sheet
  
  // Set up the headers in the first row
  sheet.appendRow(["First Name", "Last Name", "Email Address", "Date"]);

  // Define the date range (replace these with your desired dates)
  var startDate = new Date('2024-01-01'); // Change to your desired start date
  var endDate = new Date('2024-04-01');   // Change to your desired end date

  // Format dates to use in the Gmail search query
  var startFormatted = formatDateForQuery(startDate);
  var endFormatted = formatDateForQuery(endDate);

  // Get the sent emails from Gmail within the specified date range
  var threads = GmailApp.search('in:sent after:' + startFormatted + ' before:' + endFormatted);
  
  // Loop through each thread and extract email addresses and names
  var emailSet = new Set(); // To track unique email addresses
  threads.forEach(thread => {
    var messages = thread.getMessages();
    messages.forEach(message => {
      var recipients = message.getTo().split(',');
      var ccRecipients = message.getCc().split(','); // Extract CC recipients
      recipients = recipients.concat(ccRecipients); // Combine To and CC recipients
      recipients.forEach(recipient => {
        try {
          var email = recipient.match(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i);
          if (email) {
            email = email[0].trim().toLowerCase();
            if (!emailSet.has(email)) {
              emailSet.add(email);
              var nameParts = getNameParts(recipient, email);
              var date = formatDate(message.getDate());
              sheet.appendRow([nameParts.firstName, nameParts.lastName, email, date]);
            }
          }
        } catch (e) {
          Logger.log('Error processing recipient: ' + recipient + ' - ' + e.message);
        }
      });
    });
  });

  Logger.log('Export completed.');
}

// Helper function to format date for Gmail query
function formatDateForQuery(date) {
  return Utilities.formatDate(date, Session.getScriptTimeZone(), 'yyyy/MM/dd');
}

// Helper function to format date for spreadsheet
function formatDate(date) {
  return Utilities.formatDate(date, Session.getScriptTimeZone(), 'MM/dd/yyyy');
}

// Helper function to extract name parts from a recipient string
function getNameParts(recipient, email) {
  var displayName = recipient.replace(email, '').trim().replace(/["<>]/g, '');
  var nameParts = { firstName: "", lastName: "" };

  if (displayName) {
    var nameArray = displayName.split(' ');
    nameParts.firstName = nameArray[0];
    nameParts.lastName = nameArray.slice(1).join(' ');
  } else {
    var nameFromEmail = email.split('@')[0].replace(/[\.\_\-]/g, ' ');
    var nameArray = nameFromEmail.split(' ');
    nameParts.firstName = nameArray[0];
    nameParts.lastName = nameArray.slice(1).join(' ');
  }

  // Capitalize the first letter of each name part
  nameParts.firstName = toTitleCase(nameParts.firstName);
  nameParts.lastName = toTitleCase(nameParts.lastName);

  return nameParts;
}

// Helper function to convert a string to title case
function toTitleCase(str) {
  return str.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ');
}

核心问题分析

  1. Gmail搜索结果截断:GmailApp.search()默认最多返回500条线程,超过该数量时会自动截断且无报错,这是导致数据只拉取部分日期的核心原因。
  2. 低效的表格写入:频繁调用sheet.appendRow()会触发多次Spreadsheet API请求,既拖慢速度,也容易触发Google Apps Script的时间限制(最长执行时间6分钟)。
  3. 嵌套循环的性能损耗:线程→邮件→收件人的三层嵌套循环,在数据量大时会显著降低执行效率。

优化方案与代码实现

优化点说明

  • 分页获取Gmail线程:通过循环调用GmailApp.search()的分页参数,确保获取所有符合条件的线程。
  • 批量写入表格:先收集所有待写入数据,最后用setValues()一次性写入,大幅减少API调用次数。
  • 简化收件人处理:提前过滤空收件人,合并To/CC时直接处理,减少不必要的数组操作。
  • 动态计算90天日期范围:自动计算当前日期往前推90天的时间范围,无需手动修改日期。

优化后的代码

function exportSentEmailsOptimized() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  sheet.clear();

  // 表头配置
  const headers = ["名", "姓", "邮箱", "日期"];
  const outputData = [headers]; // 存储所有待写入数据,包含表头
  const emailSet = new Set(); // 跟踪唯一邮箱

  // 动态计算90天日期范围
  const endDate = new Date();
  const startDate = new Date(endDate.getTime() - 90 * 24 * 60 * 60 * 1000);
  const startFormatted = formatDateForQuery(startDate);
  const endFormatted = formatDateForQuery(endDate);
  const searchQuery = `in:sent after:${startFormatted} before:${endFormatted}`;

  // 分页获取所有Gmail线程(每次最多取500条)
  let pageToken = 0;
  const maxResults = 500;
  let threads;
  do {
    threads = GmailApp.search(searchQuery, pageToken, maxResults);
    // 处理当前页的线程
    threads.forEach(thread => {
      const messages = thread.getMessages();
      messages.forEach(message => {
        const messageDate = formatDate(message.getDate());
        // 合并To和CC收件人,过滤空值
        const allRecipients = [
          ...(message.getTo() ? message.getTo().split(',') : []),
          ...(message.getCc() ? message.getCc().split(',') : [])
        ].filter(recipient => recipient.trim() !== '');

        allRecipients.forEach(recipient => {
          try {
            const emailMatch = recipient.match(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i);
            if (!emailMatch) return;
            
            const email = emailMatch[0].trim().toLowerCase();
            if (emailSet.has(email)) return;
            
            emailSet.add(email);
            const nameParts = getNameParts(recipient, email);
            // 将数据加入待写入数组
            outputData.push([nameParts.firstName, nameParts.lastName, email, messageDate]);
          } catch (e) {
            Logger.log(`处理收件人出错:${recipient} - ${e.message}`);
          }
        });
      });
    });
    pageToken += maxResults;
  } while (threads.length === maxResults); // 直到取完所有线程

  // 批量写入表格
  if (outputData.length > 1) { // 有数据才写入
    sheet.getRange(1, 1, outputData.length, outputData[0].length).setValues(outputData);
  }

  Logger.log(`导出完成,共提取${emailSet.size}个唯一收件人`);
}

// 以下辅助函数保留原逻辑,仅调整中文注释
// 格式化日期用于Gmail查询
function formatDateForQuery(date) {
  return Utilities.formatDate(date, Session.getScriptTimeZone(), 'yyyy/MM/dd');
}

// 格式化日期用于表格显示
function formatDate(date) {
  return Utilities.formatDate(date, Session.getScriptTimeZone(), 'yyyy-MM-dd'); // 改用更通用的日期格式
}

// 提取姓名部分
function getNameParts(recipient, email) {
  let displayName = recipient.replace(email, '').trim().replace(/["<>]/g, '');
  const nameParts = { firstName: "", lastName: "" };

  if (displayName) {
    const nameArray = displayName.split(' ');
    nameParts.firstName = nameArray[0];
    nameParts.lastName = nameArray.slice(1).join(' ');
  } else {
    const nameFromEmail = email.split('@')[0].replace(/[\.\_\-]/g, ' ');
    const nameArray = nameFromEmail.split(' ');
    nameParts.firstName = nameArray[0];
    nameParts.lastName = nameArray.slice(1).join(' ');
  }

  // 首字母大写
  nameParts.firstName = toTitleCase(nameParts.firstName);
  nameParts.lastName = toTitleCase(nameParts.lastName);

  return nameParts;
}

// 转换为标题格式
function toTitleCase(str) {
  return str.split(' ').map(word => {
    if (!word) return '';
    return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
  }).join(' ');
}

额外建议

  • 启用脚本触发器:如果需要定期执行,可设置每日/每周触发器,避免手动运行超时。
  • 日志监控:通过Logger.log()查看执行日志,排查潜在问题。
  • 邮箱权限:确保脚本拥有Gmail和Google Sheets的完整权限,避免因权限不足导致的数据获取失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 13:25:09