Практическое руководство — создание PDF-файлов из веб-страниц

внешний интерфейс
Практическое руководство — создание PDF-файлов из веб-страниц

1. Предпосылки

В процессе разработки необходимо реализовать функцию генерации PDF из веб-страниц.Сгенерированный PDF необходимо загрузить на сервер, а адрес PDF используется в качестве параметра для запроса внешнего интерфейса.Этот процесс преобразования и преобразованный PDF-файл не нужно отображать пользователю во внешнем интерфейсе.

2. Технический отбор

Эту функцию не нужно отображать пользователю на фронтэнде.Для экономии ресурсов клиента выберите реализацию функции генерации PDF из веб-страниц на стороне сервера.

1. Puppeteer

PuppeteerЯвляетсяNodeбиблиотека, которая предоставляет расширенныеAPIпройтиDevToolsУправление протоколомChromeилиChromium.

Большинство действий, которые вы можете выполнять вручную в браузере, можно выполнить с помощьюPuppeteerсделано, например:

  • Создание скриншотов и PDF-файлов страниц;
  • ПолзаниеSPAи генерировать предварительно обработанный контент (т.SSR);
  • Автоматизировать отправку форм, тестирование пользовательского интерфейса, ввод с клавиатуры и т. д.;
  • Создайте современную автоматизированную тестовую среду. использовать последнююJavaScriptи функциональность браузера непосредственно в последней версииChromeзапустить тест в;
  • Захватите сайты трассировки временной шкалы, чтобы помочь диагностировать проблемы с производительностью;
  • тестовое заданиеChromeрасширение.

Сверху видно,Puppeteerможет быть реализован вNodeФункция PDF, которая создает страницы сбоку.

3. Этапы реализации

1. Установка

войти в проект, установитьpuppeteerк местному.

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

Следует отметить, что установкаpuppeteerзагружается сAPIпоследняя версия, используемая сChromiumБраузер, существуют следующие способы изменить настройки по умолчанию без загрузки браузера:

  1. существуетпеременная средыустановить вPUPPETEER_SKIP_CHROMIUM_DOWNLOAD;
  2. использоватьpuppeteer-coreзаменятьpuppeteer.

puppeteer-coreдаpuppeteerОблегченная версия не загружает браузер по умолчанию, а запускает существующий браузер или подключается к удаленному браузеру, используяpuppeteer-coreСледует отметить наличие локального подключаемого браузера и установленногоpuppeteer-coreВерсия совместима с браузером, предназначенным для подключения. Способ подключения к локальному браузеру следующий:

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

Этот проект необходимо развернуть на сервере, нет подключаемого браузера, поэтому выберите установкуpuppeteer.

2. Запустите браузер

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

headlessПредставляет безголовый режим, запустите браузер на бэкэнде, и на фронтенде не будет отображения.

Небольшое предложение: при локальной отладке рекомендуется установитьheadless: false, вы можете запустить полную версию браузера и просматривать содержимое прямо в окне браузера.

3. Откройте новую страницу

После создания браузера откройте новую страницу в браузере.

const page = await browser.newPage()

4. Перейти на указанную страницу

Перейдите на страницу, на которой вы хотите создать PDF-файл.

await page.goto(`${baseURL}/article/${id}`, {
    timeout: 60000,
    waitUntil: 'networkidle2', // networkidle2 会一直等待,直到页面加载后不存在 2 个以上的资源请求,这种状态持续至少 500 ms
  })

timeoutэто самое длинное время загрузки, по умолчанию 30 секунд, когда время загрузки страницы велико, рекомендуется установитьtimeoutИзмените значение на большее, чтобы предотвратить ошибку тайм-аута.

waitUntilУказывает, насколько загружена страница для начала создания PDF-файла или других операций.Если на веб-страницу загружается много графических ресурсов, рекомендуется установить значениеnetworkidle2, со следующими необязательными значениями:

  • нагрузка: когдаloadКогда событие срабатывает;
  • domcontentloaded: когдаDOMContentLoadedКогда событие срабатывает;
  • networkidle0: не более 0 запросов ресурсов после загрузки страницы, и это состояние длится не менее 500 мс;
  • networkidle2: не более 2 запросов ресурсов после загрузки страницы, и это состояние длится не менее 500 мс.

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 локально, рекомендуется задать путь при отладке, чтобы было удобно просматривать стиль сгенерированного PDF и проверять, нет ли проблем.

