Cendertron,動態爬蟲的滑動驗證碼繞過策略

Cendertron,動態爬蟲的滑動驗證碼繞過策略

Cendertron 安全動態爬蟲系列中我們依次介紹了安全爬蟲的設計、爬蟲的集羣搭建,本篇則是討論有關於滑動驗證碼的繞過策略。

本文采用的策略與代碼來自 How to bypass “slider CAPTCHA” with JS and Puppeteer 一文。

爬蟲中滑動驗證的繞過

驗證是常見的反爬蟲策略之一,在現在的很多站點中我們會引入滑動驗證的方式,來校驗訪問者的真實性。譬如下面著名的 jQuery 滑動插件:

在模擬登陸時,我們往往需要繞過這樣的滑動驗證,而基於 Puppeteer 的動態爬蟲也給予了便利;往往我們需要進行以下步驟:移動到滑條中間,按下鼠標,移動鼠標,釋放鼠標。

const puppeteer = require('puppeteer');

async function run() {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: { width: 1366, height: 768 }
  });
  const page = await browser.newPage();

  await page.goto('http://kthornbloom.com/slidetosubmit/');
  await page.type('input[name="name"]', 'Puppeteer Bot');
  await page.type('input[name="email"]', '[email protected]');

  let sliderElement = await page.$('.slide-submit');
  let slider = await sliderElement.boundingBox();

  let sliderHandle = await page.$('.slide-submit-thumb');
  let handle = await sliderHandle.boundingBox();

  await page.mouse.move(
    handle.x + handle.width / 2,
    handle.y + handle.height / 2
  );
  await page.mouse.down();
  await page.mouse.move(handle.x + slider.width, handle.y + handle.height / 2, {
    steps: 10
  });
  await page.mouse.up();

  await page.waitFor(3000);

  // success!

  await browser.close();
}

run();

在實際的案例中,我們可以以淘寶的註冊界面爲例:

const puppeteer = require('puppeteer');

async function run() {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: { width: 1366, height: 768 }
  });
  const page = await browser.newPage();

  await page.evaluateOnNewDocument(() => {
    Object.defineProperty(navigator, 'webdriver', {
      get: () => false
    });
  });

  await page.goto('https://world.taobao.com/markets/all/sea/register');

  let frame = page.frames()[1];
  await frame.waitForSelector('.nc_iconfont.btn_slide');

  const sliderElement = await frame.$('.slidetounlock');
  const slider = await sliderElement.boundingBox();

  const sliderHandle = await frame.$('.nc_iconfont.btn_slide');
  const handle = await sliderHandle.boundingBox();
  await page.mouse.move(
    handle.x + handle.width / 2,
    handle.y + handle.height / 2
  );
  await page.mouse.down();
  await page.mouse.move(handle.x + slider.width, handle.y + handle.height / 2, {
    steps: 50
  });
  await page.mouse.up();

  await page.waitFor(3000);

  // success!

  await browser.close();
}

run();

另一種常見的滑塊則是如下這種拼圖性質的滑塊:

const puppeteer = require('puppeteer');
const Rembrandt = require('rembrandt');

async function run() {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: { width: 1366, height: 768 }
  });
  const page = await browser.newPage();

  let originalImage = '';

  await page.setRequestInterception(true);
  page.on('request', request => request.continue());
  page.on('response', async response => {
    if (response.request().resourceType() === 'image')
      originalImage = await response.buffer().catch(() => {});
  });

  await page.goto('https://monoplasty.github.io/vue-monoplasty-slide-verify/');

  const sliderElement = await page.$('.slide-verify-slider');
  const slider = await sliderElement.boundingBox();

  const sliderHandle = await page.$('.slide-verify-slider-mask-item');
  const handle = await sliderHandle.boundingBox();

  let currentPosition = 0;
  let bestSlider = {
    position: 0,
    difference: 100
  };

  await page.mouse.move(
    handle.x + handle.width / 2,
    handle.y + handle.height / 2
  );
  await page.mouse.down();

  while (currentPosition < slider.width - handle.width / 2) {
    await page.mouse.move(
      handle.x + currentPosition,
      handle.y + handle.height / 2 + Math.random() * 10 - 5
    );

    let sliderContainer = await page.$('.slide-verify');
    let sliderImage = await sliderContainer.screenshot();

    const rembrandt = new Rembrandt({
      imageA: originalImage,
      imageB: sliderImage,
      thresholdType: Rembrandt.THRESHOLD_PERCENT
    });

    let result = await rembrandt.compare();
    let difference = result.percentageDifference * 100;

    if (difference < bestSlider.difference) {
      bestSlider.difference = difference;
      bestSlider.position = currentPosition;
    }

    currentPosition += 5;
  }

  await page.mouse.move(
    handle.x + bestSlider.position,
    handle.y + handle.height / 2,
    { steps: 10 }
  );
  await page.mouse.up();

  await page.waitFor(3000);

  // success!

  await browser.close();
}

run();

這裏我們採用了簡單的圖片對比的方式,即在滑動過程中,如果發現了有符合閾值的差異,則認爲是已經滑動成功。

Spider 配置

Cendertron 中,提供了一類特殊的 Slider Captcha Monkey,在傳入的 SpiderOption 中添加如下參數即可:

export interface SpiderOption {
  allowRedirect: boolean;
  depth: number;
  // 頁面插件
  monkies?: {
    sliderCaptcha: {
      sliderElementSelector: string;
      sliderHandleSelector: string;
    };
  };
}

延伸閱讀

您可以通過以下任一方式閱讀筆者的系列文章,涵蓋了技術資料歸納、編程語言與理論、Web 與大前端、服務端開發與基礎架構、雲計算與大數據、數據科學與人工智能、產品設計等多個領域:

  • 在 Gitbook 中在線瀏覽,每個系列對應各自的 Gitbook 倉庫。
Awesome Lists Awesome CheatSheets Awesome Interviews Awesome RoadMaps Awesome-CS-Books-Warehouse
編程語言理論 Java 實戰 JavaScript 實戰 Go 實戰 Python 實戰 Rust 實戰
軟件工程、數據結構與算法、設計模式、軟件架構 現代 Web 開發基礎與工程實踐 大前端混合開發與數據可視化 服務端開發實踐與工程架構 分佈式基礎架構 數據科學,人工智能與深度學習 產品設計與用戶體驗
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章