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', '😜😂😍', 'fillStyle', '#dd403b', 'arc', 'closePath', 'fill', '#d66500', 'beginPath',
     'createLinearGradient', 'addColorStop', '#F4F4F2', '#F5E905', '#490F44', 'white', '#FFFFFF', '#A4A3A3',
     'shadowColor', '#FFD161', 'shadowOffsetY', 'shadowBlur', 'font', '16px xxx', 'moveTo', 'bezierCurveTo', 'stroke',
     'toDataURL', 'getParameter', 'VENDOR', 'RENDERER', 'getExtension', 'WEBGL_debug_renderer_info',
     'UNMASKED_VENDOR_WEBGL', 'monospace', 'sans-serif', 'serif', 'Arial Hebrew', 'Arial Narrow',
     'Arial Rounded MT Bold', 'Bitstream Vera Sans Mono', 'Book Antiqua', 'Bookman Old Style', 'Calibri', 'Cambria',
     'Cambria Math', 'Century', 'Comic Sans MS', 'Courier', 'Geneva', 'Helvetica Neue', 'Impact', 'Lucida Bright',
     'Lucida Console', 'LUCIDA GRANDE', 'Lucida Handwriting', 'Lucida Sans', 'Lucida Sans Typewriter',
     'Lucida Sans Unicode', 'Microsoft Sans Serif', 'Monaco', 'Monotype Corsiva', 'MS Gothic', 'MS PGothic',
     'MS Reference Sans Serif', 'MS Sans Serif', 'MYRIAD', 'MYRIAD PRO', 'Palatino', 'Palatino Linotype',
     'Segoe Script', 'Segoe UI Symbol', 'Times', 'Times New Roman', 'Times New Roman PS', 'Verdana', 'Wingdings 2',
     'Wingdings 3', 'Academy Engraved LET', 'ADOBE CASLON PRO', 'Adobe Garamond', 'Agency FB', 'Aharoni',
     'Albertus Extra Bold', 'Albertus Medium', 'Amazone BT', 'American Typewriter', 'AmerType Md BT', 'Andalus',
     'Angsana New', 'Aparajita', 'Apple Chancery', 'Apple Color Emoji', 'Apple SD Gothic Neo', 'ARCHER', 'ARNO PRO',
     'Arrus BT', 'Aurora Cn BT', 'AvantGarde Bk BT', 'AvantGarde Md BT', 'AVENIR', 'Ayuthaya', 'Bangla Sangam MN',
     'Bank Gothic', 'BankGothic Md BT', 'Baskerville', 'Baskerville Old Face', 'Batang', 'BatangChe', 'Bauer Bodoni',
     'Bazooka', 'Bell MT', 'Bembo', 'Benguiat Bk BT', 'Berlin Sans FB', 'Berlin Sans FB Demi', 'Bernard MT Condensed',
     'BernhardFashion BT', 'BernhardMod BT', 'BinnerD', 'Blackadder ITC', 'Bodoni 72 Oldstyle', 'Bodoni 72 Smallcaps',
     'Bodoni MT Condensed', 'Bodoni MT Poster Compressed', 'Bookshelf Symbol 7', 'Boulder', 'Bradley Hand',
     'Bradley Hand ITC', 'Bremen Bd BT', 'Britannic Bold', 'Browallia New', 'Brush Script MT', 'Californian FB',
     'Calisto MT', 'Calligrapher', 'Centaur', 'Cezanne', 'CG Omega', 'CG Times', 'Chalkboard', 'Chalkboard SE',
     'Charlesworth', 'Charter Bd BT', 'Charter BT', 'Chaucer', 'ChelthmITC Bk BT', 'Chiller', 'Clarendon Condensed',
     'CloisterBlack BT', 'Cochin', 'Constantia', 'Cooper Black', 'Copperplate', 'Copperplate Gothic',
     'Copperplate Gothic Bold', 'Copperplate Gothic Light', 'CopperplGoth Bd BT', 'Corbel', 'CordiaUPC', 'Cornerstone',
     'Coronet', 'Curlz MT', 'DaunPenh', 'Dauphin', 'DB LCD Temp', 'DELICIOUS', 'Denmark', 'Didot', 'DilleniaUPC', 'DIN',
     'DokChampa', 'Dotum', 'DotumChe', 'Ebrima', 'Edwardian Script ITC', 'English 111 Vivace BT', 'Engravers MT',
     'EngraversGothic BT', 'Eras Bold ITC', 'Eras Demi ITC', 'Eras Medium ITC', 'EucrosiaUPC', 'Euphemia', 'EUROSTILE',
     'Exotc350 Bd BT', 'FangSong', 'Fixedsys', 'FONTIN', 'Footlight MT Light', 'Forte', 'FrankRuehl', 'Fransiscan',
     'FreesiaUPC', 'Freestyle Script', 'French Script MT', 'FrnkGothITC Bk BT', 'Fruitger', 'FRUTIGER', 'Futura',
     'Futura Bk BT', 'Futura Lt BT', 'Futura ZBlk BT', 'Galliard BT', 'Gautami', 'Geeza Pro', 'Geometr231 BT',
     'Geometr231 Hv BT', 'GeoSlab 703 Lt BT', 'GeoSlab 703 XBd BT', 'Gigi', 'Gill Sans', 'Gill Sans MT',
     'Gill Sans MT Condensed', 'Gill Sans Ultra Bold', 'Gill Sans Ultra Bold Condensed',
     'Gloucester MT Extra Condensed', 'GOTHAM', 'GOTHAM BOLD', 'GoudyHandtooled BT', 'Gujarati Sangam MN', 'Gulim',
     'GulimChe', 'Gungsuh', 'GungsuhChe', 'Gurmukhi MN', 'Haettenschweiler', 'Harlow Solid Italic', 'Heiti SC',
     'Heiti TC', 'HELV', 'High Tower Text', 'Hiragino Kaku Gothic ProN', 'Hiragino Mincho ProN', 'Hoefler Text',
     'Humanst521 BT', 'Humanst521 Lt BT', 'Imprint MT Shadow', 'Incised901 Bd BT', 'Incised901 BT', 'Incised901 Lt BT',
     'INCONSOLATA', 'Informal Roman', 'IrisUPC', 'Iskoola Pota', 'Jazz LET', 'Jenson', 'Jester', 'Jokerman',
     'Juice ITC', 'Kabel Bk BT', 'Kabel Ult BT', 'Kailasa', 'KaiTi', 'Kalinga', 'Kannada Sangam MN', 'Kartika',
     'Kaufmann Bd BT', 'Kaufmann BT', 'KodchiangUPC', 'Kokila', 'Krungthep', 'Kunstler Script', 'Latha', 'Leelawadee',
     'Letter Gothic', 'Lithograph', 'Lithograph Light', 'Long Island', 'Lydian BT', 'Magneto', 'Maiandra GD',
     'Malayalam Sangam MN', 'Malgun Gothic', 'Marigold', 'Marion', 'Marker Felt', 'Market', 'Marlett',
     'Matura MT Script Capitals', 'Meiryo', 'Meiryo UI', 'Microsoft New Tai Lue', 'Microsoft PhagsPa',
     'Microsoft Tai Le', 'Microsoft Uighur', 'Microsoft YaHei', 'Microsoft Yi Baiti', 'MingLiU', 'MingLiU_HKSCS',
     'MingLiU_HKSCS-ExtB', 'Minion Pro', 'Miriam', 'Miriam Fixed', 'Mistral', 'Modern', 'Modern No. 20', 'MONO',
     'MoolBoran', 'Mrs Eaves', 'MS LineDraw', 'MS Mincho', 'MS PMincho', 'MS UI Gothic', 'MT Extra', 'News Gothic',
     'NewsGoth BT', 'Niagara Engraved', 'Niagara Solid', 'Noteworthy', 'NSimSun', 'Nyala', 'Onyx', 'Oriya Sangam MN',
     'OSAKA', 'Palace Script MT', 'Papyrus', 'Parchment', 'Party LET', 'Pegasus', 'Perpetua', 'Perpetua Titling MT',
     'PetitaBold', 'Pickwick', 'Playbill', 'PMingLiU', 'Poor Richard', 'PRINCETOWN LET', 'Pristina', 'Pythagoras',
     'Raavi', 'Rage Italic', 'Ribbon131 Bd BT', 'Rockwell', 'Rockwell Condensed', 'Rod', 'Roman', 'Sakkal Majalla',
     'Santa Fe LET', 'Savoye LET', 'Sceptre', 'Script', 'SCRIPTINA', 'Serifa', 'Serifa BT', 'Serifa Th BT',
     'ShelleyVolante BT', 'Sherwood', 'Shonar Bangla', 'Showcard Gothic', 'Shruti', 'Signboard', 'SILKSCREEN',
     'Simplified Arabic', 'Simplified Arabic Fixed', 'SimSun', 'SimSun-ExtB', 'Sinhala Sangam MN', 'Skia',
     'Small Fonts', 'Snap ITC', 'Snell Roundhand', 'Socket', 'Terminal', 'Traditional Arabic', 'TRAJAN PRO', 'Tristan',
     'Tunga', 'Tw Cen MT', 'Tw Cen MT Condensed Extra Bold', 'TypoUpright BT', 'Unicorn', 'Univers CE 55 Medium',
     'Univers Condensed', 'Utsaah', 'Viner Hand ITC', 'VisualUI', 'Vivaldi', 'Vladimir Script', 'Vrinda', 'Westminster',
     'WHITNEY', 'Wide Latin', 'ZapfEllipt BT', 'ZapfHumnst BT', 'Zapfino', 'Zurich BlkEx BT', 'Zurich Ex BT',
     'ZWAdobeF', 'filter', 'body', 'span', 'left', '-9999px', 'fontSize', '72px', 'normal', 'fontWeight',
     'letterSpacing', 'auto', 'lineHeight', 'textTransform', 'textDecoration', 'textShadow', 'whiteSpace', 'wordBreak',
     'textContent', 'Eat Better, Live Better', 'fontFamily', 'appendChild', 'offsetWidth', 'offsetHeight',
     'ontouchstart', 'touches', 'pageX', 'offsetX', 'offsetY', 'BUTTON', 'rohr_', 'buttons', 'buttonName', 'splice',
     'clientX', 'scrollLeft', 'scrollTop', 'clientWidth', 'clientHeight', 'unshift', 'addEventListener', 'mousemove',
     'pageY', 'clientLeft', 'clientY', 'clientTop', 'toFixed', 'target', 'which', 'keyCode', 'click', 'ontouchmove',
     'touchmove', 'focus', 'inputs', 'inputName', '0-0-0-0', 'INPUT', 'keyboardEvent', 'lastTime', 'blur',
     'editFinishedTimeStamp', 'substr', 'mousedown', 'touchstart', 'AudioContext', 'createAnalyser', 'Float32Array',
     'cancelAnimationFrame', 'getFloatFrequencyData', '-Infinity', 'requestAnimationFrame', 'href', 'jsError',
     '[voiceprint_error]', 'createOscillator', 'createGain', 'connect', 'frequency', 'gain', 'currentTime',
     'linearRampToValueAtTime', 'start', 'exponentialRampToValueAtTime', 'stop', 'location', '[audio_error]', 'message',
     'attachShader', 'linkProgram', 'LINK_STATUS', 'detachShader', 'deleteProgram', 'VERTEX_SHADER',
     '\n                attribute vec4 a_position;\n                uniform mat4 u_matrix;\n                varying vec4 v_color;\n                void main() {\n                    gl_Position = a_position;\n                    v_color = gl_Position * 0.5 + 0.5;\n                }\n            ',
     'FRAGMENT_SHADER',
     '\n                precision mediump float;\n                varying vec4 v_color;\n                void main() {\n                    gl_FragColor = v_color; // return reddish-purple\n                }\n            ',
     'getAttribLocation', 'a_position', 'bindBuffer', 'ARRAY_BUFFER', 'bufferData', 'STATIC_DRAW', 'viewport',
     'clearColor', 'clear', 'COLOR_BUFFER_BIT', 'enableVertexAttribArray', 'FLOAT', 'vertexAttribPointer', 'TRIANGLES',
     'drawArrays', 'renderer', 'UNMASKED_RENDERER_WEBGL', 'hash', '2.2.1', 'innerHeight', 'availWidth', 'availHeight',
     'referrer', 'phantom', 'callPhantom', 'Window', 'WSH', 'DedicatedWorkerGlobalScope', 'wsh', 'abnormal',
     '[env_error]', 'plugins', '[plugin_error]', 'OscillatorNode', 'reload', 'cts', 'btoa', '[btoa_error]',
     'Array contains invalid value: ', 'unsupported array-like object', 'fromCharCode', '0123456789abcdef',
     'AES must be instanitated with `new`', 'key', '_prepare', 'invalid key size (must be 16, 24 or 32 bytes)', '_Kd',
     '_Ke', 'encrypt', 'invalid plaintext size (must be 16 bytes)', 'description', 'cbc',
     'invalid initialation vector size (must be 16 bytes)', '_aes', '_lastCipherblock',
     'invalid ciphertext size (must be multiple of 16 bytes)', 'decrypt', 'PKCS#7 padding byte out of range',
     'substring', 'test', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 'navigator', 'userAgent',
     'MicroMessenger', 'Firefox', 'Opera', 'OPR', 'Trident', 'Edge', 'Chrome', 'Safari', 'unknown', 'subarray',
     'session', 'atob', 'sign', 'ModeOfOperation', 'padding', 'pkcs7', 'strip', 'fromBytes', 'honey', '【url参数处理异常】',
     'utf8', 'boolean', 'uniqueId', 'Kaito', '_starttime', 'origin', 'pathname', 'search', 'YODA_Bridge', 'publish',
     'KNB', 'setTimeout', 'use', 'alert', 'options', 'parse', '_yoda_options', 'riskLevelInfo', 'yodaVersion',
     'resetVariable', 'isNeedLoad', 'getSourcePath', 'loadSource', 'signal', 'init', '_yoda_riskLevel', 'option',
     'root', 'pcHtml', 'render', 'bindEvents', 'innerHTML', 'ids', 'sel', 'list', "<div class='btnWrapper ",
     'btnWrapper', "'>\n                            <button type='button'\n                                class='btn ",
     'btn', "'\n                                data-listIndex='", "'\n                                data-verifyId='",
     '\n            <div id=', 'globalCombinationWrapper', "'>\n                <div class='titleWrapper ",
     'titleWrapper', "'>\n                    <p class='title ", 'title',
     "'>为了您的账号安全</p>\n                    <p class='title ",
     "'>请选择一种方式完成验证</p>\n                </div>\n                <div id=", '>\n                    ',
     '\n                </div>\n            </div>\n        ',
     "'>\n                            <div class='cententWrapper ", 'cententWrapper',
     "'>\n                                <span class='title ",
     "</span>\n                                <span class='subtitle ", 'subtitle',
     "'>为了完成验证,需要您提供多项信息</span>\n                            </div>\n                            <button type='button'\n                                class='btn ",
     "'>立即验证</button>\n                        </div>", "></div>\n            <div class='",
     'globalPCCombinationWrapper', "'>\n                <div class='", "'>\n                    <p class='",
     "'>为了您的账号安全请选择一种方式完成验证</p>\n                </div>\n                <div id=", " class='",
     "'>\n                    ", 'forEach', 'desc', 'verifyid', 'dataset', 'listindex', 'data-verifyid', 'getAttribute',
     '_yoda_listIndex', 'styles', 'getElementById', 'bindEvent', 'handlerClick', 'isLoading',
     "\n        <div class='globalLoadModel ", 'globalLoadModel', "'>\n            <div class='loadCircle ",
     "'>\n                <div class='circle ", 'circle', "'></div>\n                <div class='circle2 ",
     "'></div>\n                <div class='circle3 ", 'circle4', "'></div>\n                <div class='circle5 ",
     'circle5', "'></div>\n                <div class='circle6 ", 'circle6', 'circle7',
     "'></div>\n                <div class='circle8 ", "'></div>\n                <div class='circle9 ", 'circle9',
     'yodaSel', 'yodaTip', '#FFB000', 'GROUP', 'theme', '#333',
     '<div style="text-align: center;">\n                        <button type=\'button\' id=\'toggleBtn\'\n                            style=\'padding: .3em .8em; border: 1px solid #999; border-radius: .3em; background: transparent; margin: .6em auto; outline: none; color: ',
     '; border-color: ', ";'>切换验证方式</button>\n                    </div>",
     '\n            <div style=\'width: 100vw; height: 100vh; text-align: center;\n                        background: url(https://s3plus.meituan.com/v1/mss_f231eb419c414559a1837748d11d4312/yoda-resources/errorBg.png) center center no-repeat;\'>\n                <div style="padding-top: 20%;">\n                    <p style="line-height: 2em;font-size: 1.2em;font-weight: bold; color: #333;">出错了</p>\n                    <p style="line-height: 2em; font-size: 1em; color: #333;">',
     '</p>\n                    ', 'bindClick', 'toggleBtn', 'riskLevel', 'jump', 'sendBatch', '_yoda_config',
     'callUrl', 'code=', 'category', 'verifyTime', 'page', 'createbgImage', 'imageLoadError', '加载icon异常】', 'loadImg',
     'staging', 'https://verify-test.meituan.com', 'dev', 'ppe', '//verify.inf.ppe.sankuai.com', 'yodatest',
     '//yoda-yoda.test.meituan.com', 'yodapro', 'http://verify-in.vip.sankuai.com', 'https://verify.meituan.com',
     '&action=', '&randomId=', 'fetchBlob', 'onErrorHandle', 'failCallbackFun', 'failCallbackUrl', 'group', 'verify',
     'isDegrade', '_token', 'callHandle', 'onVerifySuccess', 'requestCode', 'func', 'knbFun', 'nextVerifyMethodId',
     'response_code', '103', 'response_code=', '&request_code=', 'succCallbackUrl', 'succCallbackKNBFun', 'toFailure',
     'imageNode', 'imgTitleNode', 'withCredentials', 'blob', 'URL', 'revokeObjectURL', 'catMetricCaptcha',
     'createObjectURL', 'getResponseHeader', 'Picinfo', '【图片异常】:加载图片失败Error', 'abort', 'last', 'isDoubleTap',
     'changedTouches', 'abs', 'preventDefault', 'touchend', 'tap', 'removeHandler', 'outline', 'content',
     'timeoutCount', 'count', 'firstTimeStamp', 'moveingBarX', 'maxLeft', 'isDrag', 'doms', 'yodaSliderTip',
     'yodaBoxWrapper', 'yodaMoveingBar', 'customStyle', 'whiteDuration', 'loading', 'c_techportal_verify',
     'b_techportal_whiteDuration_mv', 'initSlider', 'mounted',
     'https://s3plus.meituan.net/v1/mss_f231eb419c414559a1837748d11d4312/yoda-resources/slider/m_key.png', 'then',
     'localStorage', 'getItem', '__api_check__', ' : undefined', ' : ', ' : function', ' : null', 'event', ' | ',
     'slider', 'slider.api', 'moveingbar', 'startDrag', 'drag', 'i版上显示了PC版的滑动', 'timeoutListen', 'maxContainer',
     'targetTouches', 'stopDrag', 'moveDrag', 'mouseup', 'orientation', 'getBoundingClientRect', 'top', 'onStart',
     'globalTimer', 'oneFingerSure', 'onMove', 'cancelable', 'dealMove', 'setBoxPosition', 'translateX(', 'px)',
     'actualMove', 'boxLoading', 'showMessage', 'backToStart', 'easeOutCubic', 'transform', 'startX', 'startY', 'env',
     'trajectory', 'initTimeStamp', 'point', 'Timestamp', 'timeout', 'resultHandle', 'SINGLE', 'MULTIPLE', 'boxError',
     'code', 'swap', 'listeningOriChange', 'orientationchange', 'delLastItem', 'succCallbackFun', 'isMobile',
     'YodaSeed', '【slider加载图片异常】', 'className', 'boxLoading ', 'boxOk', 'boxStatic ', 'boxError ', 'moveingBarError ',
     'moveingBarError', 'null', 'round', 'Object.defineProperty called on non-object', '__defineGetter__',
     '__defineSetter__', 'ArrayBuffer size is not a small enough positive integer.', '_bytes',
     'length is not a small enough positive integer.', '_setter', '_getter', 'byteOffset out of range',
     'length of buffer minus byteOffset not a multiple of the element size',
     'byteOffset and length reference an area beyond the end of the buffer', 'from',
     'TYPED_ARRAY_POLYFILL_NO_ARRAY_ACCESSORS', '_unpack', 'Not enough arguments', 'copyWithin', 'every', 'find',
     'findIndex', 'Offset plus length of array is out of range', 'Unexpected argument type(s)', 'some', 'documentMode',
     'Int8Array', 'Uint8ClampedArray', 'Int16Array', 'Uint16Array', 'Int32Array', 'Uint32Array',
     'Array index out of range', 'getUint8', 'getUint16', 'getInt16', 'getInt32', 'getFloat32', 'setUint8', 'setUint32',
     'setInt32', 'setFloat64']

可以发现我们上面检测的好几个特征值都在这个里面,也就是说美团通过字符串加解密的方式,让一部分我们本应该替换的特征值没有生效,解决这个问题也很简单,你不是喜欢base64吗,那么我们把上面的特征值经过base64编码后的字符串也都替换掉。

import base64
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_']

for i in range(len(detectList)):
    detectList.append(str(base64.b64encode(bytes(detectList[i], 'utf8')), 'utf8'))


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"')

重新试试滑块 完美通过

不少朋友对滑块轨迹有些头疼,这里贴上源码,通过率勉强够用,仅供参考

import sys
import argparse
import numpy as np
import random
import asyncio
from pyppeteer import launch, launcher
from fake_useragent import UserAgent

launcher.DEFAULT_ARGS.remove("--enable-automation")


def ease_out_quad(x):
    return 1 - (1 - x) * (1 - x)


def get_tracks2(distance, seconds, ease_func=None):
    """轨迹离散分布的数学生成"""
    distance += 20
    tracks = [0]
    offsets = [0]
    for t in np.arange(0.0, seconds, 0.1):
        offset = round(ease_func(t / seconds) * distance)
        tracks.append(offset - offsets[-1])
        offsets.append(offset)
    tracks.extend([-3, -2, -3, -2, -2, -2, -2, -1, -0, -1, -1, -1])
    return tracks


async def page_init(page):
    """初始化页面特征值"""
    await page.evaluateOnNewDocument('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')
    await page.evaluateOnNewDocument('''() =>{ window.navigator.chrome = { runtime: {},  }; }''')
    await page.evaluateOnNewDocument('''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ["zh-CN", "zh"] }); }''')
    await page.evaluateOnNewDocument('''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')


async def try_validation(page, distance=200, count=0):
    """滑块验证
    :param page:
    :param distance: 滑动距离
    :param count: 滑动次数
    :return:
    """
    if count == 10:
        # 最大滑动次数
        return False
    await page.waitForXPath('//*[@id="yodaBox"]')
    btn_position = await page.evaluate('''
       () =>{
        return {
         x: document.querySelector('#yodaBox').getBoundingClientRect().x,
         y: document.querySelector('#yodaBox').getBoundingClientRect().y,
         width: document.querySelector('#yodaBox').getBoundingClientRect().width,
         height: document.querySelector('#yodaBox').getBoundingClientRect().height
         }}
        ''')
    x = btn_position['x'] + btn_position['width'] / 2
    y = btn_position['y'] + btn_position['height'] / 2
    await page.mouse.move(x, y)
    await page.mouse.down()
    x_track = get_tracks2(distance, random.randint(2, 4), ease_out_quad)
    y_track = []
    _x, _y = x, y
    for _ in x_track:
        y_track.append(random.randint(15, 50))
    while x_track:
        _x += x_track.pop(0)
        _y += y_track.pop(0)
        await page.mouse.move(_x, _y)

    await page.waitFor(2000)
    content = await page.content()
    if '拒绝操作' in content:
        print('请求异常')
        await page.evaluate('location.reload();')
        return await try_validation(page, distance, count + 1)
    if '验证失败' in content:
        print('验证失败')
        await page.mouse.up()
        return await try_validation(page, distance, count + 1)
    if '404' in content:
        return True


async def main(**kwargs):
    ua = UserAgent()
    args = ['--no-sandbox', '--disable-infobars']
    if proxyPort:
        args.append('--proxy-server=127.0.0.1:%s' % proxyPort)
    _kwargs = {'headless': False, 'args': args}
    _kwargs.update(kwargs)
    browser = await launch(_kwargs)
    page = await browser.pages()
    page = page[0]
    await page.setUserAgent(ua.chrome)
    await page.goto(url)
    if 'verify' not in page.url:
        print('轨迹验证成功')
    else:
        await page_init(page)
        r = await try_validation(page)
        if r:
            print('轨迹验证成功')
    await page.close()
    await browser.close()


if __name__ == '__main__':
    url = 'https://apimobile.meituan.com/group/v4/poi/pcsearch/'
    parser = argparse.ArgumentParser()
    parser.add_argument('-P', '--port', type=int, help='代理层端口')
    args = parser.parse_args()
    proxyPort = args.port
    if sys.version_info >= (3, 7):
        asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        loop.run_until_complete(main())
posted @ 2020-07-27 12:27  秋叶红了  阅读(5490)  评论(31编辑  收藏  举报