首页 > 解决方案 > 使用 Express API 下载 PDF

问题描述

我正在创建一个工作列表抓取工具,它可以访问多个工作站点并抓取工作,然后将它们聚合在一起。我想创建一个端点,如果我喜欢一份工作,我可以自动下载我自己的求职信,其中包含基于该工作的某些动态数据。目前,我从 Google Docs 中获取了求职信的 HTML,并从中创建了一个字符串。我设置了一个 GET 端点,它应该采用这个 HTML 字符串并将其作为可下载的 PDF 发送给请求者。目前,PDF 正在下载,但文档显示“加载 PDF 文档失败”。任何帮助将不胜感激解决这个问题。

这是我的快递应用程序:

const express = require('express');
const dotenv = require('dotenv');

const app = express();
dotenv.config();

app.get('/cover-letter', (req, res) => {
    const coverLetter = `<html><head><meta content="text/html; charset=UTF-8" http-equiv="content-type"><style type="text/css">@import url('https://themes.googleusercontent.com/fonts/css?kit=1ZpBgFLQKwrA6c9iLOONVLLukJZ0tncL9DlcRrH6sPk');ol{margin:0;padding:0}table td,table th{padding:0}.c1{border-right-style:solid;padding-top:0pt;border-top-width:0pt;border-right-width:0pt;padding-left:0pt;padding-bottom:0pt;line-height:1.0;border-left-width:0pt;border-top-style:solid;background-color:#ffffff;border-left-style:solid;border-bottom-width:0pt;border-bottom-style:solid;orphans:2;widows:2;text-align:justify;padding-right:0pt}.c21{border-right-style:solid;padding:0pt 5.4pt 0pt 5.4pt;border-bottom-color:#000000;border-top-width:0pt;border-right-width:0pt;border-left-color:#000000;vertical-align:top;border-right-color:#000000;border-left-width:0pt;border-top-style:solid;border-left-style:solid;border-bottom-width:0pt;width:311.4pt;border-top-color:#000000;border-bottom-style:solid}.c16{border-right-style:solid;padding:0pt 5.4pt 0pt 5.4pt;border-bottom-color:#000000;border-top-width:0pt;border-right-width:0pt;border-left-color:#000000;vertical-align:top;border-right-color:#000000;border-left-width:0pt;border-top-style:solid;border-left-style:solid;border-bottom-width:0pt;width:195.9pt;border-top-color:#000000;border-bottom-style:solid}.c19{padding-top:5pt;border-top-width:3pt;border-top-color:#000000;padding-bottom:0pt;line-height:1.0;orphans:2;border-top-style:solid;widows:2;text-align:left}.c5{color:#000000;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:11pt;font-family:"Arial";font-style:normal}.c0{color:#000000;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:10pt;font-family:"Tahoma";font-style:normal}.c9{padding-top:0pt;padding-bottom:0pt;line-height:1.0;orphans:2;widows:2;text-align:left}.c7{padding-top:0pt;padding-bottom:0pt;line-height:1.0;orphans:2;widows:2;text-align:right}.c11{padding-top:0pt;padding-bottom:6pt;line-height:1.0;orphans:2;widows:2;text-align:left}.c18{padding-top:0pt;padding-bottom:0pt;line-height:1.0;orphans:2;widows:2;text-align:justify}.c12{padding-top:0pt;padding-bottom:0pt;line-height:1.1500000000000001;text-align:left;height:11pt}.c10{margin-left:-5.4pt;border-spacing:0;border-collapse:collapse;margin-right:auto}.c8{color:#000000;text-decoration:none;vertical-align:baseline;font-style:normal}.c2{vertical-align:baseline;font-size:10pt;font-family:"Tahoma";font-weight:400}.c22{font-weight:400;font-size:18pt;font-family:"Tahoma"}.c6{background-color:#ffffff;max-width:496.8pt;padding:57.6pt 57.6pt 57.6pt 57.6pt}.c15{font-weight:400;font-size:14.5pt;font-family:"Tahoma"}.c14{font-weight:400;font-size:11pt;font-family:"Times New Roman"}.c4{font-size:10pt;font-family:"Tahoma";font-weight:400}.c20{font-size:18pt;font-family:"Tahoma";font-weight:700}.c3{font-size:10.5pt;font-family:"Arial";font-weight:400}.c13{height:11pt}.c17{height:0pt}.title{padding-top:24pt;color:#000000;font-weight:700;font-size:36pt;padding-bottom:6pt;font-family:"Times New Roman";line-height:1.0;page-break-after:avoid;orphans:2;widows:2;text-align:left}.subtitle{padding-top:18pt;color:#666666;font-size:24pt;padding-bottom:4pt;font-family:"Georgia";line-height:1.0;page-break-after:avoid;font-style:italic;orphans:2;widows:2;text-align:left}li{color:#000000;font-size:11pt;font-family:"Times New Roman"}p{margin:0;color:#000000;font-size:11pt;font-family:"Times New Roman"}h1{padding-top:24pt;color:#000000;font-weight:700;font-size:24pt;padding-bottom:6pt;font-family:"Times New Roman";line-height:1.0;page-break-after:avoid;orphans:2;widows:2;text-align:left}h2{padding-top:18pt;color:#000000;font-weight:700;font-size:18pt;padding-bottom:4pt;font-family:"Times New Roman";line-height:1.0;page-break-after:avoid;orphans:2;widows:2;text-align:left}h3{padding-top:14pt;color:#000000;font-weight:700;font-size:14pt;padding-bottom:4pt;font-family:"Times New Roman";line-height:1.0;page-break-after:avoid;orphans:2;widows:2;text-align:left}h4{padding-top:12pt;color:#000000;font-weight:700;font-size:12pt;padding-bottom:2pt;font-family:"Times New Roman";line-height:1.0;page-break-after:avoid;orphans:2;widows:2;text-align:left}h5{padding-top:11pt;color:#000000;font-weight:700;font-size:11pt;padding-bottom:2pt;font-family:"Times New Roman";line-height:1.0;page-break-after:avoid;orphans:2;widows:2;text-align:left}h6{padding-top:10pt;color:#000000;font-weight:700;font-size:10pt;padding-bottom:2pt;font-family:"Times New Roman";line-height:1.0;page-break-after:avoid;orphans:2;widows:2;text-align:left}</style></head><body><div class="c6"><div><p class="c9 c13"><span class="c8 c14"></span></p></div><p class="c12"><span class="c5"></span></p><a id="t.cf2cb48f4187c740031c3f8d361f8f564f9a673c"></a><a id="t.0"></a><table class="c10"><tbody><tr class="c17"><td class="c21" colspan="1" rowspan="1"><p class="c11"><span class="c20">Test</span></p></td><td class="c16" colspan="1" rowspan="1"><p class="c7"><span class="c2">710 </span><span class="c4">Example</span><span class="c0">&nbsp;Dr. </span></p><p class="c7"><span class="c4">New York</span><span class="c2">, </span><span class="c4">New York</span><span class="c2">&nbsp;</span><span class="c4">55555</span></p><p class="c7"><span class="c2">m: </span><span class="c4">618</span><span class="c2">.</span><span class="c4">235</span><span class="c2">.</span><span class="c4">1234</span><span class="c0">&nbsp;</span></p><p class="c7"><span class="c4">example</span><span class="c0">@gmail.com</span></p></td></tr></tbody></table><p class="c13 c19"><span class="c8 c15"></span></p><p class="c9"><span class="c4">8/3/2020</span></p><p class="c9 c13"><span class="c0"></span></p><p class="c9 c13"><span class="c0"></span></p><p class="c9 c13"><span class="c0"></span></p><p class="c9"><span class="c2">Dear </span><span class="c4">Hiring Manager</span><span class="c0">,</span></p><p class="c13 c18"><span class="c0"></span></p><p class="c1"><span class="c3 c8">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas cursus commodo felis. Suspendisse faucibus in lectus sed viverra. Curabitur turpis arcu, finibus vel porta et, commodo sit amet orci. Pellentesque rutrum finibus ipsum ac pharetra. Aenean varius purus erat, id rhoncus purus ornare eget. Nunc vel accumsan diam, sed sagittis augue. Aenean nec mauris dolor. Mauris vel pharetra magna. Nunc convallis id metus sed facilisis. In sed fringilla ligula.</span></p><p class="c1"><span class="c8 c3">Phasellus ut gravida nibh, vel dapibus lectus. Integer ut dui commodo tortor bibendum eleifend vel pharetra nunc. Donec at luctus leo. Praesent at tortor euismod, faucibus ante non, dignissim purus. Suspendisse sodales, lacus eu tincidunt sagittis, dui velit facilisis lacus, quis hendrerit arcu est ut urna. Praesent auctor arcu sit amet ullamcorper pellentesque. Sed dapibus quam eu lacus tempus, a luctus risus consequat. Cras id ipsum efficitur, feugiat sapien eu, mattis nunc. Proin iaculis ultricies ex ut volutpat. Vivamus sit amet consectetur nibh. Maecenas vel dictum ligula, varius porttitor elit. Suspendisse mauris nunc, sodales sit amet dignissim at, cursus dapibus est.</span></p><p class="c1"><span class="c8 c3">Nunc scelerisque efficitur diam non placerat. Aliquam enim velit, viverra id ante non, viverra aliquam quam. Quisque vel diam leo. Etiam tincidunt pellentesque mi sed eleifend. Fusce sollicitudin euismod enim, ac vestibulum metus interdum nec. Ut elementum metus in quam vehicula, sit amet scelerisque urna mollis. Praesent et arcu magna. Donec convallis sed urna vel euismod. Proin auctor faucibus leo mattis laoreet. Donec varius faucibus leo, non porttitor eros. Integer efficitur porta nunc eu ornare. Ut vel tellus nec nisi consequat dignissim.</span></p><p class="c1"><span class="c8 c3">Nunc dictum metus sed metus mattis aliquet. Vestibulum et justo tristique, fermentum metus ut, sagittis massa. Aliquam erat volutpat. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Quisque ut eros ut ligula euismod gravida. Suspendisse consequat tincidunt urna vitae tempor. Nam rhoncus dapibus ante, at iaculis nisl molestie nec. Nunc tincidunt congue viverra.</span></p><p class="c1"><span class="c3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam viverra, nunc sit amet venenatis placerat, arcu risus imperdiet dolor, non vulputate metus nunc pellentesque nulla. Quisque pulvinar interdum maximus. Mauris quis est a nisl vulputate sagittis. Suspendisse laoreet a odio ac tincidunt. Donec suscipit, turpis blandit sollicitudin iaculis, urna justo faucibus velit, varius vehicula purus urna sed orci. Nulla facilisi. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</span></p><p class="c18"><span class="c0">Sincerely,</span></p><p class="c18 c13"><span class="c0"></span></p><p class="c9"><span class="c4">Test</span></p><p class="c9 c13"><span class="c0"></span></p><div><p class="c9 c13"><span class="c8 c14"></span></p></div></div></body></html>`
   

    res.setHeader("Content-Type", "application/pdf");

    res.status(200).send(coverLetter);
});

