我正在与puppeteer一起开发Node.js中的自动化API。当调用API时,将创建一个puppeteer无头浏览器实例,并启动一些自动化过程,如Login,它将登录到调用中传递的用户配置文件中。但如果我执行自动化操作的网站检测到机器人行为。它在电子邮件上发送登录码,因此发送对该API调用的响应,请求登录码。当登录代码在另一个API调用中提交时,它只是转到该页面并放置代码。因此我们登录了。当我只有一个浏览器实例时,这个东西可以完美地工作。但是,当多个浏览器实例同时运行时,Login代码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调用中提交Login代码时
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();
}
我通过为每个实例创建一个带有uid的对象并将其压入数组来解决这个问题。
browser = {
uuid: email, //used email because it was most suitable in my case
pass: password,
instance: undefined,
page: undefined
}
browserInstances.push(browser); //browserInstances is an array initialized earlier in the code