Python + selenium 破解极验点击验证码

0x00 尝试

本来是打算截图定位的,模板匹配的函数都写好了,参见python+opencv 暴力模板匹配,但是后来发现,这个验证码,居然是可以通过xpath直接定位过去的。那直接上selenium模拟用户点击就好了。代码参见github

点击验证码

0x01 重来

image.png

那既然可以定位到元素,就尝试直接鼠标定位过去,然后点击

btn = driver.find_element_by_xpath('//div[@aria-label="点击按钮进行验证"]')
ActionChains(driver).move_to_element(btn).perform()

不过,自然不可能这么简单。果然,认证失败,需要进行二次验证才可以,二次验证也是极验验证码,会随机出现两种,一种选字验证码,一种滑块验证码。不过这次我们主要搞这个点击的验证码。
于是猜测它是通过鼠标移动情况来辨别是否是人机。为此,我进行了一项测试,鼠标迅速移动到按钮上不减速直接停止,让自己看起来像是个机器,果然,多次尝试都是认证失败。然后起始的时候慢加速,快到按钮的时候减速,缓慢停止,就像遵守交通规则的车一样,然后就轻而易举的通过验证了。

0x02 思路

那既然我们的猜想得到了初步的验证,下面就是想办法模仿鼠标的移动了。但是selenium是通过指定鼠标相对位移来移动鼠标的,而且selenium也并没提供获取鼠标位置的函数或者方法。于是初步想法是这样的:

  1. 鼠标先定位到元素
  2. 模仿用户鼠标移动,随便绕一圈再回来
  3. 点击通过验证码

思路很清晰,开始实现就行了。第一步和第三步都很简单,主要重点放在第二步:模拟鼠标移动。因为我们只需要每一步的相对位置,那么假设用户的初试鼠标位置为(0, 0),然后下面的事情就是,周期性记录鼠标位置,然后计算出相邻坐标点的差值,就是相对位移了。

import pyautogui as pag
import json
import time

class MouseTracker(object):
    """
    This function will generate tracks which is used to move mouse like human in selenium.
    the date will save to a string file. The format of the result:
    [[(x1,y1), delay1], [(x2, y2), delay2], [(x3, y3), delay3]...]
    """
    def __init__(self, filename='track.txt', period=0.01, max_stop_time=0.5):
        """
        :para filename: the filename to save the track of mouse.
        :para period: the fixed time to record mouse position.
        :para max_stop_time: the max_time user stayed which will be considerd as finishing record.
        """

        self.period = period
        self.filename = filename
        self.stop_num = int(max_stop_time/period)
        self.res = []

        # record start point of mouse
        self.start_point = tuple(pag.position())
        # this variable is to previous point 
        self.previous_point = self.start_point
        # save the record of track
        self.track = []
        # save the interval between each point
        self.sleep_time = []
        # calculate loop times
        self.track.append(self.start_point)

    def record(self):
        """
        Record the relative displacement of user's mouse each fixed time.
        """

        print('Moving your mouse to start record, stop moving to finish')

        # record the number of same position.
        num = 0
        # dead loop, break when staying longer than max_stop_time
        while True:
            new = tuple(pag.position())
            time.sleep(self.period)
            if new == self.start_point:
                continue
            if new == self.previous_point:
                num = num + 1
            else:
                self.track.append(new)
                self.sleep_time.append(num*self.period)
                num = 1
            self.previous_point = new
            if num > self.stop_num:
                break;
        self.sleep_time.append(0)

        # A function used to minus two point, like (3,2)-(2,1) is (1,1)
        tuple_minus = lambda x,y:(x[0]-y[0],x[1]-y[1])
        # save generator to speed up
        _range = range(1,len(self.track))
        # get relative displacement, that is the diff coordinate of neightbour
        diff = [tuple_minus(self.track[x],self.track[x-1]) for x in _range]
        # make sure the length of diff list is eaqual to sleep_time's
        diff.insert(0,(0,0))
        # get results list
        for i in range(len(self.track)):
            self.res.append((diff[i], self.sleep_time[i]))


    def print_res(self):
        for i in self.res:
            print(i)

    # save results to file
    def save(self):
        with open(self.filename, 'w') as f:
            json.dump(self.res, f)
            
    def generate(self):
        self.print_res()
        self.record()
        self.save()