app.listen(process.env.PORT, () => {
    console.log(`Listening on port ${process.env.PORT}...`)
})

标签: node.jsexpress

解决方案


您可以尝试使用 puppeteer 从您的 html 生成 PDF 文档。

下面的代码会做到这一点,虽然我不得不去掉样式标签(可以添加这个内联,或者弄清楚如何让 puppeteer 玩得很好)

const express = require('express');
const dotenv = require('dotenv');

const app = express();
dotenv.config();

async function generatePdf(html) { 
    const puppeteer = require("puppeteer");
    const browser = await puppeteer.launch({
        args: ['--no-sandbox'],
        headless: true
    });

    const page = await browser.newPage();
    await page.goto(`data:text/html,${html}`, {
        waitUntil: 'networkidle0'
    });
    const options = {
        format: 'A4',
        margin: { top: "60px", bottom: "60px" },
        printBackground: true,
    }
    const pdf = await page.pdf(options);
    await browser.close();
    return pdf;
}

app.get('/cover-letter', async (req, res) => {
    const coverLetter = `<html><head><meta content="text/html; charset=UTF-8" http-equiv="content-type"></head><body><div class="c6"><div><p class="c9 c13"><span class="c8 c14"></span></p></div><p class="c12"><span class="c5"></span></p><a id="t.cf2cb48f4187c740031c3f8d361f8f564f9a673c"></a><a id="t.0"></a><table class="c10"><tbody><tr class="c17"><td class="c21" colspan="1" rowspan="1"><p class="c11"><span class="c20">Test</span></p></td><td class="c16" colspan="1" rowspan="1"><p class="c7"><span class="c2">710 </span><span class="c4">Example</span><span class="c0">&nbsp;Dr. </span></p><p class="c7"><span class="c4">New York</span><span class="c2">, </span><span class="c4">New York</span><span class="c2">&nbsp;</span><span class="c4">55555</span></p><p class="c7"><span class="c2">m: </span><span class="c4">618</span><span class="c2">.</span><span class="c4">235</span><span class="c2">.</span><span class="c4">1234</span><span class="c0">&nbsp;</span></p><p class="c7"><span class="c4">example</span><span class="c0">@gmail.com</span></p></td></tr></tbody></table><p class="c13 c19"><span class="c8 c15"></span></p><p class="c9"><span class="c4">8/3/2020</span></p><p class="c9 c13"><span class="c0"></span></p><p class="c9 c13"><span class="c0"></span></p><p class="c9 c13"><span class="c0"></span></p><p class="c9"><span class="c2">Dear </span><span class="c4">Hiring Manager</span><span class="c0">,</span></p><p class="c13 c18"><span class="c0"></span></p><p class="c1"><span class="c3 c8">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas cursus commodo felis. Suspendisse faucibus in lectus sed viverra. Curabitur turpis arcu, finibus vel porta et, commodo sit amet orci. Pellentesque rutrum finibus ipsum ac pharetra. Aenean varius purus erat, id rhoncus purus ornare eget. Nunc vel accumsan diam, sed sagittis augue. Aenean nec mauris dolor. Mauris vel pharetra magna. Nunc convallis id metus sed facilisis. In sed fringilla ligula.</span></p><p class="c1"><span class="c8 c3">Phasellus ut gravida nibh, vel dapibus lectus. Integer ut dui commodo tortor bibendum eleifend vel pharetra nunc. Donec at luctus leo. Praesent at tortor euismod, faucibus ante non, dignissim purus. Suspendisse sodales, lacus eu tincidunt sagittis, dui velit facilisis lacus, quis hendrerit arcu est ut urna. Praesent auctor arcu sit amet ullamcorper pellentesque. Sed dapibus quam eu lacus tempus, a luctus risus consequat. Cras id ipsum efficitur, feugiat sapien eu, mattis nunc. Proin iaculis ultricies ex ut volutpat. Vivamus sit amet consectetur nibh. Maecenas vel dictum ligula, varius porttitor elit. Suspendisse mauris nunc, sodales sit amet dignissim at, cursus dapibus est.</span></p><p class="c1"><span class="c8 c3">Nunc scelerisque efficitur diam non placerat. Aliquam enim velit, viverra id ante non, viverra aliquam quam. Quisque vel diam leo. Etiam tincidunt pellentesque mi sed eleifend. Fusce sollicitudin euismod enim, ac vestibulum metus interdum nec. Ut elementum metus in quam vehicula, sit amet scelerisque urna mollis. Praesent et arcu magna. Donec convallis sed urna vel euismod. Proin auctor faucibus leo mattis laoreet. Donec varius faucibus leo, non porttitor eros. Integer efficitur porta nunc eu ornare. Ut vel tellus nec nisi consequat dignissim.</span></p><p class="c1"><span class="c8 c3">Nunc dictum metus sed metus mattis aliquet. Vestibulum et justo tristique, fermentum metus ut, sagittis massa. Aliquam erat volutpat. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Quisque ut eros ut ligula euismod gravida. Suspendisse consequat tincidunt urna vitae tempor. Nam rhoncus dapibus ante, at iaculis nisl molestie nec. Nunc tincidunt congue viverra.</span></p><p class="c1"><span class="c3">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam viverra, nunc sit amet venenatis placerat, arcu risus imperdiet dolor, non vulputate metus nunc pellentesque nulla. Quisque pulvinar interdum maximus. Mauris quis est a nisl vulputate sagittis. Suspendisse laoreet a odio ac tincidunt. Donec suscipit, turpis blandit sollicitudin iaculis, urna justo faucibus velit, varius vehicula purus urna sed orci. Nulla facilisi. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</span></p><p class="c18"><span class="c0">Sincerely,</span></p><p class="c18 c13"><span class="c0"></span></p><p class="c9"><span class="c4">Test</span></p><p class="c9 c13"><span class="c0"></span></p><div><p class="c9 c13"><span class="c8 c14"></span></p></div></div></body></html>`
    const pdfBuffer = await generatePdf(coverLetter);
    res.setHeader("Content-Type", "application/pdf");
    res.setHeader("Content-Disposition","inline; filename=\"test_resume_company_name.pdf\"");
    res.status(200).send(pdfBuffer);
});


app.listen(process.env.PORT, () => {
    console.log(`Listening on port ${process.env.PORT}...`)
})

推荐阅读