如何在不使用XmlService的情况下在Google Apps Script中解析HTML字符串?

37

我希望使用Google Apps Script在Google表格中创建一个网络爬虫。我知道这是可能的,并且我已经看过一些关于此的教程和讨论。

主要思路是使用:

  var html = UrlFetchApp.fetch('http://en.wikipedia.org/wiki/Document_Object_Model').getContentText();
  var doc = XmlService.parse(html);

然后获取并处理元素。但是,该方法

XmlService.parse()

对于某些页面无法正常工作。例如,如果我尝试:

function test(){
    var html = UrlFetchApp.fetch("https://www.nespresso.com/br/pt/product/maquina-de-cafe-espresso-pixie-clips-preto-lima-neon-c60-220v").getContentText();
    var parse = XmlService.parse(html);
}

我得到了以下错误:

Error on line 225: The entity name must immediately follow the '&' in the entity reference. (line 3, file "")

我尝试使用string.replace()来消除导致错误的字符,但它没有起作用。出现了各种其他的错误。以下代码是一个例子:

I've tried to use string.replace() to eliminate the characters that apparently are causing the error, but it does not work. All sort of other errors appear. The following code for example:

function test(){
    var html = UrlFetchApp.fetch("https://www.nespresso.com/br/pt/product/maquina-de-cafe-espresso-pixie-clips-preto-lima-neon-c60-220v").getContentText();
    var regExp = new RegExp("&", "gi");
    html = html.replace(regExp,"");

    var parse = XmlService.parse(html);
}

会弹出以下错误:

Error on line 358: The content of elements must consist of well-formed character data or markup. (line 6, file "")

我认为这是XmlService.parse()方法存在的问题。

我在以下讨论中阅读到:

Google App Script解析混乱的HTML表格最佳的在Google Apps Script中解析HTML的方法。其中提到可以使用一个名为xml.parse()的过时方法,该方法接受第二个参数以允许解析HTML。但是正如我所提到的,它已被弃用,并且我无法在任何地方找到任何关于它的文档。似乎xml.parse()可以解析字符串,但由于缺乏文档,我无法处理元素。而且它也不是长期安全的解决方案,因为它可能很快就会被停用。

那么,我想知道在Google Apps Script中如何解析这个HTML?

我也尝试了:

function test(){

    var html = UrlFetchApp.fetch("https://www.nespresso.com/br/pt/product/maquina-de-cafe-espresso-pixie-clips-preto-lima-neon-c60-220v").getContentText();
    var htmlOutput = HtmlService.createHtmlOutput(html).getContent();

    var parse = XmlService.parse(htmlOutput);
}

但是它不起作用,我收到了这个错误:

格式不正确的HTML内容:

我考虑使用一个开源库来解析HTML,但是我找不到任何一个。

我的最终目标是从一系列页面中获取一些信息,例如产品的价格、链接、名称等。我已经通过一系列正则表达式实现了这一点:

var ss = SpreadsheetApp.getActiveSpreadsheet();
  var linksSheet = ss.getSheetByName("Links");
  var resultadosSheet = ss.getSheetByName("Resultados");

function scrapyLoco(){

  var links = linksSheet.getRange(1, 1, linksSheet.getLastRow(), 1).getValues();
  var arrayGrandao = [];
  for (var row =  0, len = links.length; row < len; row++){
   var link = links[row];


   var arrayDeResultados = pegarAsCoisas(link[0]);
   Logger.log(arrayDeResultados);
   arrayGrandao.push(arrayDeResultados);
  }   


  resultadosSheet.getRange(2, 1, arrayGrandao.length, arrayGrandao[0].length).setValues(arrayGrandao);

}


function pegarAsCoisas(linkDoProduto) {
  var resultadoArray = [];

  var html = UrlFetchApp.fetch(linkDoProduto).getContentText();
  var regExp = new RegExp("<h1([^]*)h1>", "gi");
  var h1Html = regExp.exec(html);
  var h1Parse = XmlService.parse(h1Html[0]);
  var h1Output = h1Parse.getRootElement().getText();
  h1Output = h1Output.replace(/(\r\n|\n|\r|(^( )*))/gm,"");

  regExp = new RegExp("Ref.: ([^(])*", "gi");
  var codeHtml = regExp.exec(html);
  var codeOutput = codeHtml[0].replace("Ref.: ","").replace(" ","");

  regExp = new RegExp("margin-top: 5px; margin-bottom: 5px; padding: 5px; background-color: #699D15; color: #fff; text-align: center;([^]*)/div>", "gi");
  var descriptionHtml = regExp.exec(html);
  var regExp = new RegExp("<p([^]*)p>", "gi");
  var descriptionHtml = regExp.exec(descriptionHtml);
  var regExp = new RegExp("^[^.]*", "gi");
  var descriptionHtml = regExp.exec(descriptionHtml);
  var descriptionOutput = descriptionHtml[0].replace("<p>","");
  descriptionOutput = descriptionOutput+".";

  regExp = new RegExp("ecom(.+?)Main.png", "gi");
  var imageHtml = regExp.exec(html);
  var comecoDaURL = "https://www.nespresso.com/";
  var imageOutput = comecoDaURL+imageHtml[0];

  var regExp = new RegExp("nes_l-float nes_big-price nes_big-price-with-out([^]*)p>", "gi");
  var precoHtml = regExp.exec(html);
  var regExp = new RegExp("[0-9]*,", "gi");
  precoHtml = regExp.exec(precoHtml);
  var precoOutput = "BRL "+precoHtml[0].replace(",","");

  resultadoArray = [codeOutput,h1Output,descriptionOutput,"Home & Garden > Kitchen & Dining > Kitchen Appliances > Coffee Makers & Espresso Machines",
                    "Máquina",linkDoProduto,imageOutput,"new","in stock",precoOutput,"","","","Nespresso",codeOutput];

  return resultadoArray;
}

