├── LICENSE ├── README.md ├── api.py ├── config.toml ├── run.py ├── server.py ├── static ├── favicon.ico ├── geetest.html └── gt.js ├── test.py └── worker.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ruoyang Xu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
接收任务
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /static/gt.js: -------------------------------------------------------------------------------- 1 | "v0.4.6 Geetest Inc."; 2 | 3 | (function (window) { 4 | "use strict"; 5 | if (typeof window === 'undefined') { 6 | throw new Error('Geetest requires browser environment'); 7 | } 8 | 9 | var document = window.document; 10 | var Math = window.Math; 11 | var head = document.getElementsByTagName("head")[0]; 12 | 13 | function _Object(obj) { 14 | this._obj = obj; 15 | } 16 | 17 | _Object.prototype = { 18 | _each: function (process) { 19 | var _obj = this._obj; 20 | for (var k in _obj) { 21 | if (_obj.hasOwnProperty(k)) { 22 | process(k, _obj[k]); 23 | } 24 | } 25 | return this; 26 | } 27 | }; 28 | 29 | function Config(config) { 30 | var self = this; 31 | new _Object(config)._each(function (key, value) { 32 | self[key] = value; 33 | }); 34 | } 35 | 36 | Config.prototype = { 37 | api_server: 'api.geetest.com', 38 | protocol: 'http://', 39 | typePath: '/gettype.php', 40 | fallback_config: { 41 | slide: { 42 | static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], 43 | type: 'slide', 44 | slide: '/static/js/geetest.0.0.0.js' 45 | }, 46 | fullpage: { 47 | static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], 48 | type: 'fullpage', 49 | fullpage: '/static/js/fullpage.0.0.0.js' 50 | } 51 | }, 52 | _get_fallback_config: function () { 53 | var self = this; 54 | if (isString(self.type)) { 55 | return self.fallback_config[self.type]; 56 | } else if (self.new_captcha) { 57 | return self.fallback_config.fullpage; 58 | } else { 59 | return self.fallback_config.slide; 60 | } 61 | }, 62 | _extend: function (obj) { 63 | var self = this; 64 | new _Object(obj)._each(function (key, value) { 65 | self[key] = value; 66 | }) 67 | } 68 | }; 69 | var isNumber = function (value) { 70 | return (typeof value === 'number'); 71 | }; 72 | var isString = function (value) { 73 | return (typeof value === 'string'); 74 | }; 75 | var isBoolean = function (value) { 76 | return (typeof value === 'boolean'); 77 | }; 78 | var isObject = function (value) { 79 | return (typeof value === 'object' && value !== null); 80 | }; 81 | var isFunction = function (value) { 82 | return (typeof value === 'function'); 83 | }; 84 | 85 | var callbacks = {}; 86 | var status = {}; 87 | 88 | var random = function () { 89 | return parseInt(Math.random() * 10000) + (new Date()).valueOf(); 90 | }; 91 | 92 | var loadScript = function (url, cb) { 93 | var script = document.createElement("script"); 94 | script.charset = "UTF-8"; 95 | script.async = true; 96 | 97 | script.onerror = function () { 98 | cb(true); 99 | }; 100 | var loaded = false; 101 | script.onload = script.onreadystatechange = function () { 102 | if (!loaded && 103 | (!script.readyState || 104 | "loaded" === script.readyState || 105 | "complete" === script.readyState)) { 106 | 107 | loaded = true; 108 | setTimeout(function () { 109 | cb(false); 110 | }, 0); 111 | } 112 | }; 113 | script.src = url; 114 | head.appendChild(script); 115 | }; 116 | 117 | var normalizeDomain = function (domain) { 118 | // special domain: uems.sysu.edu.cn/jwxt/geetest/ 119 | // return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn 120 | return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest 121 | }; 122 | var normalizePath = function (path) { 123 | path = path.replace(/\/+/g, '/'); 124 | if (path.indexOf('/') !== 0) { 125 | path = '/' + path; 126 | } 127 | return path; 128 | }; 129 | var normalizeQuery = function (query) { 130 | if (!query) { 131 | return ''; 132 | } 133 | var q = '?'; 134 | new _Object(query)._each(function (key, value) { 135 | if (isString(value) || isNumber(value) || isBoolean(value)) { 136 | q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; 137 | } 138 | }); 139 | if (q === '?') { 140 | q = ''; 141 | } 142 | return q.replace(/&$/, ''); 143 | }; 144 | var makeURL = function (protocol, domain, path, query) { 145 | domain = normalizeDomain(domain); 146 | 147 | var url = normalizePath(path) + normalizeQuery(query); 148 | if (domain) { 149 | url = protocol + domain + url; 150 | } 151 | 152 | return url; 153 | }; 154 | 155 | var load = function (protocol, domains, path, query, cb) { 156 | var tryRequest = function (at) { 157 | 158 | var url = makeURL(protocol, domains[at], path, query); 159 | loadScript(url, function (err) { 160 | if (err) { 161 | if (at >= domains.length - 1) { 162 | cb(true); 163 | } else { 164 | tryRequest(at + 1); 165 | } 166 | } else { 167 | cb(false); 168 | } 169 | }); 170 | }; 171 | tryRequest(0); 172 | }; 173 | 174 | 175 | var jsonp = function (domains, path, config, callback) { 176 | if (isObject(config.getLib)) { 177 | config._extend(config.getLib); 178 | callback(config); 179 | return; 180 | } 181 | if (config.offline) { 182 | callback(config._get_fallback_config()); 183 | return; 184 | } 185 | 186 | var cb = "geetest_" + random(); 187 | window[cb] = function (data) { 188 | if (data.status == 'success') { 189 | callback(data.data); 190 | } else if (!data.status) { 191 | callback(data); 192 | } else { 193 | callback(config._get_fallback_config()); 194 | } 195 | window[cb] = undefined; 196 | try { 197 | delete window[cb]; 198 | } catch (e) { 199 | } 200 | }; 201 | load(config.protocol, domains, path, { 202 | gt: config.gt, 203 | callback: cb 204 | }, function (err) { 205 | if (err) { 206 | callback(config._get_fallback_config()); 207 | } 208 | }); 209 | }; 210 | 211 | var throwError = function (errorType, config) { 212 | var errors = { 213 | networkError: '网络错误', 214 | gtTypeError: 'gt字段不是字符串类型' 215 | }; 216 | if (typeof config.onError === 'function') { 217 | config.onError(errors[errorType]); 218 | } else { 219 | throw new Error(errors[errorType]); 220 | } 221 | }; 222 | 223 | var detect = function () { 224 | return window.Geetest || document.getElementById("gt_lib"); 225 | }; 226 | 227 | if (detect()) { 228 | status.slide = "loaded"; 229 | } 230 | 231 | window.initGeetest = function (userConfig, callback) { 232 | 233 | var config = new Config(userConfig); 234 | 235 | if (userConfig.https) { 236 | config.protocol = 'https://'; 237 | } else if (!userConfig.protocol) { 238 | config.protocol = window.location.protocol + '//'; 239 | } 240 | 241 | // for KFC 242 | if (userConfig.gt === '050cffef4ae57b5d5e529fea9540b0d1' || 243 | userConfig.gt === '3bd38408ae4af923ed36e13819b14d42') { 244 | config.apiserver = 'yumchina.geetest.com/'; // for old js 245 | config.api_server = 'yumchina.geetest.com'; 246 | } 247 | 248 | if (isObject(userConfig.getType)) { 249 | config._extend(userConfig.getType); 250 | } 251 | jsonp([config.api_server || config.apiserver], config.typePath, config, function (newConfig) { 252 | var type = newConfig.type; 253 | var init = function () { 254 | config._extend(newConfig); 255 | callback(new window.Geetest(config)); 256 | }; 257 | 258 | callbacks[type] = callbacks[type] || []; 259 | var s = status[type] || 'init'; 260 | if (s === 'init') { 261 | status[type] = 'loading'; 262 | 263 | callbacks[type].push(init); 264 | 265 | load(config.protocol, newConfig.static_servers || newConfig.domains, newConfig[type] || newConfig.path, null, function (err) { 266 | if (err) { 267 | status[type] = 'fail'; 268 | throwError('networkError', config); 269 | } else { 270 | status[type] = 'loaded'; 271 | var cbs = callbacks[type]; 272 | for (var i = 0, len = cbs.length; i < len; i = i + 1) { 273 | var cb = cbs[i]; 274 | if (isFunction(cb)) { 275 | cb(); 276 | } 277 | } 278 | callbacks[type] = []; 279 | } 280 | }); 281 | } else if (s === "loaded") { 282 | init(); 283 | } else if (s === "fail") { 284 | throwError('networkError', config); 285 | } else if (s === "loading") { 286 | callbacks[type].push(init); 287 | } 288 | }); 289 | 290 | }; 291 | 292 | 293 | })(window); -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | from api import Geetest3 4 | 5 | def main(address="127.0.0.1", port=3333): 6 | geetest3 = Geetest3(address=address, port=port) 7 | while True: 8 | captcha = get_captcha() 9 | if captcha: 10 | result = geetest3.crack(gt=captcha.get('gt'), challenge=captcha.get('challenge'), success=captcha.get('success')) 11 | status = geetest3.status() 12 | print("=" * 100) 13 | print(f"Captcha: {captcha}") 14 | print(f"Result: {result}") 15 | print(f"Status: {status}") 16 | print("=" * 100) 17 | time.sleep(1) 18 | 19 | def get_captcha(): 20 | try: 21 | response = requests.get("https://passport.bilibili.com/web/captcha/combine?plat=5").json() 22 | if response.get('code') == 0 and response.get('data', {}).get('type') == 1: 23 | return response.get('data', {}).get('result') 24 | except: 25 | pass 26 | return None 27 | 28 | if __name__ == "__main__": 29 | main() 30 | -------------------------------------------------------------------------------- /worker.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import os 3 | import platform 4 | import random 5 | import time 6 | from concurrent.futures import ThreadPoolExecutor 7 | from PIL import Image 8 | from selenium import webdriver 9 | from selenium.webdriver.common.action_chains import ActionChains 10 | 11 | def main(address="127.0.0.1", port=3333, headless=False, workers=2, delay=2): 12 | def task(thread_id): 13 | time.sleep(thread_id * delay) 14 | driver = create_driver(f"http://{address}:{port}/task", headless=headless) 15 | while True: 16 | try: 17 | crack_geetest(driver) 18 | time.sleep(2) 19 | except: 20 | time.sleep(1) 21 | 22 | if not os.path.exists("tmp"): 23 | os.makedirs("tmp") 24 | with ThreadPoolExecutor(max_workers=workers) as executor: 25 | executor.map(task, range(workers)) 26 | 27 | def create_driver(url, headless=False): 28 | options = webdriver.ChromeOptions() 29 | options.add_argument("log-level=3") 30 | if headless: 31 | options.add_argument("headless") 32 | else: 33 | options.add_argument("disable-infobars") 34 | options.add_argument("window-size=380,460") 35 | if platform.system() == "Linux": 36 | options.add_argument("no-sandbox") 37 | driver = webdriver.Chrome(options=options) 38 | driver.get(url) 39 | return driver 40 | 41 | def crack_geetest(driver, bg_class="geetest_canvas_bg geetest_absolute", full_bg_class="geetest_canvas_fullbg geetest_fade geetest_absolute", slider_class="geetest_slider_button", success_class="geetest_success"): 42 | driver.find_element_by_class_name(slider_class) 43 | rand = str(random.randint(0, 100000000)) 44 | bg_path = save_bg(driver, bg_path=os.path.join("tmp", f"bg_{rand}.png"), bg_class=bg_class) 45 | full_bg_path = save_full_bg(driver, full_bg_path=os.path.join("tmp", f"fullbg_{rand}.png"), full_bg_class=full_bg_class) 46 | distance = get_offset(full_bg_path, bg_path) - 4 47 | os.remove(full_bg_path) 48 | os.remove(bg_path) 49 | track = get_track(distance) 50 | drag_the_ball(driver, track, slider_class=slider_class) 51 | 52 | def save_base64img(data_str, save_name): 53 | img_data = base64.b64decode(data_str) 54 | file = open(save_name, "wb") 55 | file.write(img_data) 56 | file.close() 57 | 58 | def get_base64_by_canvas(driver, class_name, contain_type): 59 | bg_img = "" 60 | while len(bg_img) < 5000: 61 | get_img_js = f"return document.getElementsByClassName(\"{class_name}\")[0].toDataURL(\"image/png\");" 62 | bg_img = driver.execute_script(get_img_js) 63 | time.sleep(0.5) 64 | if contain_type: 65 | return bg_img 66 | else: 67 | return bg_img[bg_img.find(",") + 1:] 68 | 69 | def save_bg(driver, bg_path="bg.png", bg_class="geetest_canvas_bg geetest_absolute"): 70 | bg_img_data = get_base64_by_canvas(driver, bg_class, False) 71 | save_base64img(bg_img_data, bg_path) 72 | return bg_path 73 | 74 | def save_full_bg(driver, full_bg_path="fbg.png", full_bg_class="geetest_canvas_fullbg geetest_fade geetest_absolute"): 75 | bg_img_data = get_base64_by_canvas(driver, full_bg_class, False) 76 | save_base64img(bg_img_data, full_bg_path) 77 | return full_bg_path 78 | 79 | def get_slider(driver, slider_class="geetest_slider_button"): 80 | while True: 81 | try: 82 | slider = driver.find_element_by_class_name(slider_class) 83 | break 84 | except: 85 | time.sleep(0.5) 86 | return slider 87 | 88 | def is_pixel_equal(img1, img2, x, y): 89 | pix1 = img1.load()[x, y] 90 | pix2 = img2.load()[x, y] 91 | threshold = 60 92 | if abs(pix1[0] - pix2[0] < threshold) and abs(pix1[1] - pix2[1] < threshold) and abs(pix1[2] - pix2[2] < threshold): 93 | return True 94 | else: 95 | return False 96 | 97 | def get_offset(full_bg_path, bg_path, initial_offset=39): 98 | full_bg = Image.open(full_bg_path) 99 | bg = Image.open(bg_path) 100 | left = initial_offset 101 | for i in range(left, full_bg.size[0]): 102 | for j in range(full_bg.size[1]): 103 | if not is_pixel_equal(full_bg, bg, i, j): 104 | left = i 105 | return left 106 | return left 107 | 108 | def get_track(distance): 109 | track = [] 110 | current = 0 111 | mid = distance * 3 / 4 112 | t = random.randint(2, 3) / 10 113 | v = 0 114 | while current < distance: 115 | if current < mid: 116 | a = 2 117 | else: 118 | a = -3 119 | v0 = v 120 | v = v0 + a * t 121 | move = v0 * t + 1 / 2 * a * t * t 122 | current += move 123 | track.append(round(move)) 124 | return track 125 | 126 | def drag_the_ball(driver, track, slider_class="geetest_slider_button"): 127 | slider = get_slider(driver, slider_class) 128 | ActionChains(driver).click_and_hold(slider).perform() 129 | while track: 130 | x = random.choice(track) 131 | ActionChains(driver).move_by_offset(xoffset=x, yoffset=0).perform() 132 | track.remove(x) 133 | time.sleep(0.1) 134 | imitate = ActionChains(driver).move_by_offset(xoffset=-1, yoffset=0) 135 | time.sleep(0.015) 136 | imitate.perform() 137 | # time.sleep(random.randint(6, 10) / 10) 138 | imitate.perform() 139 | time.sleep(0.04) 140 | imitate.perform() 141 | time.sleep(0.012) 142 | imitate.perform() 143 | time.sleep(0.019) 144 | imitate.perform() 145 | time.sleep(0.033) 146 | ActionChains(driver).move_by_offset(xoffset=1, yoffset=0).perform() 147 | ActionChains(driver).pause(random.randint(6, 14) / 10).release(slider).perform() 148 | 149 | if __name__ == "__main__": 150 | main() 151 | --------------------------------------------------------------------------------