<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          實踐指南-網(wǎng)頁生成PDF

          共 6358字,需瀏覽 13分鐘

           ·

          2021-06-12 17:21

          一、背景

          開發(fā)工作中,需要實現(xiàn)網(wǎng)頁生成 PDF 的功能,生成的 PDF 需上傳至服務(wù)端,將 PDF 地址作為參數(shù)請求外部接口,這個轉(zhuǎn)換過程及轉(zhuǎn)換后的 PDF 不需要在前端展示給用戶。

          二、技術(shù)選型

          該功能不需要在前端展示給用戶,為節(jié)省客戶端資源,選擇在服務(wù)端實現(xiàn)網(wǎng)頁生成 PDF 的功能。

          1. Puppeteer

          Puppeteer[1] 是一個 Node 庫,它提供了高級 API 來通過 DevTools 協(xié)議控制 ChromeChromium。

          在瀏覽器中手動執(zhí)行的大多數(shù)操作都可以使用 Puppeteer 完成,比如:

          • 生成頁面的屏幕截圖和 PDF;
          • 爬取 SPA 并生成預(yù)渲染的內(nèi)容(即 SSR);
          • 自動進(jìn)行表單提交,UI 測試,鍵盤輸入等;
          • 創(chuàng)建最新的自動化測試環(huán)境。使用最新的 JavaScript 和瀏覽器功能,直接在最新版本的 Chrome 中運行測試;
          • 捕獲時間線跟蹤網(wǎng)站,以幫助診斷性能問題;
          • 測試 Chrome 擴(kuò)展程序。

          從上可見,Puppeteer 可以實現(xiàn)在Node 端生成頁面的 PDF 功能。

          三、實現(xiàn)步驟

          1. 安裝

          進(jìn)入項目,安裝 puppeteer 到本地。

          $?npm?install?-g?cnpm?--registry=https://registry.npm.taobao.org
          $?cnpm?i?puppeteer?--save

          需注意的是,安裝 puppeteer 時,會下載與 API 一起使用的最新版本的 Chromium 瀏覽器,有以下方法可以修改默認(rèn)設(shè)置,不下載瀏覽器:

          1. 環(huán)境變量[2]中設(shè)置 PUPPETEER_SKIP_CHROMIUM_DOWNLOAD
          2. puppeteer-core 代替 puppeteer。

          puppeteer-corepuppeteer 的輕量級版本,默認(rèn)不下載瀏覽器,而是啟動現(xiàn)有的瀏覽器或者連接遠(yuǎn)程瀏覽器,使用 puppeteer-core 需注意本地有可連接的瀏覽器,且安裝的 puppeteer-core 版本與打算連接的瀏覽器兼容。連接本地瀏覽器方法如下:

          const?browser?=?await?puppeteer.launch({?
          ??executablePath:?'/path/to/Chrome'?
          });

          本項目需要部署至服務(wù)端,沒有可連接的瀏覽器,因此選擇安裝的是 puppeteer。

          2. 啟動瀏覽器

          const?browser?=?await?puppeteer.launch({
          ????headless:?true,
          ????args:?['--no-sandbox',?'--font-render-hinting=medium']
          ??})

          headless 代表無頭模式,在后端啟動瀏覽器,前端不會有展示。

          小建議:本地調(diào)試時,建議設(shè)置 headless: false,可以啟動完整版本的瀏覽器,直接在瀏覽器窗口查看內(nèi)容。

          3. 打開新頁面

          生成瀏覽器后,在瀏覽器中打開新頁面。

          const?page?=?await?browser.newPage()

          4. 跳轉(zhuǎn)到指定頁面

          跳轉(zhuǎn)至要生成 PDF 的頁面。

          await?page.goto(`${baseURL}/article/${id}`,?{
          ????timeout:?60000,
          ????waitUntil:?'networkidle2',?//?networkidle2?會一直等待,直到頁面加載后不存在?2?個以上的資源請求,這種狀態(tài)持續(xù)至少?500?ms
          ??})

          timeout 是最長的加載時間,默認(rèn) 30s,網(wǎng)頁加載時間長的情況下,建議將 timeout 值改大,防止超時報錯。

          waitUntil 表示頁面加載到什么程度可以開始生成 PDF 或其他操作了,當(dāng)網(wǎng)頁需加載的圖片資源較多時,建議設(shè)置為 networkidle2,有以下值可選:

          • load:當(dāng) load 事件觸發(fā)時;
          • domcontentloaded:當(dāng) DOMContentLoaded 事件觸發(fā)時;
          • networkidle0:頁面加載后不存在 0 個以上的資源請求,這種狀態(tài)持續(xù)至少 500 ms;
          • networkidle2:頁面加載后不存在 2 個以上的資源請求,這種狀態(tài)持續(xù)至少 500 ms。

          5. 指定路徑,生成pdf

          上述指定的頁面加載完成后,將該頁面生成 PDF。

          ??const?ext?=?'.pdf'
          ??const?key?=?randomFilename(title,?ext)
          ??const?_path?=?path.resolve(config.uploadDir,?key)
          ??await?page.pdf({?path:?_path,?format:?'a4'?})

          path 表示將 PDF 保存到的文件路徑,如果未提供路徑,PDF 將不會保存至磁盤。

          小建議:不管 PDF 是不是需要保存到本地,建議在調(diào)試的時候都設(shè)置一個path,方便查看生成的 PDF 的樣式,檢查是否有問題。

          format 表示 PDF 的紙張格式,a4 尺寸為 8.27 英寸 x 11.7 英寸,是傳統(tǒng)的打印尺寸。

          注意:目前僅支持headless: true 無頭模式下生成 PDF

          6. 關(guān)閉瀏覽器

          所有操作完成后,關(guān)閉瀏覽器,節(jié)約性能。

          ??await?browser.close()
          四、難點

          1. 圖片懶加載

          由于需生成 PDF 的頁面是文章類型的頁面,包含大量圖片,且圖片引入了懶加載,導(dǎo)致生成的 PDF 會帶有很多懶加載兜底圖,效果如下圖:

          1c6a2fc38668d2f14e01907c71842106.webp

          解決方法是跳轉(zhuǎn)到頁面后,將頁面滾動到底部,所有圖片資源都會得到請求,waitUntil 設(shè)置為 networkidle2,圖片就能加載成功了。

          await?autoScroll(page)?//?因為文章圖片引入了懶加載,所以需要把頁面滑動到最底部,保證所有圖片都加載出來

          /**
          ?*?控制頁面自動滾動
          ?*?*/
          function?autoScroll?(page)?{
          ??return?page.evaluate(()?=>?{
          ????return?new?Promise<void>(resolve?=>?{
          ??????let?totalHeight?=?0
          ??????const?distance?=?100
          ??????//?每200毫秒讓頁面下滑100像素的距離
          ??????const?timer?=?setInterval(()?=>?{
          ????????const?scrollHeight?=?document.body.scrollHeight
          ????????window.scrollBy(0,?distance)
          ????????totalHeight?+=?distance
          ????????if?(totalHeight?>=?scrollHeight)?{
          ??????????clearInterval(timer)
          ??????????resolve()
          ????????}
          ??????},?200)
          ????})
          ??})
          }

          這里用到了 page.evaluate() 方法,用來控制頁面操作,比如使用內(nèi)置的 DOM 選擇器、使用 window 方法等等。

          2. CSS 打印樣式

          根據(jù)官網(wǎng)[3]說明,page.pdf() 生成 PDF 文件的樣式是通過 print css media 指定的,因此可以通過 css 來修改生成的 PDF 的樣式,以本文需求為例,生成的 PDF 需要隱藏頭部、底部,以及其他和文章主體無關(guān)的部分,代碼如下:

          @media?print?{
          ??.other_info,
          ??.authors,
          ??.textDetail_comment,
          ??.detail_recTitle,
          ??.detail_rec,
          ??.SuspensePanel?{
          ????display:?none?!important;
          ??}

          ??.Footer,
          ??.HeaderSuctionTop?{
          ????display:?none;
          ??}
          }

          3. 登錄態(tài)

          由于存在一部分文章不對外部用戶公開,需要鑒權(quán)用戶身份,符合要求的用戶才能看到文章內(nèi)容,因此跳轉(zhuǎn)到指定文章頁后,需要在生成的瀏覽器窗口中注入登錄態(tài),符合條件的登錄用戶才能看到這部分文章的內(nèi)容。

          采用注入 cookie 的方式來獲取登錄態(tài),使用 page.evaluate() 設(shè)置 cookie,代碼如下:


          async?function?simulateLogin?(page,?cookies,?domain)?{
          ??return?await?page.evaluate((sig,?sess,?domain)?=>?{
          ????let?date?=?new?Date()
          ????date?=?new?Date(date.setDate(date.getDate()?+?1))
          ????let?expires?=?''
          ????expires?=?`;?expires=${date.toUTCString()}`
          ????document.cookie?=?`koa:sess.sig=${sig}${expires};?domain=${domain};?path=/`
          ????document.cookie?=?`koa:sess=${sess}=${expires};?domain=${domain};?path=/`?//?=是這個cookie的value
          ????document.cookie?=?`is_login=true${expires};?domain=${domain};?path=/`
          ??},?cookies['koa:sess.sig'],?cookies['koa:sess'],?domain)
          }


          await?simulateLogin(page,?cookies,?config.domain.split('//')[1])

          小建議:Puppeteer 也有自帶的 api 實現(xiàn) cookie 注入,如 page.setCookie({name: name, value: value}),但是我用這個方式注入沒能獲取到登錄態(tài),沒有找到具體原因,建議還是直接用我上面這個方法來注入 cookie,注意除 namevalue外,expires、domainpath 也需要配置。

          4. Docker 部署 Puppeteer

          根據(jù)上文操作,本地已經(jīng)可以成功將頁面生成 PDF 了,本地體驗沒問題后,需要部署到服務(wù)端給到測試、上線。

          沒有修改 Dockerfile 時,部署后發(fā)現(xiàn)了如下錯誤:633a3ed0a820f7185e9abe5c66da99b3.webp

          官網(wǎng)有給 Docker 配置說明[4]可以參考,最終實踐可用的 ubuntu 系統(tǒng)的 Dockerfile 如下:

          #?...省略...

          #?安裝?puppeteer?依賴
          RUN?apt-get?update?&&?\
          ????apt-get?install?-y?libgbm-dev?&&?\
          ????apt-get?install?gconf-service?libasound2?libatk1.0-0?libatk-bridge2.0-0?libc6?libcairo2?libcups2?libdbus-1-3?libexpat1?libfontconfig1?libgcc1?libgconf-2-4?libgdk-pixbuf2.0-0?libglib2.0-0?libgtk-3-0?libnspr4?libpango-1.0-0?libpangocairo-1.0-0?libstdc++6?libx11-6?libx11-xcb1?libxcb1?libxcomposite1?libxcursor1?libxdamage1?libxext6?libxfixes3?libxi6?libxrandr2?libxrender1?libxss1?libxtst6?ca-certificates?fonts-liberation?libappindicator1?libnss3?lsb-release?xdg-utils?wget?build-essential?libcairo2-dev?libpango1.0-dev?libjpeg-dev?libgif-dev?librsvg2-dev?-y?&&?\
          ????apt-get?install?-y?fonts-ipafont-gothic?fonts-wqy-zenhei?fonts-thai-tlwg?fonts-kacst?fonts-freefont-ttf?--no-install-recommends

          #?...省略...

          只需要重點關(guān)注 安裝 puppeteer 依賴 部分即可。

          注意:在 v1.18.1 之前,Puppeteer 至少需要 Node v6.4.0。從 v1.18.1 到 v2.1.0 的版本都依賴于 Node 8.9.0+。從 v3.0.0 開始,Puppeteer 開始依賴于 Node 10.18.1+。配置 Dockerfile 時也需要注意服務(wù)端的 node 版本。

          五、總結(jié)

          本文講述了實現(xiàn)在 Node 端將網(wǎng)頁生成 PDF 文件的完整過程,總結(jié)為以下 3 點:

          1. 技術(shù)選型,根據(jù)需求場景選擇合適的手段實現(xiàn)功能;
          2. 閱讀官方文檔[5],快速過一遍文檔才能少遇到些坑;
          3. 破解難點,使用一個未使用的工具,會遇到?jīng)]有解決過的難題,遇招拆招吧 ^ ^。

          參照 Demo 源碼[6] 可快速上手上述功能,希望本文能對你有所幫助,感謝閱讀??


          · 往期精彩 ·

          【直播回顧·程序媛的成長蛻變】

          【大規(guī)格文件的上傳優(yōu)化】

          【JDR DESIGN 開發(fā)小結(jié)】

          參考資料

          [1]

          Puppeteer: https://pptr.dev/

          [2]

          環(huán)境變量: https://github.com/puppeteer/puppeteer/blob/v8.0.0/docs/api.md#environment-variables

          [3]

          官網(wǎng): https://pptr.dev/#?product=Puppeteer&version=v8.0.0&show=api-pagepdfoptions

          [4]

          Docker 配置說明: https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#running-puppeteer-in-docker

          [5]

          官方文檔: https://pptr.dev/

          [6]

          Demo 源碼: https://github.com/jiaozitang/puppeteerPdfDemo

          往期推薦

          d7563c2798333ed1ea7b24e34ce8ed42.webp

          Vite 太快了,煩死了,是時候該小睡一會了。


          722d8f269bd87967a12d7748f7b24a43.webp

          如何實現(xiàn)比 setTimeout 快 80 倍的定時器?


          a8ad031775bd2cc06a6ce21f8c117489.webp

          萬字長文!總結(jié)Vue 性能優(yōu)化方式及原理


          1c80b499cdcc42b50b5e7075e1594120.webp

          90 行代碼的 webpack,你確定不學(xué)嗎?


          最后





          如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個小忙:

          1. 點個「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點在看,都是耍流氓 -_-)

          2. 歡迎加我微信「huab119」拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...

          3. 關(guān)注公眾號「前端勸退師」,持續(xù)為你推送精選好文,也可以加我為好友,隨時聊騷。




          74fd756e5ae70f72c032fbca5ee0165c.webp點個在看支持我吧,轉(zhuǎn)發(fā)就更好了



          瀏覽 66
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  婷婷综合五月天 | 欧美午夜激情视频 | 亚洲视频在线免费观 | 欧美一级成人网站 | 男女操逼视频免费看 |