使用node.js和无头浏览器抓取动态页面

时间:2018-08-15 20:14:58

标签: javascript node.js web-scraping puppeteer headless-browser

我正在尝试从动态加载的页面中抓取数据。为此,我使用无头浏览器 puppeteer

  在代码中

Puppeteer 可以被视为 headlessBrowserClient

主要挑战是在收到所需数据后立即正常关闭浏览器。但是,如果您在评估执行之前完成关闭它-评估执行进度将丢失。

  

evaluateCustomCode 是一个可以像在Chrome Dev工具中运行它一样调用的函数。

要控制网络请求和puppeteer API的异步流程-我使用了异步发生器,该发生器封装了上述所有逻辑。

问题是我觉得代码有异味,但看不到任何更好的解决方案。

想法?

module.exports = function buildClient (headlessBrowserClient) {
  const getPageContent = async (pageUrl, evaluateCustomCode) => {
    const request = sendRequest(pageUrl)
    const { value: page } = await request.next()

    if (page) {
      const pageContent = await page.evaluate(evaluateCustomCode)
      request.next()

      return pageContent
    }
  }

  async function * sendRequest (url) {
    const browser = await headlessBrowserClient.launch()
    const page = await browser.newPage()

    const state = {
      req: { url },
    }

    try {
      await page.goto(url)
      yield page
    } catch (error) {
      throw new APIError(error, state)
    } finally {
      yield browser.close()
    }
  }

  return {
    getPageContent,
  }
}

1 个答案:

答案 0 :(得分:0)

您可以将waitForFunctionwaitForevaluatePromise.all一起使用。无论网站的动态程度如何,您都在等待最后的结果,然后在发生这种情况时关闭浏览器。

由于我无权访问您的动态网址,因此我将使用一些随机变量和延迟作为示例。变量返回真值后,它将解决。

await page.waitForFunction((()=>!!someVariableThatShouldBeTrue);

在评估代码后,如果动态页面实际上在某处创建了选择器?在这种情况下,

await page.waitFor('someSelector')

现在回到您的customCode,让我为您重新命名,

await page.evaluate(customCode)

其中的customCode是将变量someVariableThatShouldBeTrue设置为true的地方。坦白地说,它可以是任何内容,请求,字符串或其他任何内容。可能性是无限的。

您可以在page.evaluate 中放一个承诺,最近的铬对它们的支持很好。因此,以下内容也将起作用,一旦加载函数/数据,请解决。确保customCode是异步函数或返回promise。

const pageContent = await page.evaluate(CustomCode);

好的,现在我们已经准备好了所有必需品。我对代码进行了一些修改,以免对我:D,

module.exports = function buildClient(headlessBrowserClient) {
  return {
    getPageContent: async (url, CustomCode) => {
      const state = {
        req: { url },
      };
      // so that we can call them on "finally" block
      let browser, page;
      try {
        // launch browser
        browser = await headlessBrowserClient.launch()
        page = await browser.newPage()
        await page.goto(url)

        // evaluate and wait for something to happen
        // first element returns the pageContent, but whole will resolve if both ends truthy
        const [pageContent] = await Promise.all([
          await page.evaluate(CustomCode),
          await page.waitForFunction((() => !!someVariableThatShouldBeTrue))
        ])

        // Or, You realize you can put a promise inside page.evaluate, recent chromium supports them very well
        // const pageContent = await page.evaluate(CustomCode)

        return pageContent;
      } catch (error) {
        throw new APIError(error, state)
      } finally {
        // NOTE: Maybe we can move them on a different function
        await page.close()
        await browser.close()
      }
    }
  }
}

您可以根据需要进行更改和调整。我没有测试最终代码(因为我没有APIError,evaluateCustomCode等),但是它应该可以工作。

它没有所有这些生成器和类似的东西。 承诺,这就是处理动态页面的方法:D。

PS:IMO,这样的问题更适合the code review