Menu

Puppeteer 혹은 Selenium에서 Anti-Captcha 플러그인 이용하는 방법

Puppeteer 및 Selenium은 브라우저 자동화를 위한 두 가지 주요 엔진으로, 당사의 플러그인의 경우, 이 두 엔진과 완벽하게 통합하여 사용하실 수 있습니다. 이 글에서는 각각 NodeJS 및 Python 프로그래밍 언어용 Puppeteer와 Selenium에서 사용하는 방법을 소개하겠습니다. Puppeteer와 Selenium 둘 중에서 하나를 선택하고자 하시는 경우, 네이티브 환경에서는 NodeJS+Puppeteer 조합을 강력히 추천합니다.

1. Dependency를 설치합니다. NodeJS의 경우, 아래에 지정된 npm 패키지를 설치하면 되며, Python의 경우, 패키지를 설치한 뒤 페이지에서 "chromedriver" 실행 파일을 다운로드 하시기 바랍니다. 드라이버 버전의 경우, 시스템에 설치된 Chrome 버전과 일치하여야 합니다.

Javascript
Python
npm install adm-zip puppeteer puppeteer-extra puppeteer-extra-plugin-stealth

2. Chrome용 플러그인을 ZIP 버전으로 다운로드하고, 프로젝트 폴더에 압축을 풉니다. 실제 버전은 여기에 있습니다. 또한, 프로그래밍 방식으로도 진행할 수 있습니다:

Javascript
Python
//npm install adm-zip
const https = require('https')
const fs = require('fs');
const AdmZip = require("adm-zip");

const pluginURL = 'https://antcpt.com/anticaptcha-plugin.zip';

(async () => {
    // 플러그인 다운로드
    await new Promise((resolve) => {
        https.get(pluginURL, resp => resp.pipe(fs.createWriteStream('./plugin.zip').on('close', resolve)));
    })
    // 압축 해제
    const zip = new AdmZip("./plugin.zip");
    await zip.extractAllTo("./plugin/", true);
})();

3. 다음으로 ./plugin/js/config_ac_api_key.js 파일에서 API 키를 구성합니다. API 키는 고객지원 영역에서 확인하실 수 있습니다. 해당 작업을 진행하려면 약간의 플러스 잔고를 보유하고 있어야 합니다.

Javascript
Python
const apiKey = 'API_KEY_32_BYTES';
if (fs.existsSync('./plugin/js/config_ac_api_key.js')) {
    let confData = fs.readFileSync('./plugin/js/config_ac_api_key.js', 'utf8');
    confData = confData.replace(/antiCapthaPredefinedApiKey = ''/g, `antiCapthaPredefinedApiKey = '${apiKey}'`);
    fs.writeFileSync('./plugin/js/config_ac_api_key.js', confData, 'utf8');
} else {
    console.error('plugin configuration not found!')
}

4. 플러그인으로 브라우저를 초기화합니다. Puppeteer의 경우, 저희는 웹 자동화된 Chromium 브라우저의 모든 흔적을 감추는 'puppeteer-extra' 패키지용 플러그인을 추천합니다.

Javascript
Python
//npm install puppeteer puppeteer-extra puppeteer-extra-plugin-stealth
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());

(async () => {
    const browser = await puppeteer.launch({
        headless: false,
        ignoreDefaultArgs: [
            "--disable-extensions",
            "--enable-automation"
        ],
        args: [
            '--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',
            '--disable-extensions-except=./plugin',
            '--load-extension=./plugin'
        ]
    });
    const page = await browser.newPage();
})();

5. 대상 페이지로 이동하여 필요한 경우 양식을 작성합니다. 그러면 플러그인에서 자동으로 reCHAPTCHA를 골라내어 풀기 시작합니다.

Javascript
Python
(async () => {
    const url = 'https://anti-captcha.com/demo/?page=recaptcha_v2_textarea';
    const login = 'Test login';
    const password = 'Test password';

    try {
        await page.goto(url, {
            waitUntil: "networkidle0"
        });
    } catch (e) {
        console.error('err while loading the page: '+e);
    }
    // 탐색 타임아웃 오류 비활성화
    await page.setDefaultNavigationTimeout(0);

    await page.$eval('#login', (element, login) => {
        element.value = login;
    }, login);
    await page.$eval('#password', (element, password) => {
        element.value = password;
    }, password);

})();

6. 다음은 조금 까다로운 부분에 해당합니다. 일부 웹 양식의 경우에는 사용자가 reCAPTCHA를 푼 다음 제출 버튼을 눌러야 하지만, 다른 경우에는 콜백을 활용하여 자동으로 제출하는 경우도 있으니 말입니다. 첫 번째 경우에서는 reCAPTCHA를 푼 다음 바로 제출 버튼을 눌러 제출하도록 진행하고자 합니다. 여기서 적절한 시기에 제출 버튼을 누르려면 .antigate_solver.solved 셀렉터가 나타날 때까지 기다린 다음 제출 버튼을 누르면 됩니다.

