在有界面瀏覽器模擬美團滑塊滑動可以直接通過(此處使用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())