但是编写这个程序需要很长时间,很难动态更改,并且不太可靠。

我需要一种解析HTML并轻松访问其元素的方法。实际上这不是一个插件,而是一个简单的Google应用脚本。


3
这个问题的回答是否解决了您的疑问?使用CSS选择器解析HTML字符串 - Kos
8个回答

39

我为你的问题制作了cheeriogs。它在GAS上像cheerio一样工作,具有类似于jQuery的API。你可以像这样操作。

const content = UrlFetchApp.fetch('https://example.co/').getContentText();
const $ = Cheerio.load(content);
Logger.log($('p .blah').first().text()); // blah blah blah ...

参见 https://github.com/asciian/cheeriogs


1
它不工作。甚至无法运行。一旦我添加了库,谷歌脚本甚至都无法进行调试。 - toddmo
@toddmo 有时候它运行。 - cikatomo
v12 对我来说运行得非常好。 - thdoan
这正是我想要的! - DSdavidDS

13

此前已经讨论过 - 请参见这个问答

XML服务不同,XMLService对格式不正确的HTML文档不太宽容。 Justin Bicknell在回答中提供的技巧可以解决这个问题。尽管XML服务已被弃用,但它仍然继续工作。


6
XMLService 对格式良好的 HTML 连宽容也没有。HTML 不是 XML,即使是基本的现代 HTML,XML 解析器也会出现错误。 - Mashmagar
4
Xml.parse 已完全从 Google Scripts 中移除。 - Chirag

12

我已经使用原生 JavaScript 实现了此操作。并没有进行真正的 HTML 解析。只是尝试从字符串(URL)中获取一些内容:

function getLKKBTC() {
  var url = 'https://www.lykke.com/exchange';
  var html = UrlFetchApp.fetch(url).getContentText();
  var searchstring = '<td class="ask_BTCLKK">';
  var index = html.search(searchstring);
  if (index >= 0) {
    var pos = index + searchstring.length
    var rate = html.substring(pos, pos + 6);
    rate = parseFloat(rate)
    rate = 1/rate
    return parseFloat(rate);
  }
  throw "Failed to fetch/parse data from " + url;
}

4
请注意,某些网站可能不允许自动抓取其内容,请在使用Apps Script提取内容之前查阅其服务条款。
XmlService仅适用于有效的XML文档,而大多数HTML(特别是HTML5)都不是有效的XML。之前版本的XmlService,简称为Xml,允许“宽松”解析,从而使其能够解析HTML。该服务已于2013年停用,但目前仍在运行。参考文档不再可用,但这个旧教程展示了它的使用方法。
另一种选择是使用像Kimono这样的服务,它处理抓取和解析部分,并提供一个简单的API,您可以通过UrlFetchApp调用以检索结构化数据。

2
Kimono在2016年2月被收购并关闭。Portia是一个开源的替代品。 - Chris

4

今天我运气不错,只是通过修改HTML代码:

// close unclosed tags
html = html.replace(/(<(?=link|meta|br|input)[^>]*)(?<!\/)>/ig, '$1/>')
// force script / style content into cdata
html = html.replace(/(<(script|style)[^>]*>)/ig, '$1<![CDATA[').replace(/(<\/(script|style)[^>]*>)/ig, ']]>$1')
// change & to &amp;
html = html.replace(/&(?!amp;)/g, '&amp;')
// now it works! (tested with original url)
let document = XmlService.parse(html)

1
运行良好,我添加了img标签到这里。 - MC9000
它对我不起作用。我尝试将img添加到第一行,但不确定是否做对了。我们能否更新此代码,使其像大多数情况下一样正常工作? - Jason Cramer

1
我发现使用Google App Script有一个非常好的爬虫替代方案。它叫做PhantomJS Cloud。可以使用urlFetchApp访问API。这使得在页面上执行Jquery代码变得更加简单易行。保留html标签。

0

你能否使用JavaScript解析HTML?如果你的Google Apps脚本将HTML作为字符串检索并返回给JavaScript函数,那么似乎你可以在Google Apps脚本之外很好地解析它。任何你想要抓取的标签,你都可以发送到一个专用的Google Apps函数来保存内容。

你可能更容易地使用jQuery实现这一点。


1
我需要在Google App Script上运行它。我没有找到在App Script中运行Jquery的方法。 - user3347814
我没有仔细阅读原帖 - 我写下了我的答案,想象你正在制作一个 Web 应用程序,并且可以在 JavaScript 文件和 Google 应用脚本文件之间轻松传递数据。我不知道如何在电子表格插件中实现这个结果。 - Eric Dauenhauer

0

也许不是最干净的方法,但简单的字符串处理也可以完成工作,而无需使用xmlservice:

var url = 'https://somewebsite.com/?q=00:11:22:33:44:55';
var html = UrlFetchApp.fetch(url).getContentText();
// we want only the link text displayed from here:
//<td><a href="/company/ubiquiti-networks-inc">Ubiquiti Networks Inc.</a></td>
var string1 = html.split('<td><a href="/company/')[1]; // all after '<td><a href="/company/'
var string2 = string1.split('</a></td>')[0];           // all before '</a></td>'
var string3 = string2.split('>')[1];                   // all after '>'
Logger.log('link text: '+string3);                     // string3 => "Ubiquiti Networks Inc."

1
作为一个初学者,我很欣赏你使用“vanilla”结构。 - aNewb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接