【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));
  });
})();
相关推荐
autotian2 天前
2.8 群辉 黑群晖 意味断电 抱歉,您所指定的页面不存在。
报错·群辉·页面不存在
YONG823_API6 天前
高并发的API请求有哪些注意事项?
大数据·前端·数据库·人工智能·网络爬虫
大风吹PP凉8 天前
38配置管理工具(如Ansible、Puppet、Chef)
linux·运维·服务器·ansible·puppet
编程咕咕gu-11 天前
使用Python爬虫技术爬取飞卢小说内容
开发语言·爬虫·python·网络爬虫·python知识点
苏三有春14 天前
PyQt5实战——翻译的实现,成功爬取微软翻译(可长期使用)经验总结(九)
python·microsoft·网络爬虫
龙哥说跨境15 天前
如何利用指纹浏览器爬虫绕过Cloudflare的防护?
服务器·网络·python·网络爬虫
墨城烟柳Q17 天前
python爬取m3u8视频(思路到实现全讲解!!!)
爬虫·python·网络爬虫
捉鸭子20 天前
某海关征信瑞数6vmp算法还原&数据解密
前端·javascript·爬虫·python·web安全·网络爬虫
我就说好玩21 天前
使用Scrapy框架爬取博客信息
scrapy·pycharm·网络爬虫·spider