-
Notifications
You must be signed in to change notification settings - Fork 283
Expand file tree
/
Copy pathheadless-util.js
More file actions
342 lines (315 loc) · 11.3 KB
/
headless-util.js
File metadata and controls
342 lines (315 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/**
* Puppeteer无头浏览器工具类
*
* 功能描述:
* 这是一个基于Puppeteer的无头浏览器操作工具类,提供了网页自动化操作的封装。
* 支持页面访问、内容获取、元素操作、Cookie管理、PDF生成等功能。
*
* 主要功能:
* 1. 页面访问 - 支持普通页面访问和API请求
* 2. 内容获取 - 获取页面HTML内容、标题、URL等
* 3. 元素操作 - 点击、输入、获取文本和属性
* 4. Cookie管理 - 获取页面Cookie信息
* 5. 等待机制 - 等待元素出现
* 6. PDF生成 - 将页面保存为PDF
* 7. 自定义脚本 - 在页面上下文中执行JavaScript
* 8. 代理支持 - 支持HTTP代理配置
*
* 使用场景:
* - 网页数据抓取
* - 自动化测试
* - 页面截图和PDF生成
* - 模拟用户操作
* - 绕过反爬虫机制
*
* 环境变量:
* - CHROME_PATH: Chrome浏览器可执行文件路径
*
* @author drpy-node
* @version 1.0.0
*/
import * as puppeteer from 'puppeteer'
/**
* Puppeteer助手类
* 封装了Puppeteer的常用操作,提供简化的API接口
*/
class PuppeteerHelper {
/**
* 构造函数
* 初始化浏览器和页面实例为null
*/
constructor() {
this.browser = null; // 浏览器实例
this.page = null; // 页面实例
}
/**
* 访问网页或发送HTTP请求
* 支持普通页面访问和API请求两种模式
*
* @param {Object} config - 配置对象
* @param {string} config.url - 目标URL
* @param {boolean} [config.headless=true] - 是否无头模式
* @param {boolean} [config.proxy=false] - 是否使用代理
* @param {Object} [config.headers] - 请求头
* @param {boolean} [config.isJSON=false] - 是否为JSON请求
* @param {boolean} [config.isReq=false] - 是否为API请求
* @param {string} [config.method='GET'] - HTTP方法
* @param {Object} [config.data] - 请求数据
* @param {Object} [config.options] - 页面访问选项
* @returns {Promise<string>} 页面内容或响应文本
*
* @example
* // 访问普通页面
* const html = await helper.gotoHtml({url: 'https://example.com'});
*
* // 发送API请求
* const data = await helper.gotoHtml({
* url: 'https://api.example.com/data',
* isJSON: true,
* method: 'POST',
* data: {key: 'value'}
* });
*/
async gotoHtml(config) {
let proxy = ''
// 如果配置了代理,使用本地代理服务器
if(config.proxy){
proxy = 'http://127.0.0.1:7897';
}
// 启动浏览器实例
this.browser = await puppeteer.launch({
headless: config.headless || true, // 无头模式,默认true
// executablePath: process.env.CHROME_PATH, // Chrome路径(可选)
args: [`--proxy-server=${proxy}`] // 代理服务器配置
});
// 创建新页面
this.page = await this.browser.newPage();
// 设置请求头
await this.page.setExtraHTTPHeaders(config.headers || {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36',
})
let content = ''
// 判断是否为API请求模式
if (config.isJSON || config.isReq) {
// 在页面上下文中执行fetch请求
return await this.page.evaluate(async (url, mth, headers, data) => {
// 构建请求选项
const requestOptions = {
method: mth || 'GET',
headers: headers || {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36',
'Content-Type': 'application/json'
},
};
// 如果不是GET请求且有数据,添加请求体
if (mth !== 'GET' && (data !== '' && data !== undefined)) {
requestOptions.body = JSON.stringify(data);
}
// 发送请求
const response = await fetch(url, requestOptions);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.text();
}, config.url, config.method, config.headers, config.data);
} else {
// 普通页面访问模式
await this.page.goto(config.url, config.options);
content = await this.page.content()
return content
}
}
/**
* 获取页面Cookie信息
* 访问指定URL并获取该页面的所有Cookie
*
* @param {Object} config - 配置对象
* @param {string} config.url - 目标URL
* @param {boolean} [config.headless=true] - 是否无头模式
* @param {Object} [config.options] - 页面访问选项
* @returns {Promise<Array>} Cookie数组
*
* @example
* const cookies = await helper.gotoCookie({url: 'https://example.com'});
*/
async gotoCookie(config) {
// 启动浏览器(使用环境变量中的Chrome路径)
this.browser = await puppeteer.launch({
headless: config.headless || true,
executablePath: process.env.CHROME_PATH
});
this.page = await this.browser.newPage();
await this.page.goto(config.url, config.options || {});
// 获取当前页面的cookies
const cookies = await this.page.cookies();
// 打印cookies(调试用,已注释)
// log('Cookies:', cookies);
return cookies || []
}
/**
* 在指定元素中输入文本
*
* @param {string} selector - CSS选择器
* @param {string} text - 要输入的文本
* @param {Object} [options={}] - 输入选项(如延迟等)
* @returns {Promise<void>}
*
* @example
* await helper.type('#username', 'myuser', {delay: 100});
*/
async type(selector, text, options = {}) {
return await this.page.type(selector, text, ...options);//{ delay: 100 }
}
/**
* 点击指定元素
*
* @param {string} selector - CSS选择器
* @returns {Promise<void>}
*
* @example
* await helper.gotoclick('#submit-button');
*/
async gotoclick(selector) {
return await this.page.click(selector);
}
/**
* 生成PDF文件
* 将当前页面保存为PDF格式
*
* @param {string} path - PDF文件保存路径
* @param {Object} [options={}] - PDF生成选项
* @returns {Promise<void>}
*
* @example
* await helper.pdf('./output.pdf', {format: 'A4'});
*/
async pdf(path, options = {}) {
return await this.page.pdf({path, ...options});
}
/**
* 获取页面标题
*
* @returns {Promise<string>} 页面标题
*
* @example
* const title = await helper.getTitle();
*/
async getTitle() {
return await this.page.title();
}
/**
* 获取当前页面URL
*
* @returns {Promise<string>} 当前页面URL
*
* @example
* const url = await helper.getUrl();
*/
async getUrl() {
return await this.page.url();
}
/**
* 在页面上下文中执行自定义JavaScript函数
* 将函数序列化后在浏览器环境中执行
*
* @param {Function} fn - 要执行的函数
* @param {...any} args - 函数参数
* @returns {Promise<any>} 函数执行结果
*
* @example
* const result = await helper.evaluate(() => {
* return document.title;
* });
*/
async evaluate(fn, ...args) {
const funcString = fn.toString();// 将函数转换为字符串
// 构造一个在页面上下文中调用的函数,该函数调用传入的自定义函数
const pageFunction = new Function(`return (${funcString})`).apply(null, args.map(arg => JSON.stringify(arg)));
return await this.page.evaluate(pageFunction);
}
/**
* 获取页面元素的文本内容
*
* @param {string} selector - CSS选择器
* @returns {Promise<string|null>} 元素文本内容,如果元素不存在返回null
*
* @example
* const text = await helper.gotoText('.title');
*/
async gotoText(selector) {
return await this.page.evaluate((selector) => {
const element = document.querySelector(selector);
return element ? element.textContent : null;
}, selector);
}
/**
* 获取页面元素的属性值
*
* @param {string} selector - CSS选择器
* @param {string} attribute - 属性名
* @returns {Promise<string|null>} 属性值,如果元素不存在或属性不存在返回null
*
* @example
* const href = await helper.gotoAttribute('a.link', 'href');
*/
async gotoAttribute(selector, attribute) {
return await this.page.evaluate((selector, attribute) => {
const element = document.querySelector(selector);
return element ? element.getAttribute(attribute) : null;
}, selector, attribute);
}
/**
* 等待指定选择器的元素出现
* 提供超时机制和回调处理
*
* @param {string} selector - CSS选择器
* @param {Object} [options={}] - 等待选项
* @param {number} [options.timeout=30000] - 超时时间(毫秒),默认30秒
* @param {Function} [options.onFound] - 元素找到后的回调函数
* @returns {Promise<ElementHandle>} 元素句柄
* @throws {Error} 如果超时或元素未找到
*
* @example
* const element = await helper.waitForSelector('.dynamic-content', {
* timeout: 10000,
* onFound: (el) => console.log('Element found!')
* });
*/
async waitForSelector(selector, options = {}) {
const {timeout = 30000, onFound} = options; // 默认超时为30秒
try {
// 使用 Page 的 waitForSelector 方法,并设置超时
const elementHandle = await this.page.waitForSelector(selector, {timeout});
// 如果提供了 onFound 回调,则调用它并传入元素句柄
if (onFound) {
await onFound(elementHandle);
}
// 返回元素句柄(如果需要的话,调用者可以进一步处理它)
return elementHandle;
} catch (error) {
// 如果超时或选择器未找到,则抛出错误
throw new Error(`Element with selector "${selector}" not found within ${timeout / 1000} seconds.`);
}
}
/**
* 关闭浏览器和页面
* 清理资源,释放内存
*
* @returns {Promise<void>}
*
* @example
* await helper.close();
*/
async close() {
// 关闭页面
if (this.page) {
await this.page.close();
}
// 关闭浏览器
if (this.browser) {
await this.browser.close();
}
}
}
// 导出PuppeteerHelper类
export const puppeteerHelper = PuppeteerHelper