Gmail内嵌图片提取方案失效,求有效方法及X-Attachment-Id提取方案
Gmail邮件导出PDF:内嵌图片提取解决方案
问题描述
我编写了一个脚本,用于将收到的Gmail邮件导出为PDF,所有功能均正常,但无法提取通过Gmail账号发送的内嵌图片。
核心问题:内嵌图片附件(如复制粘贴插入的同名文件grafik.png)与HTML代码中的cid(如ii_l5vcyjv50)无法关联。网上常见的通过message.getRawContent()提取base64再替换cid的方案已失效——要么是getRawContent()结构变化导致正则失效,要么是Google调整了cid关联逻辑。需要找到可靠的方法,将X-Attachment-Ids与对应base64编码提取到数组中。
附原始邮件内容片段:
Dies ist ein Screenshot: [image: grafik.png] Dies ist eine per Drag und Drop eingef=C3=BCgte Bilddatei: [image: amsel.jpg] Dies ist ein Bild aus Word: [image: grafik.png] Dies ist ein Bild direkt aus dem Browser =C3=BCber "Grafik kopieren" [image: grafik.png] --=20 Viele Gr=C3=BC=C3=9Fe Benni --0000000000008333b505e48ad3d1 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable <div dir=3D"ltr"><br><br><div class=3D"gmail_quote"><div dir=3D"ltr" class==3D"gmail_attr">---------- Forwarded message ---------<br>Von: <b class=3D"=gmail_sendername" dir=3D"auto">Summer Moon</b> <span dir=3D"auto">&lt;<a href=3D"mailto:summermoonair@gmail.com">summermoonair@gmail.com</a>&gt;</span=><br>Date: Do., 21. Juli 2022 um 20:24=C2=A0Uhr<br>Subject: Gmail Inline Image<br>To: Summer Moon &lt;<a href=3D"mailto:summermoonair@gmail.com">summermoonair@gmail.com</a>&gt;<br></div><br><br><div dir=3D"ltr"><div>Dies ist ein Screenshot:</div><div><img src=3D"cid:ii_l5vd00311" alt=3D"grafik.png" width=3D"468" height=3D"263"><br><br></div><div>Dies ist eine per Drag und Drop eingef=C3=BCgte Bilddatei:</div><div><img src=3D"cid:ii_l5vd1fs22" alt=3D"amsel.jpg" width=3D"468" height=3D"222"><br><br></div><div><br></div><div>Dies ist ein Bild aus Word:</div><div><img src=3D"cid:ii_l5vd44ms3" alt=3D"grafik.png" width=3D"468" height=3D"468"><br><br></div><div><br></div><div>Dies ist ein Bild direkt aus dem Browser =C3=BCber &quot;Grafik kopieren&quot;<br></div><div><img src=3D"cid:ii_l5vcyjv50" alt=3D"grafik.png" width=3D"468" height=3D"222"><br><br>-- <br><div dir=3D"ltr" data-smartmail=3D"gmail_signature"><div dir=3D"ltr">Viele Gr=C3=BC=C3=9Fe=C2=A0<div>Benni</div></div></div></div></div> </div></div> --0000000000008333b505e48ad3d1-- --0000000000008333b605e48ad3d2 Content-Type: image/png; name="grafik.png" Content-Disposition: inline; filename="grafik.png" Content-Transfer-Encoding: base64 Content-ID: <ii_l5vcyjv50> X-Attachment-Id: ii_l5vcyjv50 iVBORw0KGgoAAAANSUhEUgAAB4AAAAOPCAYAAAAqu9wAAAAABHNCSVQICAgIfAhkiAAAIABJREFU eF7svdmXJOd53vnmXllbV29oNIAGAZAUaZImJYqmRGksjzSWfSEf6VbnzPHV3M7dXMyl+WdoLnx0 xvacIx/bc6GjsWXNcEYSTJEUKYIUuADEjsbS6LX23DPmeSMruqOiMjIyKzMrt182ClUZ8S3v9/u+ iKzKJ5/3ywVBJwiCnPlXNzB9t9ijq5/z8QP8DAEIQAACEIBAjEAul4bj1AtqWqGpHc+nBza1Pgc1 fDac2fIZFCvnIJBFQL82j/nw37HTH7lgvN+/AxscoH7zT+88PDO4vv5iGFg/yI07vjH7T0zQ2fvP wPAzT57+e+lJ8WH78b+5Bj3yGcNPqx7FdSqO3NnSfQ6Fcx7Vj+YvlxhQtG6y1ld8bP37elLiXNdS VqWMTjNOD5qa8NyY3We2P26BrPiy2s/mM3iB5mIXSDyWYePq1/+wdX1suZP7V5Bxn8ricN7z8f77 jaVfuz6+fnEPWz8c98mlHrFKYxZvM61MFOMo/aeNq9/xcY5FnFLbSLtBn1SY9ZizmCbvu6njPOeJ s68IpxsafHWfs1OqQQACEIAABCAAAQjMjEBxZj3TMQQgAAEIQAACEIAABFaSwHgC7/wjW/bxnZ2B YcXfszU5MjKBLAVl5AapAAEIQAACEIAABCAAAQhAAAIQWD4CCMDLN6eMCAIQgAAEIAABCEAAAhAQ gWkIs95mhslsIPtpxDSww1FPRg70M0JrJOxnechG7ZDyy0Qgy2G5TGOd9lhCZ3Dicov4ph2PYvLz zMW0Z4j2IQABCEAAAhCAAAQgMN8EEIDne36IDgIQgAAEINCXACna+mLhIATmgkDyjfmLDmrW94eZ j/8CAIwj4k47vFPtn0OrfTy2hPo0q5S6o14/s15/o8Y76fJZ4x9fFDzHotI gs+KaNIdlaa+fCOxj´´´ SzseH/cyisBZ98+s1Tnv6zAr/qx1ncUnqz7nIQABCEAAAhCAAASWi
解决方案
1. 优先使用Gmail API(最可靠)
别直接解析原始内容,Gmail API已经把附件信息结构化了,步骤如下:
- 调用
users.messages.get接口,指定format='full',获取邮件完整详情,包含所有附件的attachmentId、Content-ID、X-Attachment-Id等字段 - 遍历邮件的
payload.parts,筛选出Content-Disposition为inline且MIME类型以image/开头的附件 - 对每个符合条件的附件,调用
users.messages.attachments.get获取base64编码内容(注意处理URL安全的base64转义) - 将HTML中的
cid:xxx替换为data:image/[格式];base64,[编码内容]
示例代码(Google Apps Script):
// 提取内嵌图片的cid与base64映射 function getInlineImages(messageId) { const gmailMessage = Gmail.Users.Messages.get('me', messageId, {format: 'full'}); const inlineImages = {}; // 递归遍历所有邮件部分 const traverseParts = (parts) => { parts.forEach(part => { // 检查是否是内嵌图片 const isInline = part.headers?.some(h => h.name === 'Content-Disposition' && h.value.includes('inline')); const isImage = part.mimeType?.startsWith('image/'); const contentIdHeader = part.headers?.find(h => h.name === 'Content-ID'); const attachmentId = part.body?.attachmentId; if (isInline && isImage && contentIdHeader && attachmentId) { const cid = contentIdHeader.value.replace(/[<>]/g, ''); // 去掉尖括号 // 获取附件base64内容 const attachment = Gmail.Users.Messages.Attachments.get('me', messageId, attachmentId); // 修正URL安全的base64编码 const base64Data = attachment.data.replace(/-/g, '+').replace(/_/g, '/'); inlineImages[cid] = `data:${part.mimeType};base64,${base64Data}`; } // 递归处理子部分 if (part.parts) traverseParts(part.parts); }); }; if (gmailMessage.payload.parts) traverseParts(gmailMessage.payload.parts); return inlineImages; } // 替换HTML中的cid为base64数据URI function replaceCidWithBase64(html, inlineImages) { let updatedHtml = html; Object.keys(inlineImages).forEach(cid => { updatedHtml = updatedHtml.replace(new RegExp(`cid:${cid}`, 'g'), inlineImages[cid]); }); return updatedHtml; }
2. 解析原始内容的正则方案(备选)
如果必须用getRawContent(),可以按邮件边界分段处理,避免依赖整体结构:
- 提取邮件的分隔边界(如
--0000000000008333b605e48ad3d2) - 将原始内容按边界分割成多个部分
- 对每个部分,匹配
Content-ID、Content-Type和base64编码内容 - 整理cid与base64的映射关系
示例代码(JavaScript):
function parseRawContentForInlineImages(rawContent) { const inlineImages = {}; // 按邮件边界分割内容 const parts = rawContent.split(/--[0-9a-f]{20,}/); parts.forEach(part => { // 匹配Content-ID(不带尖括号) const cidMatch = part.match(/Content-ID:\s*<([^>]+)>/i); // 匹配图片类型 const contentTypeMatch = part.match(/Content-Type:\s*(image\/[^\n]+)/i); // 确认是base64编码 const isBase64 = part.match(/Content-Transfer-Encoding:\s*base64/i); if (cidMatch && contentTypeMatch && isBase64) { const cid = cidMatch[1]; const contentType = contentTypeMatch[1]; // 提取空行后的base64内容,去掉换行和无效字符 const base64Section = part.split(/\n\n/)[1]; if (base64Section) { const base64Data = base64Section.replace(/\n/g, '').replace(/[^A-Za-z0-9+/=]/g, ''); inlineImages[cid] = `data:${contentType};base64,${base64Data}`; } } }); return inlineImages; }
关键注意事项
- 永远优先用官方API,避免依赖Gmail原始内容的格式(Google随时可能调整)
- 处理base64时要注意URL安全编码的转换(
-→+,_→/) - 同名图片只能靠cid区分,必须用cid作为关联键,不能用文件名
内容的提问来源于stack exchange,提问作者Summer Moon
相关产品推荐
相关产品推荐

