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));
});
})();