-
Notifications
You must be signed in to change notification settings - Fork 251
/
pteer.js
286 lines (266 loc) · 9.98 KB
/
pteer.js
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
import fs from "fs";
import path from "path";
import puppeteer from "puppeteer-extra";
import StealthPlugin from "puppeteer-extra-plugin-stealth";
import dotenv from "dotenv";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
puppeteer.use(StealthPlugin());
// Load the default .env file
dotenv.config();
if (fs.existsSync(".env.local")) {
console.log("Using .env.local file to supply config environment variables");
const envConfig = dotenv.parse(fs.readFileSync(".env.local"));
for (const k in envConfig) {
process.env[k] = envConfig[k];
}
}
// 从环境变量解析用户名和密码
const usernames = process.env.USERNAMES.split(",");
const passwords = process.env.PASSWORDS.split(",");
const loginUrl = process.env.WEBSITE;
// 每个浏览器实例之间的延迟时间(毫秒)
const delayBetweenInstances = 10000;
//随机等待时间
function delayClick(time) {
return new Promise(function (resolve) {
setTimeout(resolve, time);
});
}
(async () => {
try {
if (usernames.length !== passwords.length) {
console.log(usernames.length, usernames, passwords.length, passwords);
console.log("用户名和密码的数量不匹配!");
return;
}
// 并发启动浏览器实例进行登录
const loginPromises = usernames.map((username, index) => {
const password = passwords[index];
const delay = index * delayBetweenInstances;
return new Promise((resolve, reject) => {
//其实直接使用await就可以了
setTimeout(() => {
launchBrowserForUser(username, password).then(resolve).catch(reject);
}, delay);
});
});
// 等待所有登录操作完成
await Promise.all(loginPromises);
} catch (error) {
// 错误处理逻辑
console.error("发生错误:", error);
}
})();
async function launchBrowserForUser(username, password) {
try {
const browser = await puppeteer.launch({
headless: process.env.ENVIRONMENT !== "dev", // 当ENVIRONMENT不是'dev'时启用无头模式
args: [
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-infobars",
"--window-position=0,0",
"--ignore-certifcate-errors",
"--ignore-certifcate-errors-spki-list",
"--disable-web-security",
"--disable-features=IsolateOrigins,site-per-process",
"--allow-running-insecure-content",
"--disable-blink-features=AutomationControlled",
"--no-sandbox",
"--mute-audio",
"--no-zygote",
"--no-xshm",
"--window-size=1920,1080",
"--no-first-run",
"--no-default-browser-check",
"--disable-dev-shm-usage",
"--disable-gpu",
"--enable-webgl",
"--ignore-certificate-errors",
"--lang=en-US,en;q=0.9",
"--password-store=basic",
"--disable-gpu-sandbox",
"--disable-software-rasterizer",
"--disable-background-timer-throttling",
"--disable-backgrounding-occluded-windows",
"--disable-renderer-backgrounding",
"--disable-infobars",
"--disable-breakpad",
"--disable-canvas-aa",
"--disable-2d-canvas-clip-aa",
"--disable-gl-drawing-for-tests",
"--enable-low-end-device-mode",
], //linux需要
defaultViewport: {
width: 1280,
height: 800,
userAgent:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
},
});
const page = await browser.newPage();
// 设置额外的 headers
await page.setExtraHTTPHeaders({
"accept-language": "en-US,en;q=0.9",
});
// 调用封装的反检测函数
await bypassDetection(page);
// 验证 `navigator.webdriver` 属性是否为 undefined
const isWebDriverUndefined = await page.evaluate(() => {
return `${navigator.webdriver}`;
});
console.log("navigator.webdriver is :", isWebDriverUndefined); // 输出应为 true
page.on("pageerror", (error) => {
console.error(`Page error: ${error.message}`);
});
page.on("error", async (error) => {
console.error(`Error: ${error.message}`);
// 检查是否是 localStorage 的访问权限错误
if (
error.message.includes(
"Failed to read the 'localStorage' property from 'Window'"
)
) {
console.log("Trying to refresh the page to resolve the issue...");
await page.reload(); // 刷新页面
// 重新尝试你的操作...
}
});
page.on("console", async (msg) => {
console.log("PAGE LOG:", msg.text());
// 使用一个标志变量来检测是否已经刷新过页面
if (
!page._isReloaded &&
msg.text().includes("the server responded with a status of 429")
) {
// 设置标志变量为 true,表示即将刷新页面
page._isReloaded = true;
//由于油候脚本它这个时候可能会导航到新的网页,会导致直接执行代码报错,所以使用这个来在每个新网页加载之前来执行
await page.evaluateOnNewDocument(() => {
localStorage.setItem("autoLikeEnabled", "false");
});
// 等待一段时间,比如 3 秒
await new Promise((resolve) => setTimeout(resolve, 3000));
console.log("Retrying now...");
// 尝试刷新页面
await page.reload();
}
});
//登录操作
await page.goto(loginUrl, { waitUntil: "networkidle0" });
console.log("登录操作");
// 使用XPath查询找到包含"登录"或"login"文本的按钮
await page.evaluate(() => {
let loginButton = Array.from(document.querySelectorAll("button")).find(
(button) =>
button.textContent.includes("登录") ||
button.textContent.includes("login")
);
// 如果没有找到,尝试根据类名查找
if (!loginButton) {
loginButton = document.querySelector(
".widget-button.btn.btn-primary.btn-small.login-button.btn-icon-text"
);
}
console.log(loginButton);
if (loginButton) {
loginButton.click();
console.log("Login button clicked.");
} else {
console.log("Login button not found.");
}
});
await login(page, username, password);
// 查找具有类名 "avatar" 的 img 元素验证登录是否成功
const avatarImg = await page.$("img.avatar");
if (avatarImg) {
console.log("找到avatarImg,登录成功");
// 可以继续对 avatarImg 进行操作,比如获取其属性等
} else {
console.log("未找到avatarImg,登录失败");
}
//真正执行阅读脚本
// 读取外部脚本文件的内容
const externalScriptPath = path.join(
dirname(fileURLToPath(import.meta.url)),
"external.js"
);
const externalScript = fs.readFileSync(externalScriptPath, "utf8");
// 在每个新的文档加载时执行外部脚本
await page.evaluateOnNewDocument((...args) => {
const [scriptToEval] = args;
eval(scriptToEval);
}, externalScript);
// 添加一个监听器来监听每次页面加载完成的事件
page.on("load", async () => {
// await page.evaluate(externalScript); //因为这个是在页面加载好之后执行的,而脚本是在页面加载好时刻来判断是否要执行,由于已经加载好了,脚本就不会起作用
});
await page.goto("https://linux.do/t/topic/13716/190");
} catch (err) {
console.log(err);
}
}
async function login(page, username, password) {
// 等待用户名输入框加载
await page.waitForSelector("#login-account-name");
// 模拟人类在找到输入框后的短暂停顿
await delayClick(500); // 延迟500毫秒
// 清空输入框并输入用户名
await page.click("#login-account-name", { clickCount: 3 });
await page.type("#login-account-name", username, {
delay: 100,
}); // 输入时在每个按键之间添加额外的延迟
// 等待密码输入框加载
await page.waitForSelector("#login-account-password");
// 模拟人类在输入用户名后的短暂停顿
await delayClick(500);
// 清空输入框并输入密码
await page.click("#login-account-password", { clickCount: 3 });
await page.type("#login-account-password", password, {
delay: 100,
});
// 模拟人类在输入完成后思考的短暂停顿
await delayClick(1000);
// 假设登录按钮的ID是'login-button',点击登录按钮
await page.waitForSelector("#login-button");
await delayClick(500); // 模拟在点击登录按钮前的短暂停顿
try {
await Promise.all([
page.waitForNavigation({ waitUntil: "domcontentloaded" }), // 等待 页面跳转 DOMContentLoaded 事件
page.click("#login-button"), // 点击登录按钮触发跳转
]); //注意如果登录失败,这里会一直等待跳转,导致脚本执行失败
} catch (error) {
console.error("Navigation timed out in login.:", error);
throw new Error("Navigation timed out in login.");
}
await delayClick(1000);
}
async function bypassDetection(page) {
// 在页面上下文中执行脚本,修改 `navigator.webdriver` 以及其他反检测属性
await page.evaluateOnNewDocument(() => {
// Overwrite the `navigator.webdriver` property to return `undefined`.
Object.defineProperty(navigator, "webdriver", {
get: () => undefined,
});
// Pass the Chrome Test.
window.chrome = {
runtime: {},
// Add more if needed
};
// Pass the Permissions Test.
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) =>
parameters.name === "notifications"
? Promise.resolve({ state: Notification.permission })
: originalQuery(parameters);
// Pass the Plugins Length Test.
Object.defineProperty(navigator, "plugins", {
get: () => [1, 2, 3, 4, 5], // Make plugins array non-empty
});
// Pass the Languages Test.
Object.defineProperty(navigator, "languages", {
get: () => ["en-US", "en"],
});
});
}