formatУказывает формат бумаги для PDF, размер a4 составляет 8,27 дюймов x 11,7 дюймов, что является традиционным размером печати.

Примечание. В настоящее время поддерживается только безголовый режим: true для создания PDF в автономном режиме.

6. Закройте браузер

После завершения всех операций закройте браузер для экономии производительности.

  await browser.close()

4. Трудности

1. Отложенная загрузка изображения

Поскольку страница, которая должна создать PDF-файл, является страницей типа статьи, она содержит много изображений, а изображения загружаются отложенно, поэтому сгенерированный PDF-файл будет иметь много нижних карт с отложенной загрузкой, и эффект будет следующим:

Решение состоит в том, чтобы прокрутить страницу вниз после перехода на страницу, будут запрошены все ресурсы изображения,waitUntilУстановить как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()метод, используемый для управления операциями на странице, такими как использование встроенногоDOMселектор, использоватьwindowметод и так далее.

2. Стили печати CSS

согласно сОфициальный сайтпроиллюстрировать,page.pdf()Стиль сгенерированного PDF-файла создаетсяprint css mediaуказано, поэтому может быть переданоcssЧтобы изменить стиль сгенерированного PDF-файла, взяв в качестве примера требования этой статьи, в сгенерированном PDF-файле необходимо скрыть заголовок, нижнюю часть и другие части, не относящиеся к основной части статьи.Код выглядит следующим образом. :

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

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

3. Состояние входа

Так как есть некоторые статьи, которые не открыты для внешних пользователей, пользователи должны быть аутентифицированы, а пользователи, соответствующие требованиям, могут видеть содержимое статьи, поэтому после перехода на указанную страницу статьи вам необходимо внедрить состояние входа в сгенерированное окно браузера и квалифицированный логин Только пользователи могут видеть содержание этой части статьи.

инъекцияcookieспособ получить статус входа, используйтеpage.evaluate()настраивать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выполнитьcookieвводить, какpage.setCookie({name: name, value: value}), но мне не удалось получить состояние входа в систему, внедрив таким образом, и я не нашел конкретной причины.Рекомендуется использовать вышеуказанный метод для прямой инъекции.cookie, обратите внимание наnameа такжеvalueза пределами,expires,domain,pathТакже требуется настройка.

4. Докер развертывает Puppeteer

В соответствии с приведенной выше операцией страница может быть успешно сгенерирована в формате PDF локально.После того, как локальный опыт не будет проблемой, ее необходимо развернуть на сервере для тестирования и онлайн.

без модификацииDockerfile, после развертывания была обнаружена следующая ошибка:

На официальном сайте естьИнструкции по настройке докераДоступно для справки, финальная практика доступнаubuntuсистематический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

# ...省略...

Просто нужно сосредоточитьсяУстановить зависимости кукловодачасть.

Примечание. До версии 1.18.1 для Puppeteer требовалось как минимум Node версии 6.4.0. Версии от v1.18.1 до v2.1.0 зависят от Node 8.9.0+. Начиная с версии 3.0.0, Puppeteer начинает зависеть от Node 10.18.1+. При настройке Dockerfile также необходимо обращать внимание на нод-версию сервера.

V. Резюме

В этой статье описывается реализация вNodeПолный процесс создания файла PDF из веб-страницы на стороне клиента сводится к следующим трем пунктам:

  1. Выбор технологии, выбор соответствующих средств для реализации функций в соответствии со сценариями спроса;
  2. читатьофициальная документация, просмотрите документ быстро, чтобы не столкнуться с некоторыми ямами;
  3. Для решения трудности используйте неиспользованный инструмент, вы столкнетесь с нерешенными проблемами, давайте разберем его ^^.

Ссылаться наИсходный код демоВы можете быстро начать работу с вышеуказанными функциями, я надеюсь, что эта статья поможет вам, спасибо за чтение ❤️


· Прекрасное прошлое ·

[Обзор прямой трансляции · Рост и трансформация Чэн Юаня]

【Оптимизация загрузки файлов большого размера】

【Резюме разработки JDR DESIGN】