Веб-страницы в Интернете не всегда соответствуют стандарту HTML. Однако синтаксический анализатор XML (а именно XMLService.parse), предоставляемый Google App Script, мог анализировать только допустимые файлы XML.

Проблемы

Был старый синтаксический анализатор под названием Xml.parse(), но время выполнения скрипта предупредит вас, если вы используете его в своем скрипте, что этот синтаксический анализатор устарел. Между тем, хотя этот старый синтаксический анализатор мог анализировать искаженный HTML, установив для второго параметра значение true, он предоставляет чрезвычайно ограниченные методы для выполнения операций DOM. Например: маловероятно, что содержимое текстового узла будет находиться между двумя родственными элементами тега.

Чтобы решить эту проблему, я должен использовать XMLService. Мой подход заключается в преобразовании искаженного HTML в более привлекательный формат. Деформированный HTML, из-за которого XMLService не работает, обычно имеют:

  1. бродячие теги (это сложно, проблема на уровне механизма рендеринга браузера, здесь не рассматривается)
  2. значения атрибутов тега без двойных кавычек
  3. текстовые элементы, содержащие символ &, который имеет значение «символ» в спецификации XML
  4. может быть, больше…

Поскольку мы занимаемся парсингом веб-страниц, некоторые атрибуты, такие как style, lang и height/width, не являются ключевыми, поэтому их можно отбросить.

Из UrlFetchApp.fetch().getContentText() вы получите обычный текст html. Прежде чем использовать XMLService.parse(), я создал функцию для «преобразования» с именем fixTags():

function fixTags(inhtml) {
  function attrRemoveQuote(inHtml) {
    var nextHtml = ''
    var regex1 = RegExp('<((.|\s)+?)"((.|\s)+?)>');
    if (regex1.test(inHtml) === false) {
      return inHtml
    } else {
      nextHtml = inHtml
      .replace(/<((.|\s)+?)"((.|\s)*?)>/g, '<$1$3>')
      return attrRemoveQuote(nextHtml)
    }
  }
  function addQuote(inHtml) {
    var nextHtml = inHtml
    .replace(/<(.+?)=([^"]+?)(\s|>)/g, '<$1="$2"$3')
    if (inHtml.length === nextHtml.length) {
      return inHtml
    } else {
      return addQuote(nextHtml)
    }
  }
  
  var inhtml2 = attrRemoveQuote(
   inhtml.replace(/\sstyle='(.|\s)+?'/g, ' ')
   .replace(/\sname="(.|\s)+?"/g, ' ')
  )
  .replace(/\slang=('?).+?('|>|\s)/g, ' $2')
  .replace(/<([^']+)'([^']+)/g, '<$1"$2')
  .replace(/<([^']+)'([^']*)>/g, '<$1"$2>')
  .replace(/<(.+?)=([^"]+?)(\s|>)/g, '<$1="$2"$3')
  
  return addQuote(inhtml2)
  .replace(/\&nbsp\;/g, ' ')
  .replace(/\&amp\;/g, '&')
  .replace(/\&quot\;/g, '"')
}
  1. удалить ненужные атрибуты тега style, name
  2. удалите двойную кавычку для некоторого стиля семейства шрифтов, если таковой имеется
  3. преобразовать одинарные кавычки в двойные кавычки
  4. преобразовывать экранированные специальные символы HTML (&, ", ‹space›) в обычный текст или символы полной ширины, чтобы не запутать синтаксический анализатор

После этого можно вызывать XMLService.parse (в моем случае), можно, наконец, использовать getChild(), getChildren() вместо getElement(), getElements(). Вы также можете воспользоваться методами, описанными в документации.

Ограничение

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

В других случаях вы можете рассмотреть Scrapy, работающий на morph.io (также на ежедневной основе), или более обычный облачный веб-сервис, такой как AWS lambda, с использованием вашей собственной программы очистки, или просто настроить сервер очистки… и т. д.

Microsoft Flow иногда тоже может работать! XD