【发布时间】:2021-12-15 01:35:21
【问题描述】:
我正在使用 puppeteer 在 Node.js 中开发自动化 API。当调用 API 时,会创建一个 puppeteer 无头浏览器实例,并启动一些自动化过程,如登录,并登录到调用中传递的用户配置文件。但是,如果我正在执行自动化的网站检测到机器人行为。它会在电子邮件中发送登录代码,以便发送对该 API 调用的响应,要求提供登录代码。当登录代码在另一个 API 调用中提交时,它只是转到该页面并放置代码。因此我们登录了。当我只有一个浏览器实例时,这件事完美无缺。但是当多个浏览器实例正在运行时,登录代码 API 不会获得所需的页面。它跳转到最后创建的浏览器实例,该实例可能正在执行其他进程,而不是需要登录代码的目标实例。
我的问题是如何唯一标识浏览器实例,以便我可以将登录代码放在该实例上,而不是为不同用户执行不同进程的其他实例。
启动浏览器
let browser;
let page;
async function startBrowser() {
if(proxyUrl == '' || proxyUn == '' || proxyPw == ''){
browser = await puppeteer.launch({
//slowMo: 30, // to slowdown the process
headless: false, // set as false to open a chromium
ignoreDefaultArgs: ["--enable-automation"],
defaultViewport: null,
args: ["--no-sandbox",
"--disable-setuid-sandbox",
"--start-maximized",
'--window-size=1920,1080',
"--disable-gpu",
"--disable-dev-profile",
]
});
}else{
//proxy Chain for proxy application
const oldProxyUrl = `http://${proxyUn}:${proxyPw}@${proxyUrl}`;
const newProxyUrl = await proxyChain.anonymizeProxy(oldProxyUrl);
browser = await puppeteer.launch({
//slowMo: 30, // to slowdown the process
headless: false, // set as false to open a chromium
ignoreDefaultArgs: ["--enable-automation"],
defaultViewport: null,
args: ["--no-sandbox",
"--disable-setuid-sandbox",
"--start-maximized",
'--window-size=1920,1080',
"--disable-gpu",
"--disable-dev-profile",
`--proxy-server=${newProxyUrl}`,
]
});
}
browser.on('disconnected', function(){
browser = '';
});
page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080 });
page.setUserAgent(
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"
);
}
播放测试
async function playTest(url, res) {
try {
try{
await startBrowser();
await page.setDefaultNavigationTimeout(32400000);
}catch(e){
proxyFailedFlag = true;
console.error(new Date().toLocaleString() + ': ', e);
return res.status(400).json({ status: 'failure', info: 'Invalid Proxy' }).end();
}
try{
console.log(new Date().toLocaleString() + ': ', 'connecting login page ...');
await page.goto(url);
}catch(e){
proxyFailedFlag = true;
await closeBrowser(browser);
console.error(new Date().toLocaleString() + ': ', e);
return res.status(400).json({ status: 'failure', info: 'Invalid Proxy' }).end();
}
await waitTillHTMLRendered(page);
await page.setViewport({
width: 1400,
height: 966
});
await playing(res);
} catch (e) {
console.error(new Date().toLocaleString() + ': ', e);
}
}
播放
const playing = async (res) => {
console.log(new Date().toLocaleString() + ': ', 'waiting for login form ...');
if (await page.$(ACCEPT_COOKIES_SELECTOR) !== null) {
const accept_elm = await page.$(ACCEPT_COOKIES_SELECTOR);
await accept_elm.click({ clickCount: 1 });
}
await page.waitForSelector(USERNAME_SELECTOR, {
visible: true,
});
await page.click(USERNAME_SELECTOR);
await page.keyboard.type(email);
await page.click(PASSWORD_SELECTOR);
await page.keyboard.type(password);
const m_login_elm = await page.$(M_LOGIN_SELECTOR);
await m_login_elm.click({ clickCount: 1 });
console.log(new Date().toLocaleString() + ': ', 'logging in ...');
try {
await page.waitForNavigation();
let pageURL = page.url();
console.log("page url: ", pageURL)
if (pageURL === 'https://www.targetwebsite.com/feed/') {
loginFailedFlag = false;
let accountElm = await page.$(PROFILE_NAME_SELECTOR);
accountName = await page.evaluate(accountElm => accountElm.textContent, accountElm);
accountName = accountName.trim();
console.log("Login success", accountName);
} else if (pageURL === "https:/www.targetwebsite.com/login-submit") {
console.log("Exception Login Submit Form!");
if (await page.$(NOT_REMEMBER_BTN_SELECTOR) !== null) {
const notRememberBtn = await page.$(NOT_REMEMBER_BTN_SELECTOR);
await notRememberBtn.click({ clickCount: 1 });
} else {
throw new Error('Login fail!');
}
} else {
pageURL = pageURL.split("challenge");
if(pageURL[0]==="https://www.targetwebsite.com/captcha/"){
loginFailedFlag = true;
return res.status(400).json({ status: 'failure', info: 'Captcha' }).end();
}
loginFailedFlag = false;
console.log("Pin code is required");
}
} catch (error) {
loginFailedFlag = true;
return res.status(400).json({ status: 'failure', info: 'Invalid Login' }).end();
}
if (await page.$(PIN_INPUT_SELECTOR) !== null) {
console.log("pin code required")
warnFlag = true
warnmsg = 'Awaiting IP Code';
} else {
warnFlag = false
console.log('not found Pin Code step');
await waitTillHTMLRendered(page);
if (await page.$(RE_ACCEPT_COOKIES_SELECTOR) !== null) {
const accept_elm = await page.$(RE_ACCEPT_COOKIES_SELECTOR);
await accept_elm.click({ clickCount: 1 });
}
await getCookies();
}
}
在第二次 API 调用中提交登录代码时
async function handleIpProcess(ip_email, ip_code) {
await waitTillHTMLRendered(page);
await page.click(PIN_INPUT_SELECTOR);
await page.keyboard.type(ip_code);
await page.waitForSelector(PIN_SUBMIT_SELECTOR, {
visible: true,
});
await page.click(PIN_SUBMIT_SELECTOR);
await getCookies();
}
【问题讨论】: