1. 程式人生 > 實用技巧 >headless模式美團滑塊驗證碼無法通過的解決方案

headless模式美團滑塊驗證碼無法通過的解決方案

在有介面瀏覽器模擬美團滑塊滑動可以直接通過(此處使用pyppeteer,selenium未測試),一旦使用headless模式則無法通過驗證,今天就來聊一聊如何繞過美團的headless檢測。

單獨開啟驗證頁面,可以看到載入了3個js檔案,均經過高度混淆

首先找找"webdriver",這是一個最常見的特徵值

選中的這兩行即美團會檢測的JS值,我們要做的就是,確保這部分關鍵字在正常瀏覽器和無頭瀏覽器返回值保持一致

如何確保一致?一是通過載入頁面後執行JS程式碼,來覆蓋headless下的特徵值,二是使用mitmproxy攔截修改伺服器響應的JS程式碼來實現目的

這裡我使用mitmproxy

from mitmproxy import ctx

detectList = ['webdriver', '__driver_evaluate', '__webdriver_evaluate',
              '__selenium_evaluate', '__fxdriver_evaluate', '__driver_unwrapped',
              '__webdriver_unwrapped', '__selenium_unwrapped', '__fxdriver_unwrapped',
              '_Selenium_IDE_Recorder', '_selenium', 'calledSelenium',
              '_WEBDRIVER_ELEM_CACHE', 'ChromeDriverw', 'driver-evaluate',
              'webdriver-evaluate', 'selenium-evaluate', 'webdriverCommand',
              'webdriver-evaluate-response', '__webdriverFunc', '__webdriver_script_fn',
              '__$webdriverAsyncExecutor', '__lastWatirAlert', '__lastWatirConfirm',
              '__lastWatirPrompt', '$chrome_asyncScriptInfo', '$cdc_asdjflasutopfhvcZLmcfl_']

def response(flow):
    if '.js' in flow.request.url:
        for key in detectList:
            flow.response.text = flow.response.text.replace('"{}"'.format(key), '"NO-SUCH-ATTR"')

其中detectList是常見的檢測的特徵

使用代理攔截後發現依然無法驗證通過,那可能原因是還有其他檢測的特徵值不在我們的列表中,也有可能是上面的特徵值字串被加密了

注意到slider.js的第一行,混淆加密的列表,大概率是經過base64編碼後的字串列表

對其base64解碼得到

