nodejs_chromium 1.1.13 → 1.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +14 -5
- package/package.json +1 -1
- package/src/chrome.js +192 -62
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require('nodejs_patch');
|
|
2
2
|
const puppeteer = require("puppeteer");
|
|
3
|
+
const { KnownDevices } = require("puppeteer");
|
|
3
4
|
const chrome = require("./src/chrome.js");
|
|
4
5
|
const { exec } = require("child_process");
|
|
5
6
|
global.__UA__ = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36';
|
|
@@ -16,13 +17,17 @@ async function NormalClose(path) {
|
|
|
16
17
|
*/
|
|
17
18
|
let file = `${path}/Default/Preferences`;
|
|
18
19
|
// console.log({file});
|
|
19
|
-
if (!fs.existsSync(file))
|
|
20
|
+
if (!fs.existsSync(file)) {
|
|
21
|
+
console.log(file, 'NotExists')
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
20
24
|
let text = fs.readFileSync(file);
|
|
21
25
|
let json = JSON.parse(text);
|
|
22
26
|
json.credentials_enable_service = false; //新增项,禁止保存密码
|
|
23
27
|
json.profile.exit_type = 'Normal'; //原值可能为:Crashed
|
|
24
28
|
// console.log('json.profile.exit_type', json.profile.exit_type);
|
|
25
29
|
fs.writeFileSync(file, JSON.stringify(json));
|
|
30
|
+
console.log(file, json.profile.exit_type)
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
|
|
@@ -99,7 +104,7 @@ async function newBrowser(params) {
|
|
|
99
104
|
|
|
100
105
|
const browser = await puppeteer.launch(option);
|
|
101
106
|
|
|
102
|
-
await NormalClose(option.userDataDir); //禁止显示【要恢复页面吗?chromium 未正确关闭。】
|
|
107
|
+
// await NormalClose(option.userDataDir); //禁止显示【要恢复页面吗?chromium 未正确关闭。】
|
|
103
108
|
|
|
104
109
|
return browser;
|
|
105
110
|
}
|
|
@@ -126,6 +131,7 @@ async function newChrome(params) {
|
|
|
126
131
|
cookies = null, //若cookies=false,则不处理cookies,不指定则由chrome处理,若=文件路径
|
|
127
132
|
abort = [], //要禁止的类型,常见["font","image","ping","stylesheet","document","fetch","script","xhr"]
|
|
128
133
|
headers = {},
|
|
134
|
+
device = null
|
|
129
135
|
} = params;
|
|
130
136
|
|
|
131
137
|
const browser = await newBrowser(params);
|
|
@@ -136,11 +142,14 @@ async function newChrome(params) {
|
|
|
136
142
|
// const version = await browser.version();
|
|
137
143
|
const page = (await browser.pages())[0];
|
|
138
144
|
// const page = await browser.newPage();
|
|
139
|
-
await page.setRequestInterception(true); //允许拦截
|
|
145
|
+
// await page.setRequestInterception(true); //允许拦截
|
|
140
146
|
|
|
141
147
|
if (ua) await page.setUserAgent(ua);
|
|
142
|
-
else if (mobile)
|
|
148
|
+
else if (mobile) {
|
|
149
|
+
await page.emulate(device || KnownDevices['iPhone 15 Pro']);
|
|
150
|
+
}
|
|
143
151
|
else await page.setUserAgent(__UA__);
|
|
152
|
+
if (device) await page.emulate(device); //必须要放在setUserAgent后面
|
|
144
153
|
|
|
145
154
|
await page.evaluateOnNewDocument(() => {
|
|
146
155
|
const newProto = navigator.__proto__;
|
|
@@ -149,7 +158,7 @@ async function newChrome(params) {
|
|
|
149
158
|
});
|
|
150
159
|
|
|
151
160
|
if (cookies !== false) cookies = `runtime/.cache/${id}/cookies.json`;
|
|
152
|
-
const pageOption = { cookies, visible, abort, headers };
|
|
161
|
+
const pageOption = { cookies, visible, abort, headers, device };
|
|
153
162
|
return new chrome(browser, page, pageOption);
|
|
154
163
|
}
|
|
155
164
|
catch (err) {
|
package/package.json
CHANGED
package/src/chrome.js
CHANGED
|
@@ -2,7 +2,6 @@ const fs = require("fs");
|
|
|
2
2
|
const Cookies = require("./cookies");
|
|
3
3
|
const { URL } = require('url');
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
/**
|
|
7
6
|
*
|
|
8
7
|
* class CdpFrame extends _classSuper
|
|
@@ -19,8 +18,10 @@ module.exports = class {
|
|
|
19
18
|
requestCall = void 0;
|
|
20
19
|
cookies = void 0;
|
|
21
20
|
options = void 0;
|
|
21
|
+
device = void 0;
|
|
22
22
|
isFrame = false; //是不是在iFrame中
|
|
23
23
|
visible = false; //是否可见,也就是有没有启动窗口
|
|
24
|
+
resIndex = 0; //当前窗口经过response的序号
|
|
24
25
|
|
|
25
26
|
constructor(browser, page, options, isFrame = false) {
|
|
26
27
|
this.browser = browser;
|
|
@@ -28,28 +29,78 @@ module.exports = class {
|
|
|
28
29
|
this.options = options;
|
|
29
30
|
this.cookies = new Cookies(page, options.cookies);
|
|
30
31
|
this.visible = !!options.visible;
|
|
32
|
+
this.device = options.device;
|
|
31
33
|
this.isFrame = !!isFrame;
|
|
32
34
|
this.doListening(options);
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
/**
|
|
38
|
+
* 设置拦截
|
|
39
|
+
* 如果只是要读取cookies这些信息而不拦截处理,只需调用interception
|
|
40
|
+
*
|
|
41
|
+
* @param call
|
|
42
|
+
* @returns {Promise<exports>}
|
|
43
|
+
*/
|
|
44
|
+
async request(call) {
|
|
45
|
+
|
|
46
|
+
if (call === true) {
|
|
47
|
+
//等同于this.interception(true)
|
|
48
|
+
await this.page.setRequestInterception(true); //允许拦截
|
|
49
|
+
this.page.interception = true;
|
|
50
|
+
this.requestCall = null;
|
|
51
|
+
return this;
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
else if (call === false) {
|
|
55
|
+
//等同于this.interception(false)
|
|
56
|
+
await this.page.setRequestInterception(false); //关闭允许拦截
|
|
57
|
+
this.page.interception = false;
|
|
58
|
+
this.requestCall = null;
|
|
59
|
+
return this;
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await this.page.setRequestInterception(true); //允许拦截
|
|
64
|
+
this.page.interception = true;
|
|
36
65
|
this.requestCall = call;
|
|
37
66
|
return this;
|
|
38
67
|
}
|
|
39
68
|
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 设置是否允许拦截
|
|
72
|
+
*
|
|
73
|
+
* @param val
|
|
74
|
+
* @returns {Promise<exports>}
|
|
75
|
+
*/
|
|
76
|
+
async interception(val = true) {
|
|
77
|
+
await this.page.setRequestInterception(val);
|
|
78
|
+
this.page.interception = val;
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
40
83
|
response(call) {
|
|
41
84
|
this.responseCall = call;
|
|
42
85
|
return this;
|
|
43
86
|
}
|
|
44
87
|
|
|
88
|
+
newPage(device = null) {
|
|
89
|
+
return this.clone(device);
|
|
90
|
+
}
|
|
91
|
+
|
|
45
92
|
/**
|
|
46
93
|
* 重新在当前浏览器创建新窗口
|
|
47
94
|
*/
|
|
48
|
-
async clone() {
|
|
95
|
+
async clone(device = null) {
|
|
49
96
|
try {
|
|
50
97
|
const page = await this.browser.newPage();
|
|
51
98
|
await page.setBypassCSP(true); //绕过页面的内容安全策略
|
|
52
|
-
await page.setRequestInterception(true); //允许拦截
|
|
99
|
+
// await page.setRequestInterception(true); //允许拦截
|
|
100
|
+
|
|
101
|
+
if (device) await page.emulate(device);
|
|
102
|
+
else if (this.options.device) await page.emulate(this.options.device);
|
|
103
|
+
|
|
53
104
|
await page.evaluateOnNewDocument(() => {
|
|
54
105
|
const newProto = navigator.__proto__;
|
|
55
106
|
delete newProto.webdriver; //删除 navigator.webdriver字段
|
|
@@ -108,10 +159,10 @@ module.exports = class {
|
|
|
108
159
|
/**
|
|
109
160
|
* 这必须放在已经打开过这个URL之后
|
|
110
161
|
*/
|
|
111
|
-
waitForFrame() {
|
|
162
|
+
waitForFrame(name = 'Test') {
|
|
112
163
|
try {
|
|
113
164
|
this.page.waitForFrame(async frame => {
|
|
114
|
-
return frame.name() ===
|
|
165
|
+
return await frame.name() === name;
|
|
115
166
|
});
|
|
116
167
|
return this;
|
|
117
168
|
}
|
|
@@ -127,7 +178,7 @@ module.exports = class {
|
|
|
127
178
|
async waitFrame(urlKey, timeout = 5000) {
|
|
128
179
|
try {
|
|
129
180
|
return await this.page.waitForFrame(async frame => {
|
|
130
|
-
return frame.url().
|
|
181
|
+
return frame.url().includes(urlKey);
|
|
131
182
|
}, { timeout });
|
|
132
183
|
}
|
|
133
184
|
catch (e) {
|
|
@@ -137,6 +188,7 @@ module.exports = class {
|
|
|
137
188
|
|
|
138
189
|
/**
|
|
139
190
|
* 按tag创建当前窗口中的某个iFrame
|
|
191
|
+
*
|
|
140
192
|
* @param {Object} tag
|
|
141
193
|
*/
|
|
142
194
|
async iframe(tag) {
|
|
@@ -245,6 +297,21 @@ module.exports = class {
|
|
|
245
297
|
}
|
|
246
298
|
}
|
|
247
299
|
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 获取部分或全部HTML
|
|
303
|
+
* @param {Object} obj
|
|
304
|
+
*/
|
|
305
|
+
async evaluate(obj, callback) {
|
|
306
|
+
try {
|
|
307
|
+
return await this.page.evaluate(callback, obj);
|
|
308
|
+
}
|
|
309
|
+
catch (e) {
|
|
310
|
+
console.log('[chrome.evaluate.Error]', e.message);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
|
|
248
315
|
/**
|
|
249
316
|
* 获取部分或全部HTML
|
|
250
317
|
* @param {Object} obj
|
|
@@ -291,7 +358,7 @@ module.exports = class {
|
|
|
291
358
|
return this;
|
|
292
359
|
}
|
|
293
360
|
catch (e) {
|
|
294
|
-
console.log('[chrome.
|
|
361
|
+
console.log('[chrome.display.Error]', [tag, e.message]);
|
|
295
362
|
}
|
|
296
363
|
}
|
|
297
364
|
|
|
@@ -305,22 +372,56 @@ module.exports = class {
|
|
|
305
372
|
*/
|
|
306
373
|
async elements(tag1, tag2, call) {
|
|
307
374
|
try {
|
|
308
|
-
|
|
309
|
-
if (!
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
for (const
|
|
313
|
-
const html = await
|
|
314
|
-
const text = await
|
|
315
|
-
call(
|
|
375
|
+
let has = await this.wait(tag, 2, 2);
|
|
376
|
+
if (!has) return null;
|
|
377
|
+
|
|
378
|
+
let index = 0;
|
|
379
|
+
for (const ele of (await div.$$(tag2))) {
|
|
380
|
+
const html = await ele.evaluate(node => node.innerHTML);
|
|
381
|
+
const text = await ele.evaluate(node => node.innerText);
|
|
382
|
+
await call(ele, { html, text, index: index++ });
|
|
316
383
|
}
|
|
384
|
+
|
|
317
385
|
}
|
|
318
386
|
catch (e) {
|
|
319
|
-
console.log('[chrome.elements.Error]', e.message)
|
|
387
|
+
console.log('[chrome.elements.Error]', [tag1, tag2, e.message])
|
|
320
388
|
}
|
|
321
389
|
}
|
|
322
390
|
|
|
323
391
|
|
|
392
|
+
/**
|
|
393
|
+
* 遍历tag1里的tag2,并由call判断,只有call返回true时才resolve
|
|
394
|
+
*
|
|
395
|
+
* @param {Object} tag1
|
|
396
|
+
* @param {Object} tag2
|
|
397
|
+
* @param {Object} call
|
|
398
|
+
*/
|
|
399
|
+
async search(tag1, tag2, call) {
|
|
400
|
+
return await new Promise(async (resolve) => {
|
|
401
|
+
try {
|
|
402
|
+
let has = await this.wait(tag, 2, 2);
|
|
403
|
+
if (!has) return {};
|
|
404
|
+
|
|
405
|
+
let index = 0;
|
|
406
|
+
for (const ele of (await div.$$(tag2))) {
|
|
407
|
+
index++;
|
|
408
|
+
const html = await ele.evaluate(node => node.innerHTML);
|
|
409
|
+
const text = await ele.evaluate(node => node.innerText);
|
|
410
|
+
let val = await call(ele, { html, text, index });
|
|
411
|
+
if (val === true) return resolve({ success: true, ele, html, text, index });
|
|
412
|
+
}
|
|
413
|
+
resolve({ success: false, index });
|
|
414
|
+
|
|
415
|
+
}
|
|
416
|
+
catch (e) {
|
|
417
|
+
console.log('[chrome.search.Error]', [tag1, tag2, e.message])
|
|
418
|
+
return {};
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
|
|
324
425
|
/**
|
|
325
426
|
* 支持css普通选择器方式和伪类方式
|
|
326
427
|
* div.body
|
|
@@ -336,11 +437,14 @@ module.exports = class {
|
|
|
336
437
|
*/
|
|
337
438
|
async element(tag, selector = false) {
|
|
338
439
|
try {
|
|
440
|
+
let has = await this.wait(tag, 2, 2);
|
|
441
|
+
if (!has) return null;
|
|
442
|
+
|
|
339
443
|
if (selector) return await this.page.querySelector(tag);
|
|
340
444
|
return await this.page.$(tag);
|
|
341
445
|
}
|
|
342
446
|
catch (e) {
|
|
343
|
-
console.log('[chrome.element.Error]', e.message);
|
|
447
|
+
console.log('[chrome.element.Error]', [tag, e.message]);
|
|
344
448
|
return null;
|
|
345
449
|
}
|
|
346
450
|
}
|
|
@@ -400,9 +504,6 @@ module.exports = class {
|
|
|
400
504
|
}
|
|
401
505
|
}
|
|
402
506
|
|
|
403
|
-
/**
|
|
404
|
-
* delay=每键入一个字符延迟毫秒
|
|
405
|
-
*/
|
|
406
507
|
async clear(el) {
|
|
407
508
|
try {
|
|
408
509
|
await this.page.$eval(el, ele => ele.value = '');
|
|
@@ -443,12 +544,12 @@ module.exports = class {
|
|
|
443
544
|
*/
|
|
444
545
|
async click(el, option = {}) {
|
|
445
546
|
try {
|
|
547
|
+
|
|
446
548
|
let { delay = 100, count = 1, x = 6, y = 3 } = option;
|
|
447
|
-
await this.page.click(el, { delay, count, offset: { x, y } });
|
|
448
|
-
return this;
|
|
549
|
+
return await this.page.click(el, { delay, count, offset: { x, y } });
|
|
449
550
|
}
|
|
450
551
|
catch (e) {
|
|
451
|
-
console.log('[chrome.click.Error]', e.message);
|
|
552
|
+
console.log('[chrome.click.Error]', el, e.message);
|
|
452
553
|
}
|
|
453
554
|
}
|
|
454
555
|
|
|
@@ -596,65 +697,91 @@ module.exports = class {
|
|
|
596
697
|
}
|
|
597
698
|
|
|
598
699
|
skipTypes = ['image', 'font', 'other', 'script', 'stylesheet', 'document', 'ping'];
|
|
700
|
+
|
|
599
701
|
setSkipType(items) {
|
|
600
702
|
this.skipTypes.length = 0;
|
|
601
703
|
this.skipTypes = [...items];
|
|
602
704
|
return this;
|
|
603
705
|
}
|
|
604
706
|
|
|
605
|
-
delSkipType(
|
|
707
|
+
delSkipType(item) {
|
|
606
708
|
this.skipTypes = this.skipTypes.del(...item);
|
|
607
709
|
return this;
|
|
608
710
|
}
|
|
609
711
|
|
|
712
|
+
|
|
610
713
|
async parseResponse(response) {
|
|
611
714
|
// const response = await this.page.waitForResponse(res => res);
|
|
612
|
-
|
|
613
715
|
const value = {};
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
if (value.
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
716
|
+
|
|
717
|
+
try {
|
|
718
|
+
|
|
719
|
+
const request = await response.request();
|
|
720
|
+
const headers = await response.headers();
|
|
721
|
+
|
|
722
|
+
value.method = await request.method();
|
|
723
|
+
if (value.method === 'OPTIONS') return;
|
|
724
|
+
// if (value.method === 'DELETE') return;
|
|
725
|
+
value.index = this.resIndex++
|
|
726
|
+
|
|
727
|
+
value.type = await request.resourceType();
|
|
728
|
+
// value.redirect = await response.redirectURL();
|
|
729
|
+
if (value.type === 'fetch') value.type = 'xhr';
|
|
730
|
+
if (value.type === 'xhr') value.type = 'AJAX';
|
|
731
|
+
value.url = await response.url();
|
|
732
|
+
value.headers = { request: request.headers(), response: headers };
|
|
733
|
+
value.domain = (new URL(value.url))['host'];
|
|
734
|
+
value.content = headers['content-type'];
|
|
735
|
+
value.length = headers['content-length'];
|
|
736
|
+
value.status = await response.status();
|
|
737
|
+
value.ok = await response.ok();
|
|
738
|
+
value.datetime = (new Date(headers['date'])).date('yyyy-mm-dd hh:ii:ss');
|
|
739
|
+
if (headers['server']) value.server = headers['server'];
|
|
740
|
+
if (headers['set-cookie']) value.cookies = await this.cookies.parse(headers['set-cookie']);
|
|
741
|
+
value.remote = await response.remoteAddress(); //目标服务器
|
|
742
|
+
if (value.status === 301 || value.status === 302) return value;
|
|
743
|
+
if (this.skipTypes.includes(value.type)) return value;
|
|
744
|
+
|
|
745
|
+
value.post = await request.postData();
|
|
746
|
+
if (value.post) value.post = value.post.toString();
|
|
747
|
+
|
|
748
|
+
}
|
|
749
|
+
catch (e) {
|
|
750
|
+
const err = e.parse();
|
|
751
|
+
value.error = err.message;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
try {
|
|
755
|
+
|
|
756
|
+
if (value.content.includes('text/html')) {
|
|
757
|
+
const buffer = await response.buffer();
|
|
758
|
+
value.html = buffer.toString();
|
|
759
|
+
return value;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (value.content) {
|
|
763
|
+
if (value.content.includes('application/vnd')) return value;
|
|
764
|
+
if (value.content.includes('application/xml')) return value;
|
|
765
|
+
if (value.content.includes('application/javascript')) return value;
|
|
766
|
+
if (value.content.includes('text/css')) return value;
|
|
767
|
+
if (value.content.includes('text/plain')) return value;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
}
|
|
771
|
+
catch (e) {
|
|
772
|
+
const err = e.parse();
|
|
773
|
+
value.error = err.message;
|
|
774
|
+
}
|
|
649
775
|
|
|
650
776
|
try {
|
|
651
777
|
const buffer = await response.buffer();
|
|
652
778
|
value.response = buffer.toString();
|
|
779
|
+
if (!/^[\[\{].+[\]\}]$/.test(value.response)) return value;
|
|
653
780
|
value.json = JSON.parse(value.response);
|
|
654
781
|
}
|
|
655
782
|
catch (e) {
|
|
656
783
|
const err = e.parse();
|
|
657
|
-
value.
|
|
784
|
+
value.error = err.message;
|
|
658
785
|
}
|
|
659
786
|
|
|
660
787
|
return value;
|
|
@@ -669,6 +796,8 @@ module.exports = class {
|
|
|
669
796
|
async doListening(options) {
|
|
670
797
|
|
|
671
798
|
this.page.on('request', async (request) => {
|
|
799
|
+
if (!this.page.interception) return;
|
|
800
|
+
|
|
672
801
|
const host = this.host(request.url());
|
|
673
802
|
const headers = request.headers();
|
|
674
803
|
if (headers.cookie) this.cookies.request(headers.cookie, host);
|
|
@@ -676,6 +805,7 @@ module.exports = class {
|
|
|
676
805
|
if (this.requestCall) {
|
|
677
806
|
const run = await this.requestCall(request);
|
|
678
807
|
if (run === false) return request.abort();
|
|
808
|
+
if (run === true) return;
|
|
679
809
|
}
|
|
680
810
|
|
|
681
811
|
const rType = request.resourceType();
|