Javascript
Python
// "solved" 셀렉터가 나타날 때까지 기다립니다.
await page.waitForSelector('.antigate_solver.solved').catch(error => console.log('failed to wait for the selector'));
console.log('{{ $t('articles.how-to-integrate.code-comments.recaptcha-solved') }}');

// 제출 버튼 클릭
await Promise.all([
    page.click('#submitButton'),
    page.waitForNavigation({ waitUntil: "networkidle0" })
]);
console.log('작업 완료됨. reCAPTCHA 양식 우회 완료.');

이제 양식 작성, reCAPTCHA 풀기 및 우회가 모두 완료되었습니다. 전체 코드 샘플은 다음과 같습니다:

Javascript
Python
// first run the following to install required npm packages:
//
// npm install adm-zip follow-redirects puppeteer puppeteer-extra puppeteer-extra-plugin-stealth
//
//
const https = require('follow-redirects').https;
const fs = require('fs');
const AdmZip = require("adm-zip");

const apiKey = 'YOUR_API_KEY_HERE!';
const pluginURL = 'https://antcpt.com/anticaptcha-plugin.zip';
const url = 'https://anti-captcha.com/demo/?page=recaptcha_v2_textarea';
const login = 'Test login';
const password = 'Test password';
let page = null;


const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());

(async () => {
    // 플러그인 다운로드
    await new Promise((resolve) => {
        https.get(pluginURL, resp => resp.pipe(fs.createWriteStream('./plugin.zip').on('close', resolve)));
    })
    // 압축 해제
    const zip = new AdmZip("./plugin.zip");
    await zip.extractAllTo("./plugin/", true);

    //  구성 파일에서 API 키 설정
    await new Promise((resolve, reject) => {
        if (fs.existsSync('./plugin/js/config_ac_api_key.js')) {
            let confData = fs.readFileSync('./plugin/js/config_ac_api_key.js', 'utf8');
            confData = confData.replace(/antiCapthaPredefinedApiKey = ''/g, `antiCapthaPredefinedApiKey = '${apiKey}'`);
            fs.writeFileSync('./plugin/js/config_ac_api_key.js', confData, 'utf8');
            resolve();
        } else {
            console.error('plugin configuration not found!')
            reject();
        }
    });

    // 브라우저 실행 옵션 설정
    const options = {
        headless: false,
        ignoreDefaultArgs: [
            "--disable-extensions",
            "--enable-automation"
        ],
        args: [
            '--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',
            '--disable-extensions-except=./plugin',
            '--load-extension=./plugin'
        ]
    }

    try {
        // 플러그인으로 브라우저 실행
        const browser = await puppeteer.launch();
        page = await browser.newPage();
    } catch (e) {
        console.log('could not launch browser: '+e.toString())
        return;
    }

    // 대상 페이지로 이동
    try {
        await page.goto(url, {
            waitUntil: "networkidle0"
        });
    } catch (e) {
        console.error('err while loading the page: '+e);
    }

    // 탐색 타임아웃 오류 비활성화
    await page.setDefaultNavigationTimeout(0);

    // 양식 작성
    await page.$eval('#login', (element, login) => {
        element.value = login;
    }, login);
    await page.$eval('#password', (element, password) => {
        element.value = password;
    }, password);

    // "solved" 셀렉터가 나타날 때까지 기다립니다.
    await page.waitForSelector('.antigate_solver.solved').catch(error => console.log('failed to wait for the selector'));
    console.log('{{ $t('articles.how-to-integrate.code-comments.recaptcha-solved') }}');

    // 제출 버튼 클릭
    await Promise.all([
        page.click('#submitButton'),
        page.waitForNavigation({ waitUntil: "networkidle0" })
    ]);
    console.log('reCAPTCHA 처리됨');

})();

보너스: Chrome에서는 플러그인을 통한 브라우저 자동화를 지원하지 않으므로, 헤드리스 모드에서 플러그인을 실행하는 트릭을 사용할 수 있습니다. 애플리케이션에 가상 데스크톱을 제공하는 Xvfb 유틸리티를 사용해 보시기 바랍니다.

# 패키지 설치
apt-get install -y xvfb

# 디스플레이 변수 설정
export DISPLAY=:0

# 백그라운드에서 Xvfb 데몬 시작 (한 번만)
/usr/bin/Xvfb :0 -screen 0 1024x768x24 &

# 다음이 표시될 때까지 잠시 기다립니다. (한 번만).
sleep 5

# "xvfb-run" 접두사를 "node" 또는 "python" 스크립트에 추가
xvfb-run node myscript.js
# 혹은,
xvfb-run python myscript.py