_0x2c02_b64decode = ['apply', 'return (function() ', 'console', 'log', 'warn', 'info', 'error', 'exception', 'trace', 'exports',
     'undefined', 'Math', 'return this', 'number', 'hasOwnProperty', 'call', '2.6.5', 'version', 'function',
     ' is not an object!', 'document', 'createElement', 'defineProperty', 'div', 'toString', 'valueOf',
     "Can't convert object to primitive value", 'get', 'Accessors not supported!', 'value', 'Symbol(', 'concat',
     '__core-js_shared__', 'push', 'global', '© 2019 Denis Pushkarev (zloirock.ru)', 'native-function-to-string', 'src',
     'inspectSource', 'join', 'prototype', ' is not a function!', 'core', 'meta', 'isExtensible', 'preventExtensions',
     'string', 'NEED', 'KEY', 'getWeak', 'onFreeze', 'wks', 'Symbol', 'Symbol.', 'propertyIsEnumerable', 'String',
     'split', "Can't call method on  ", 'ceil', 'min', 'max', 'length', 'keys', 'IE_PROTO',
     'constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf',
     'getOwnPropertySymbols', 'isArray', 'Array', 'defineProperties', 'iframe', 'style', 'display', 'none',
     'javascript:', 'write', '<script>document.F=Object</script>', 'close', 'create', 'getOwnPropertyNames', 'object',
     '[object Window]', 'slice', 'getOwnPropertyDescriptor', 'stringify', '_hidden', 'toPrimitive', 'symbol-registry',
     'symbols', 'op-symbols', 'QObject', 'findChild', 'iterator', 'symbol', 'enumerable',
     'Symbol is not a constructor!',
     'hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables',
     'store', 'charAt', 'Object', '[null]', 'JSON', 'getPrototypeOf', 'constructor', 'seal', 'isFrozen', 'assign',
     'abcdefghijklmnopqrst', ": can't set as prototype!", 'setPrototypeOf', '__proto__', 'set', 'Arguments', 'Null',
     'toStringTag', '[object z]', '[object ', 'Reflect', 'ownKeys', 'random', 'typed_array', 'view', 'DataView',
     'ArrayBuffer', 'Wrong index!', 'RangeError', 'Infinity', 'pow', 'LN2', 'byteOffset', 'reverse', 'ABV', 'setInt8',
     'getInt8', 'buffer', 'getIteratorMethod', '@@iterator', 'species', 'unscopables', 'next', 'entries', 'name',
     'values', 'return', 'Uint8Array', 'Shared', 'BYTES_PER_ELEMENT', 'lastIndexOf', 'reduce', 'sort', 'toLocaleString',
     'typed_constructor', 'def_constructor', 'CONSTR', 'TYPED', 'VIEW', 'Wrong length!', 'Wrong offset!',
     ' is not a typed array!', 'It is not a typed array constructor!', 'done', 'floor', 'configurable', 'writable',
     'byteLength', 'Float64', 'Int32', 'Uint32', 'Int16', 'Int8', 'Uint16', 'match', 'ignoreCase', 'multiline',
     'unicode', 'sticky', '/a/i', 'RegExp', 'source', 'exec',
     'RegExp exec method returned something other than an Object or null',
     'RegExp#exec called on incompatible receiver', 'replace', 'lastIndex', '$(?!\\s)', 'index', '$<a>',
     '\t\n\x0b\x0c\r \xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\ufeff',
     'trim', 'Number', 'charCodeAt', '0b1',
     'MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger',
     'flags', '/a/b', 'Invalid Date', 'getTime', 'groups', 'throw', 'label', 'pop', 'trys', 'ops',
     'Please drag the slider to the right', 'Please slide with one finger', 'スライダを右にドラッグしてください', 'zh-CN', 'template',
     "<div class='yoda-slider-tip ", "' id=", 'tip', 'wrapper', "'>\n                <p class='sliderTitle ",
     'sliderTitle', 'dragRight', "</p>\n                <div class='boxWrapper ", 'boxWrapper',
     ">\n                    <div class='boxStatic ", 'boxStatic', 'box', 'moveingBar',
     '></div>\n                </div>\n            </div>', 'callback', 'url', 'jsonp_', 'data', 'removeChild',
     'success', 'indexOf', 'time', 'fail', '請求超時', '121000', '121002', '121004', '121005', '121018', '121045', '99999',
     '121009', '121011', '121036', '121040', '121042', '121043', '121046', '121052', '121053', '121055', '121056',
     '121058', '121061', '121065', '121066', '121067', '121088', '121099', '00101', '您的請求出現了異常', '00102', '您的網路狀況不好',
     '00300', '00400', '網路資源異常,請稍後再試', '00500', '網路重定向,請稍後再試', '伺服器異常,請稍後再試', 'Request exception',
     'Server exception, please try again later', 'ネットワークのつなぎ狀態が不安定です', 'ネットワークがリダイレクトしました、後でもう一度やり直してください',
     'リクエストがエラー発生しました', 'サーバーが異常です。しばらくしてからもう一度お試しください', 'now', 'open', 'setRequestHeader', 'seed', 'config',
     'language', 'yoda-language', 'facespeech', 'onload', 'readyState', 'status', 'response', 'Yoda', 'CAT',
     'postBatch', '200|', 'ajax', '當前請求狀態', 'NETWORK_REDIRECT_CODE', 'NETWORK_REDIRECT_TIP', 'NETWORK_REQUEST_CODE',
     'NETWORK_SERVER_CODE', 'NETWORK_SERVER_TIP', 'ontimeout', 'sendLog', 'NETWORK_TIMEOUT_CODE', 'onerror',
     'ajaxError', 'NETWORK_FAILURE_TIP', 'send', 'XDomainRequest', '建立請求物件失敗', 'catch', 'HTTP請求失敗', 'FormData',
     'Content-Type', 'application/x-www-form-urlencoded', 'POST', 'GET', 'HEAD', 'verifyAPIST', 'yodaInitTime', 'type',
     'metric', 'action', 'YODA_CONFIG', '__API_URL__', '/v2/ext_api/', '/verify', 'request_code', 'report', 'sent',
     'driver-evaluate,webdriver-evaluate,selenium-evaluate,webdriverCommand,webdriver-evaluate-response',
     'removeEventListener', 'hasAttribute', 'nodeName', 'cd_frame_id_', 'documentElement', 'webdriver', 'domAutomation',
     '__lastWatirPrompt', '__webdriver_script_fn', 'cookieChromeDriver', 'cookie', 'asyncScriptInfo',
     '$cdc_asdjflasutopfhvcZLmcfl_', 'webdriverElemCache', '_WEBDRIVER_ELEM_CACHE', 'webdriverAsyncExecutor',
     '__$webdriverAsyncExecutor', 'getElementsByTagName', 'frame', 'lwc', 'createShader', 'compileShader',
     'getShaderParameter', 'COMPILE_STATUS', 'deleteShader', 'width', 'height', 'getContext', 'webgl',
     'experimental-webgl', 'canvas', 'inline', '30px serif', 'textAlign', 'center', 'textBaseline', 'middle',
     'fillText', '