Puppeteer - 将空内容返回到我的抓取中

Puppeteer - Returning null content to my scrape

我正在 google 页面上进行网络抓取,使用 node.js 和 puppeteer,因此用户输入股票代码,我连接到 google 搜索 URL 然后我抓取那只股票的变化。但有时它有效,有时我收到错误:错误:评估失败:TypeError:无法读取 属性 'textContent' of null.

我已经尝试使用 waitForSelector 函数,然后超时,而且,使用 waitUntil: "domcontentloaded" 也没有用。我该怎么办?

这是我的代码示例,它不起作用:(有 3 个可能的元素,如果变化向上、向下或为零,这就是为什么有 2 个条件)

const browser = await puppeteer.launch({ args: ["--no-sandbox"] });
const page = await browser.newPage();
const ticker = fundParser(fund);
const url = "https://www.google.com/search?q=" + ticker.ticker; //Ticker value could be rztr11, arct11 or rzak11
await page.goto(url,{ waitUntil: "networkidle2"});
console.log("Visiting " + url);

 // scrapes variation text. If positive or zero, the first scrape will be null, so there's a conditional for changing its value to the correct one
var variation = await page.$(
    "#knowledge-finance-wholepage__entity-summary > div > g-card-section > div > g-card-section > div.wGt0Bc > div:nth-child(1) > span.WlRRw.IsqQVc.fw-price-dn > span:nth-child(1)"
);
if (variation == null) {
  variation = await page.$(
    "#knowledge-finance-wholepage__entity-summary > div > g-card-section > div > g-card-section > div.wGt0Bc > div:nth-child(1) > span.WlRRw.IsqQVc.fw-price-up > span:nth-child(1)"
  );
  if (variation == null) {
    variation = await page.$(
    "#knowledge-finance-wholepage__entity-summary > div > g-card-section > div > g-card-section > div.wGt0Bc > div:nth-child(1) > span.WlRRw.IsqQVc.fw-price-nc > span:nth-child(1)"
    );
}}
console.log("Extracting fund variation");
const variationText = await page.evaluate(
  (variation1) => variation1.textContent,
  variation
);
console.log("Extracted:" + variationText);

两件事:

  1. 您的选择器太脆弱了:如果 google 更新您的选择器中的任何内容,它会破坏整个选择器。您需要简化选择器。
  2. 您不需要重复 if null 检查,您可以通过用逗号 (,) 连接选择器来传递多个元素。
  3. 归根结底,如果您的选择器没有 return 任何东西,您需要决定您的应用程序将如何处理该错误状态。

项目 1 - 脆性选择器

对于第一项,您绝对不希望选择器中的任何位置出现乱七八糟的类名。这些是 类,例如 .wGt0Bc.WlRRw.IsqQVc 等。我不知道 google 背后使用的是什么技术,但看起来就像他们正在使用一些 CSS-in-JS 解决方案一样,这意味着这些奇怪的类名已经完成生成并且可能会随着时间的推移而改变。因此,将它们用作选择器意味着您的人偶脚本将需要不断更新。相反,如果您避免在选择器中使用这些,您的人偶代码将运行更长时间。

我推荐以下选择器:

#knowledge-finance-wholepage__entity-summary .fw-price-dn > span:first-child,
#knowledge-finance-wholepage__entity-summary .fw-price-up > span:first-child,
#knowledge-finance-wholepage__entity-summary .fw-price-nc > span:first-child

由于没有生成这些类名,我猜这些类名将在更长时间内保持不变。

项目 2 - 使用单个选择器

如前所述,不需要重复调​​用page.$(),只需要创建一个可以匹配多个元素的选择器即可the same way you would in CSS.

项目 3 - 错误处理

最终,您的代码 运行 不正确,因为它没有正确处理错误。由您决定如何处理此错误。在您的示例代码中,您只是将事情注销,所以也许您只是想注销您无法获得该股票代码的价格变化。

综合起来

page.waitForSelector() 方法 return 如果找到元素则 ElementHandle ,否则抛出错误。因此,我们可以直接使用它而不是 page.$().

这是我能够在本地测试的一些似乎有效的代码。

const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
    const page = await browser.newPage();
    const ticker = fundParser(fund);
    const url = 'https://www.google.com/search?q=' + ticker.ticker; //Ticker value could be rztr11, arct11 or rzak11

    console.log('Visiting ' + url);
    await page.goto(url, { waitUntil: 'networkidle2' });
    console.log('Visiting ' + url);

    const variation_selector = `#knowledge-finance-wholepage__entity-summary .fw-price-dn > span:first-child,
    #knowledge-finance-wholepage__entity-summary .fw-price-up > span:first-child,
    #knowledge-finance-wholepage__entity-summary .fw-price-nc > span:first-child`;

    try {
        console.log('Extracting fund variation');

        // By default this has a 30 second (30000 ms) timeout. If no element is found after then, an error is thrown.
        const variation = await page.waitForSelector(variation_selector, { timeout: 30000 });

        const variationText = await page.evaluate(
            (variation1) => variation1.textContent,
            variation
        );
        console.log('Extracted: ' + variationText);
    } catch (err) {
        console.error('No variation element could be found.');
    }

    await browser.close();
})();

或者,您也可以获取某些内容的整个 text,然后单独解析,而不是尝试解析 DOM.

例如:

const knowledge_summary_selector = '#knowledge-finance-wholepage__entity-summary > div > g-card-section';
let knowledge_summary_inner_text;
try {
    const knowledge_summary = await page.waitForSelector(knowledge_summary_selector);
    
    /**
     * Example value for `knowledge_summary_inner_text`:
     * "Market Summary > FI Imobiliario Riza Terrax unica\n106.05 BRL\n0.00 (0.00%)\nFeb 12, 6:06 PM GMT-3 ·Disclaimer\nBVMF: RZTR11\nFollow"
     */
    knowledge_summary_inner_text = await page.evaluate(
        (element) => element.innerText.toString().trim(),
        knowledge_summary
    );

    // Now, parse your `knowledge_summary_inner_text` via some means
    const knowledge_summary_pieces = knowledge_summary_inner_text.split('\n');
    // etc...
} catch (err) {
    console.error('...');
}

在这里,knowledge_summary_inner_text 看起来像:

Market Summary > FI Imobiliario Riza Terrax unica
106.05 BRL
0.00 (0.00%)
Feb 12, 6:06 PM GMT-3 ·Disclaimer
BVMF: RZTR11
Follow

现在这个内容可能更容易解析,比如说,在 .split('\n') 和一些 regular expression matching 之后。