【nest】puppeteer 使用 addScriptTag 在页面中添加方法的方式

1.js方法支持(已测试)

1.1 先支持js

tsconfig.json

"compilerOptions": {
    "allowJs": true,
    ...
    }

1.2 需要将抽离的方法放在js中封装

src/utils/utils.js

export function concatLabel(tagList)     {
    let labels = [];
    for (let i = 1; true; i++) {
        let li = tagList.querySelector(`li:nth-child(${i})`);
        if (li) {
            labels.push(li.textContent.trim());
        } else {
            break; // 当没有更多的 li 元素时,结束循环
        }
    }
    return labels.join('@');
}

1.3 service中使用该方法

import {concatLabel} from "./utils/utils.js";

// 将该方法添加为script,因为如果不使用js的话,在页面执行的时候可能会报错
await page.addScriptTag({
                        content: concatLabel.toString()
                    });
 //使用 await page.$eval('执行页面代码
 const jobs = await page.$eval('.job-list-box', (el, methodName) => {
                        return [...el.querySelectorAll('.job-card-wrapper')].map(item => {
                            let tagList = item.querySelector('.company-tag-list');
                            console.log("=========================")
                            // 需要添加注解 @ts-ignore 否则编译无法通过
                            // @ts-ignore
                            // 由于该方法已注入页面,因此使用this即可调用
                            let xxxxx = this.concatLabel(tagList);

2.引入 webpack 和 babel 的编译机制(未测试,记录下)

来源: https://www.xiday.com/2019/09/21/puppeteer-run-js/

有时我们可能需要在 Puppeteer 环境中执行一段 JS 代码。

根据官方提供的 API,我们有两种选择,

2.1 一种是添加 script 标签的方式引入 JS。

page.addScriptTag(options)

options <[Object]>
url <[string]> URL of a script to be added.
path <[string]> Path to the JavaScript file to be injected into frame. If path is a relative path, then it is resolved relative to [current working directory].
content <[string]> Raw JavaScript content to be injected into frame.
type <[string]> Script type. Use 'module' in order to load a Javascript ES6 module. See script for more details.
returns: <[Promise]<[ElementHandle]>> which resolves to the added tag when the script's onload fires or when the script content was injected into frame.

2.2 另一种是使用page.evaluate

const result = await page.evaluate(x => {
  return Promise.resolve(8 * x);
}, 7);
console.log(result); // prints "56"

2.3 说明及遇到的问题

page.addScriptTag虽然可以引用本地文件作为 JS 执行,但是模块系统(ES6 Module 和 CommonJS 等)支持并不完善,部分 ES6 代码不支持 (最新 Chrome 可忽略)。
page.evaluate中的代码相当于在 DevTools 的控制台执行的,同样也有模块系统和 ES6 的问题,只是函数传值比page.addScriptTag方便。
所以我认为最好的解决方式是引入 webpack 和 babel 的编译机制。
具体方案是使用 Webpack Node API 配合 memory-fs 将要执行的 JS 文件作为入口,将编译结果输出为字符串,再通过page.evaluate执行。

2.4 具体方案

2.4.1 安装相关依赖

yarn add webpack memory-fs @babel/core @babel/preset-env babel-loader

2.4.2 编写一个buildModule函数来将指定文件作为入口,将相关模块依赖打包为 JS Bundle 字符串。

const webpack = require('webpack');
const MemoryFS = require('memory-fs');

const buildModule = file => {
  const compiler = webpack({
    mode: 'development',
    devtool: 'cheap-module-eval-source-map',
    entry: require.resolve(file),
    output: {
      filename: 'bundle.js',
      path: '/build',
    },
    module: {
      rules: [
        {
          test: /\.m?js$/,
          exclude: /(node_modules|bower_components)/,
          use: {
            loader: 'babel-loader',
          },
        },
      ],
    },
  });
  const fs = new MemoryFS();
  compiler.outputFileSystem = fs;
  return new Promise((resolve, reject) => {
    compiler.run(error => {
      if (error) {
        reject(error);
        return;
      }
      const content = fs.readFileSync('/build/bundle.js');
      resolve(content.toString());
    });
  });
};

2.4.3在根目录新建babel.config.js来指定打包时的 babel 配置,当然也可以复用项目已有的配置。

babel.config.js

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        modules: false,
        useBuiltIns: 'entry',
        corejs: 3,
        targets: {
          chrome: 70,
        },
      },
    ],
  ],
};

最后,我们准备需要执行的 JS 入口文件,如果需要执行某些函数,可以将相关模块暴露到 window 对象,供 Puppeteer 使用。在这里我们将自己写的 sum 函数暴露到 window 上。

browser_run.js

import add from 'lodash/add';

const sum = (...param) => [...param].reduce((a, b) => add(a, b), 0);

window.sum = sum;

最终在 Puppeteer 中调用buildModule函数,传入入口文件路径,经由 webpack 打包和 babel 编译,最后通过page.evaluate函数执行。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  const scriptStr = await buildModule('./browser_run.js');
  await page.evaluate(scriptStr);
  await page.evaluate(() => {
    console.log('sum:', window.sum(1, 2, 3, 4, 5));
  });
})();
相关推荐
亿牛云爬虫专家18 小时前
高效使用 Guzzle:POST 请求与请求体参数的最佳实践
网络爬虫·php·爬虫代理·异步·post·代理ip·guzzle
Python大数据分析@19 小时前
使用requests爬取拉勾网python职位数据
开发语言·python·网络爬虫
无极低码1 天前
用网上抓取的天气的接口做了一个系统
java·前端·数据库·网络爬虫·echarts·天气·数据资源
Python之栈2 天前
Python爬虫实战案例——王者荣耀皮肤抓取
python·网络爬虫
懂电商API接口的Jennifer3 天前
经典案列|淘宝商品数据爬取与分析
java·开发语言·前端·数据库·爬虫·网络爬虫
huakej_4 天前
如何循环遍历循环中的剩余元素
开发语言·python·网络爬虫
blues_C6 天前
Python爬虫技术与反爬虫策略
爬虫·python·网络爬虫·反爬虫策略
懂电商API接口的Jennifer7 天前
爬取电商商品详情数据的经验分享(数据已封装API可调用)
数据库·爬虫·网络爬虫
程序猿阿伟7 天前
Puppet 在大规模分布式系统中的性能优化策略有哪些?
性能优化·puppet
懂电商API接口的Jennifer8 天前
探索网络爬虫技术:原理、实践与挑战
运维·爬虫·自动化·网络爬虫