if __name__ == "__main__":
    mouseTracker().generate()

然后运行程序,我们做一回遵守“交通规则”的鼠标,缓加速,慢停止,移动一圈鼠标回到原点附近的位置。这样就记录下来鼠标每一个周期移动的相对位置。这里经过多次调试,选择了默认周期为0.01s,也可以初始化的时候传入自定义的记录周期。鼠标停止一定时间会认为结束记录,即类的初始化中的max_stop_time参数。一般设置成0.5~1s不会觉得冗长,刚刚好。默认设置为1s。

0x03 破解

接下来就是利用selenium来模拟用户登陆. 首先找到登陆页面. 然后模拟用户输入和提交表单. selenium的用法就不赘述了. 直接上代码

class Crack(object):                                                                                           
    """                                                                                                        
    Crack geetest click CAPTCHA and auto login.                                                                
    Please make sure you have generated the mouse-track file in this path.                                     
    """                                                                                                        
    def __init__(self, username, password, trackfilename='track.txt', proxy=''):                               
        # Get login information and init variable.                                                             
        self.username = username                                                                               
        self.password = password                                                                               
        self.cookies = ''                                                                                      
                                                                                                               
        # This is login entrance                                                                               
        self.url = 'https://passport.weibo.cn/signin/login'                                                    
                                                                                                               
                                                                                                               
        # read track data                                                                                      
        self.trackfilename = trackfilename                                                                     
        self.track = []                                                                                        
        with open(self.trackfilename,'r') as f:                                                                
            self.track = json.load(f)                                                                          
                                                                                                               
        # start Chrome headless, add proxy and run in headless mode.                                           
        if proxy:                                                                                              
            chrome_options = Options()                                                                         
            #chrome_options.add_argument('--headless')                                                         
            chrome_options.add_argument('--proxy-server='+proxy)                                               
            print('proxy set sucess')                                                                          
            self.driver = webdriver.Chrome(chrome_options=chrome_options)                                      
        else:                                                                                                  
            chrome_options = Options()                                                                         
            #chrome_options.add_argument('--headless')                                                         
            self.driver = webdriver.Chrome(chrome_options=chrome_options)                                      
        self.wait = WebDriverWait(self.driver, 6)                                                              
                                                                                                               
        # Waiting for chrome to open and open login entrance.                                                  
        self.driver.implicitly_wait(5)                                                                         
        try:                                                                                                   
            self.driver.get(self.url, 10)                                                                      
        except Exception as e:                                                                                 
            print("Target URL cannot be reached: ", e)                                                         
            self.__del__()                                                                                     
                                                                                                               
    def __del__(self):                                                                                         
        """ Destroy the web browser """                                                                        
        print(self.cookies)                                                                                    
        self.driver.close()                                                                                    
                                                                                                               
    def wait_for_main_page(self):                                                                              
        """                                                                                                    
        Waiting for the loading of main page                                                                   
        :return : If the main page load in given time, return True.                                            
        """                                                                                                    
        try:                                                                                                   
            self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'main-wrap')))                      
            return True                                                                                        
        except:                                                                                                
            return False                                                                                       
                                                                                                               
    def move(self):                                                                                            
        """ Move mouse by using given track """                                                                
        for offset, sleeptime in self.track:                                                                   
            x, y = offset                                                                                      
            ActionChains(self.driver).move_by_offset(x,y).perform()                                            
            time.sleep(sleeptime)                                                                              
        ActionChains(self.driver).click().perform()                                                            
                                                                                                               
    def login(self):                                                                                           
        """ Login weibo by selenium """                                                                        
                                                                                                               
        # Waiting until the presence of login button.                                                          
        class button():                                                                                        
            def __call__(self, driver):                                                                        
                if driver.find_element_by_xpath('//*[@id="loginAction"]'):                                     
                    return True                                                                                
                else:                                                                                          
                    return False                                                                               
        WebDriverWait(self.driver, 15, 0.5).until(button())                                                    
                                                                                                               
        # Input username and password.                                                                         
        print('Inputing username and password...', end='')                                                     
        username_area = self.driver.find_element_by_xpath('//*[@id="loginName"]')                              
        username_area.send_keys(self.username)                                                                 
        time.sleep(1)                                                                                          
        psw_area = self.driver.find_element_by_xpath('//*[@id="loginPassword"]')                               
        psw_area.send_keys(self.password)                                                                      
        print('Ok')                                                                                            
                                                                                                               
        # Submit login form.                                                                                   
        print('Posint form data...', entranc)                                                                  
        btn = self.driver.find_element_by_xpath('//*[@id="loginAction"]')                                      
        btn.click()                                                                                            
        print('Ok')                                                                                            
                                                                                                               
        # If their is a CAPTCHA, then crack it.                                                                
        if self.driver.current_url.find('CAPTCHA'):                                                            
            print('CAPTCHA has been detected, need crack.')                                                    
            self.crack()                                                                                       
        else:                                                                                                  
            ret = self.wait_for_main_page()                                                                    
            self.cookies = self.driver.get_cookies()                                                           
                                                                                                               
    def crack(self):                                                                                           
        """ Crack the click CAPTCHA """                                                                        
        # Waiting for page loading.                                                                            
        class button():                                                                                        
            def __call__(self, driver):                                                                        
                if driver.find_element_by_xpath('//div[@aria-label="点击按钮进行验证"]'):                              
                    return True                                                                                
                else:                                                                                          
                    return False                                                                               
        print('Loading CAPTCHA...', end='')                                                                    
        WebDriverWait(self.driver, 10, 0.5).until(button())                                                    
        print('Compelete')                                                                                     
                                                                                                               
        # find button and move to the button                                                                   
        print('Cracking...', end='')                                                                           
        btn = self.driver.find_element_by_xpath('//div[@aria-label="点击按钮进行验证"]')                               
        ActionChains(self.driver).move_to_element(btn).perform()                                               
        self.move()                                                                                            
        ActionChains(self.driver).click().perform()                                                            
        print('Complete')                                                                                      
                                                                                                               
        # waiting from page and get cookies                                                                    
        ret = self.wait_for_main_page()                                                                        
        if ret:                                                                                                
            print('Cracking success!')                                                                         
        # Choose-Word-CAPTCHA  has been appeared, need second varification.                                    
        elif EC.presence_of_element_located((By.CLASS_NAME, 'geetest_commit_tip')):                            
            print('Cracking failed, Choose-Word-CAPTCHA has been appeared!')                                   
            self.__del__()                                                                                     
            # crack_choose_CAPTCH()                                                                            
        # Slide-CAPTCHA  has been appeared, need second varification.                                          
        elif EC.presence_of_element_located((By.CLASS_NAME, 'geetest_slider_track')):                          
            print('Cracking failed, Slider-CAPTCHA has been appeared!')                                        
            self.__del__()                                                                                     
            # crack_slide_CAPTCH()                                                                             
        else:                                                                                                  
            print('Unknown Error!')                                                                            
            self.__del__()                                                                                     
            # log_error()                                                                                      
                                                                                                               
        if ret:                                                                                                
            cookies_dict = {}                                                                                  
            cookies = self.driver.get_cookies()                                                                
            for d in cookies:                                                                                  
                cookies_dict[d['name']] = d['value']                                                           
            print('Get cookies:', cookies_dict)                                                                
            self.cookies = json.dumps(cookies_dict)                                                            
                                                                                                               
                                                                                                               
if __name__ == '__main__':                                                                                                                            
    Crack('xxxxxxxxxx@sina.com', 'xxxxxxxx').login()                                          

值得注意的, 我在类中增加了代理的参数, 就是说, 可以提供代理来破解. 因为多次实验发现, 同一个ip在短时间内连续登陆就容易出现二次验证, 二次验证码有两种形式, 一种是点选的验证码, 选择图片中出现的文字, 另外一种是滑块验证码(相对容易破解). 这两种不一定出现哪一种,不过如果登陆太频繁, 一般触发的都是第一种. 代码中分别留出了这两种二次验证的扩展位置. 日后可以加上去.

代理可以通过自己建立和维护代理池来获取, 不过可用的不是很多了. 如果使用代理的话, 就在类的初始化中传入参数:

if __name__ == '__main__':     
    # 这里是代理, ip:port    
    proxy = "xxx.xxx.xxx.xxx:xxxxx"
    proxy = "http://" + proxy                                                                              
    Crack('xxxxxxxxxx@sina.com', 'xxxxxxxx', proxy=proxy).login() 
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271