zoukankan      html  css  js  c++  java
  • 爬虫系列之第3章-Selenium模块

    简介

    selenium最初是一个自动化测试工具,而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题 selenium本质是通过驱动浏览器,完全模拟浏览器的操作,比如跳转、输入、点击、下拉等,来拿到网页渲染之后的结果,可支持多种浏览器

    安装

    1 下载驱动

    http://npm.taobao.org/mirrors/chromedriver/2.35/ 

    if mac系统:

    然后将解压后的chromedriver移动到/usr/local/bin目录下 

    if window系统:

    下载chromdriver.exe放到python安装路径的scripts目录中即可,注意最新版本是2.38,并非2.9

    2 安装pip包

    pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple selenium

    注意:selenium3默认支持的webdriver是Firfox,而Firefox需要安装geckodriver 下载链接

    1 简单使用

    from selenium import webdriver
    from selenium.webdriver.common.by import By  # 按照什么方式查找,By.ID,By.CSS_SELECTOR
    from selenium.webdriver.common.keys import Keys  # 键盘按键操作
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.support.wait import WebDriverWait  # 等待页面加载某些元素
    import time
    
    browser = webdriver.Chrome()
    
    try:
        browser.get("https://www.jd.com")
        input_tag = browser.find_element_by_id("key")  # 找到id为key的标签
        input_tag.send_keys("美女")  # 在输入框内输入“美女”
        input_tag.send_keys(Keys.ENTER)  # 点击回车
        wait = WebDriverWait(browser, 10)  # 显示等待,(就是等页面全部加载出来在记性下一步操作)
        wait.until(EC.presence_of_element_located((By.ID, "J_goodsList")))  # 等到id为J_goodsList的元素加载完毕,最多等10秒
        time.sleep(8)
    finally:
        browser.close()

    Selenium支持非常多的浏览器,如Chrome、Firefox、Edge等,还有Android、BlackBerry等手机端的浏览器。另外,也支持无界面浏览器PhantomJS。

    from selenium import webdriver
      
    browser = webdriver.Chrome()
    browser = webdriver.Firefox()
    browser = webdriver.Edge()
    browser = webdriver.PhantomJS()
    browser = webdriver.Safari()

     2 元素定位

    webdriver 提供了一系列的元素定位方法,常用的有以下几种:

     id
     name
     class name
     tag name
     link text
     partial link text
     xpath
     css selector

    分别对应python webdriver 中的方法为:

    from selenium import webdriver
    
    
    browser = webdriver.Chrome()
    browser.get("https://www.jd.com")
    tags = browser.find_elements_by_id("navitems")  # 找到id为xxx的
    tags = browser.find_elements_by_class_name("text")  # 找到类为 xxx的
    tags = browser.find_elements_by_name("keywords")
    tags = browser.find_elements_by_link_text("")  # 单独找到text文本
    tags = browser.find_elements_by_partial_link_text("")  # 全部查找(相当于模糊匹配)
    tags = browser.find_elements_by_css_selector(".cate_menu_item a")  # css 查找
    tags = browser.find_elements_by_xpath("//*[@id='logo']/h1/a")  # 配合xpath使用
    # 通过文本找到标签
    tags = browser.find_elements(By.CLASS_NAME, "text")
    for tag in tags:
        print(tag.tag_name)
    
    browser.close()

    注意

    1、find_element_by_xxx找的是第一个符合条件的标签,find_elements_by_xxx找的是所有符合条件的标签。

    2、根据ID、CSS选择器和XPath获取,它们返回的结果完全一致。

    3、另外,Selenium还提供了通用方法find_element(),它需要传入两个参数:查找方式By和值。实际上,它就是find_element_by_id()这种方法的通用函数版本,比如find_element_by_id(id)就等价于find_element(By.ID, id),二者得到的结果完全一致。

    3 节点交互

    Selenium可以驱动浏览器来执行一些操作,也就是说可以让浏览器模拟执行一些动作。比较常见的用法有:输入文字时用send_keys()方法,清空文字时用clear()方法,点击按钮时用click()方法。示例如下:

    from selenium import webdriver
    import time
     
    browser = webdriver.Chrome()
    browser.get('https://www.taobao.com')
    input = browser.find_element_by_id('q')
    input.send_keys('MAC')
    time.sleep(1)
    input.clear()
    input.send_keys('IPhone')
    button = browser.find_element_by_class_name('btn-search')
    button.click()

    常见节点的动作操作

    4 动作链

    在上面的实例中,一些交互动作都是针对某个节点执行的。比如,对于输入框,我们就调用它的输入文字和清空文字方法;对于按钮,就调用它的点击方法。其实,还有另外一些操作,它们没有特定的执行对象,比如鼠标拖曳、键盘按键等,这些动作用另一种方式来执行,那就是动作链。

    比如,现在实现一个节点的拖曳操作,将某个节点从一处拖曳到另外一处,可以这样实现:

    from selenium.webdriver import ActionChains
    import time
    
    browser = webdriver.Chrome()
    url = "http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable"
    browser.get(url)
    browser.switch_to.frame("iframeResult")
    source = browser.find_element_by_css_selector('#draggable')  # 小方块
    target = browser.find_element_by_css_selector("#droppable")  # 大方块
    
    actions = ActionChains(browser)
    
    actions.click_and_hold(source)
    
    for i in range(5):
        actions.move_by_offset(xoffset=17, yoffset=0).perform()
        time.sleep(0.5)
    
    actions.release()

    更多的动作链操

    5 执行JavaScript

    对于某些操作,Selenium API并没有提供。比如,下拉进度条,它可以直接模拟运行JavaScript,此时使用execute_script()方法即可实现,代码如下:

    from selenium import webdriver
     
    browser = webdriver.Chrome()
    browser.get('https://www.jd.com/')
    browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
    browser.execute_script('alert("123")')

    6 获取节点信息

    通过page_source属性可以获取网页的源代码,接着就可以使用解析库(如正则表达式、Beautiful Soup、pyquery等)来提取信息了。

    不过,既然Selenium已经提供了选择节点的方法,返回的是WebElement类型,那么它也有相关的方法和属性来直接提取节点信息,如属性、文本等。这样的话,我们就可以不用通过解析源代码来提取信息了,非常方便。

    from selenium import webdriver
    from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTOR
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素
    
    browser=webdriver.Chrome()
    
    browser.get('https://www.amazon.cn/')
    
    wait=WebDriverWait(browser,10)
    wait.until(EC.presence_of_element_located((By.ID,'cc-lm-tcgShowImgContainer')))
    
    tag=browser.find_element(By.CSS_SELECTOR,'#cc-lm-tcgShowImgContainer img')
    
    #获取标签属性,
    print(tag.get_attribute('src'))
    #获取标签ID,位置,名称,大小(了解)
    print(tag.id)
    print(tag.location)
    print(tag.tag_name)
    print(tag.size)
    
    
    browser.close()

    7 延时等待

    在Selenium中,get()方法会在网页框架加载结束后结束执行,此时如果获取page_source,可能并不是浏览器完全加载完成的页面,如果某些页面有额外的Ajax请求,我们在网页源代码中也不一定能成功获取到。所以,这里需要延时等待一定时间,确保节点已经加载出来。这里等待的方式有两种:一种是隐式等待,一种是显式等待。

    隐式等待:

    当使用隐式等待执行测试的时候,如果Selenium没有在DOM中找到节点,将继续等待,超出设定时间后,则抛出找不到节点的异常。换句话说,当查找节点而节点并没有立即出现的时候,隐式等待将等待一段时间再查找DOM,默认的时间是0。示例如下:

    from selenium import webdriver
    from selenium.webdriver import ActionChains
    from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTOR
    from selenium.webdriver.common.keys import Keys #键盘按键操作
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素
    
    browser=webdriver.Chrome()
    
    #隐式等待:在查找所有元素时,如果尚未被加载,则等10秒
    browser.implicitly_wait(10)
    
    browser.get('https://www.baidu.com')
    input_tag=browser.find_element_by_id('kw')
    input_tag.send_keys('美女')
    input_tag.send_keys(Keys.ENTER)
    
    contents=browser.find_element_by_id('content_left') #没有等待环节而直接查找,找不到则会报错
    print(contents)
    
    browser.close()

    显示等待:

    隐式等待的效果其实并没有那么好,因为我们只规定了一个固定时间,而页面的加载时间会受到网络条件的影响。这里还有一种更合适的显式等待方法,它指定要查找的节点,然后指定一个最长等待时间。如果在规定时间内加载出来了这个节点,就返回查找的节点;如果到了规定时间依然没有加载出该节点,则抛出超时异常。

    from selenium import webdriver
    from selenium.webdriver import ActionChains
    from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTOR
    from selenium.webdriver.common.keys import Keys #键盘按键操作
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素
    
    browser=webdriver.Chrome()
    browser.get('https://www.baidu.com')
    
    
    input_tag=browser.find_element_by_id('kw')
    input_tag.send_keys('美女')
    input_tag.send_keys(Keys.ENTER)
    
    
    #显式等待:显式地等待某个元素被加载
    wait=WebDriverWait(browser,10)
    wait.until(EC.presence_of_element_located((By.ID,'content_left')))
    
    contents=browser.find_element(By.CSS_SELECTOR,'#content_left')
    print(contents)
    
    
    browser.close()

    关于等待条件,其实还有很多,比如判断标题内容,判断某个节点内是否出现了某文字等。更多查看

     8 前进和后退 

    平常使用浏览器时都有前进和后退功能,Selenium也可以完成这个操作,它使用back()方法后退,使用forward()方法前进。示例如下:

    # 模拟浏览器的前进后退
    import time
    from selenium import webdriver
     
    browser=webdriver.Chrome()
    browser.get('https://www.baidu.com')
    browser.get('https://www.taobao.com')
    browser.get('http://www.sina.com.cn/')
     
    browser.back()
    time.sleep(10)
    browser.forward()
    browser.close()

    9 Cookies

    使用Selenium,还可以方便地对Cookies进行操作,例如获取、添加、删除Cookies等。示例如下:

    from selenium import webdriver
     
    browser = webdriver.Chrome()
    browser.get('https://www.zhihu.com/explore')
    print(browser.get_cookies())
    browser.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'germey'})
    print(browser.get_cookies())
    browser.delete_all_cookies()
    print(browser.get_cookies())

    10 异常处理

    from selenium import webdriver
    from selenium.common.exceptions import TimeoutException,NoSuchElementException,NoSuchFrameException
    
    try:
        browser=webdriver.Chrome()
        browser.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
        browser.switch_to.frame('iframssseResult')
    
    except TimeoutException as e:
        print(e)
    except NoSuchFrameException as e:
        print(e)
    finally:
        browser.close()

    案例讲解

    滑动验证码的破解

    from selenium import webdriver
    from selenium.webdriver.support.ui import WebDriverWait # 等待元素加载的
    from selenium.webdriver.common.action_chains import ActionChains  #拖拽
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.common.exceptions import TimeoutException, NoSuchElementException
    from selenium.webdriver.common.by import By
    from PIL import Image
    import requests
    import re
    import random
    from io import BytesIO
    import time
    
    
    def merge_image(image_file,location_list):
        """
         拼接图片
        """
        im = Image.open(image_file)
        im.save('code.jpg')
        new_im = Image.new('RGB',(260,116))
        # 把无序的图片 切成52张小图片
        im_list_upper = []
        im_list_down = []
        # print(location_list)
        for location in location_list:
            # print(location['y'])
            if location['y'] == -58: # 上半边
                im_list_upper.append(im.crop((abs(location['x']),58,abs(location['x'])+10,116)))
            if location['y'] == 0:  # 下半边
                im_list_down.append(im.crop((abs(location['x']),0,abs(location['x'])+10,58)))
    
        x_offset = 0
        for im in im_list_upper:
            new_im.paste(im,(x_offset,0))  # 把小图片放到 新的空白图片上
            x_offset += im.size[0]
    
        x_offset = 0
        for im in im_list_down:
            new_im.paste(im,(x_offset,58))
            x_offset += im.size[0]
        #new_im.show()
        return new_im
    
    def get_image(driver,div_path):
        '''
        下载无序的图片  然后进行拼接 获得完整的图片
        :param driver:
        :param div_path:
        :return:
        '''
        background_images = driver.find_elements_by_xpath(div_path)
        location_list = []
        for background_image in background_images:
            location = {}
            result = re.findall('background-image: url("(.*?)"); background-position: (.*?)px (.*?)px;',background_image.get_attribute('style'))
            # print(result)
            location['x'] = int(result[0][1])
            location['y'] = int(result[0][2])
    
            image_url = result[0][0]
            location_list.append(location)
        image_url = image_url.replace('webp','jpg')
        # '替换url http://static.geetest.com/pictures/gt/579066de6/579066de6.webp'
        image_result = requests.get(image_url).content
        image_file = BytesIO(image_result) # 是一张无序的图片
        image = merge_image(image_file,location_list)
    
        return image
    
    
    def get_track(distance):
    
        # 初速度
        v=0
        # 单位时间为0.2s来统计轨迹,轨迹即0.2内的位移
        t=0.2
        # 位移/轨迹列表,列表内的一个元素代表0.2s的位移
        tracks=[]
        tracks_back=[]
        # 当前的位移
        current=0
        # 到达mid值开始减速
        mid=distance * 7/8
        print("distance",distance)
        global random_int
        random_int=8
        distance += random_int # 先滑过一点,最后再反着滑动回来
    
        while current < distance:
            if current < mid:
                # 加速度越小,单位时间的位移越小,模拟的轨迹就越多越详细
                a = random.randint(2,5)  # 加速运动
            else:
                a = -random.randint(2,5) # 减速运动
            # 初速度
            v0 = v
            # 0.2秒时间内的位移
            s = v0*t+0.5*a*(t**2)
            # 当前的位置
            current += s
            # 添加到轨迹列表
            if round(s)>0:
                tracks.append(round(s))
            else:
                tracks_back.append(round(s))
    
    
            # 速度已经达到v,该速度作为下次的初速度
            v= v0+a*t
    
            print("tracks:",tracks)
            print("tracks_back:",tracks_back)
            print("current:",current)
    
        # 反着滑动到大概准确位置
    
        tracks_back.append(distance-current)
        tracks_back.extend([-2,-5,-8,])
    
        return tracks,tracks_back
    
    
    def get_distance(image1,image2):
        '''
           拿到滑动验证码需要移动的距离
          :param image1:没有缺口的图片对象
          :param image2:带缺口的图片对象
          :return:需要移动的距离
          '''
        # print('size', image1.size)
    
        threshold = 50
        for i in range(0,image1.size[0]):  # 260
            for j in range(0,image1.size[1]):  # 160
                pixel1 = image1.getpixel((i,j))
                pixel2 = image2.getpixel((i,j))
                res_R = abs(pixel1[0]-pixel2[0]) # 计算RGB差
                res_G = abs(pixel1[1] - pixel2[1])  # 计算RGB差
                res_B = abs(pixel1[2] - pixel2[2])  # 计算RGB差
                if res_R > threshold and res_G > threshold and res_B > threshold:
                    return i  # 需要移动的距离
    
    
    def main_check_code(driver,element):
        """
        拖动识别验证码
        :param driver:
        :param element:
        :return:
        """
    
        login_btn = driver.find_element_by_class_name('js-login')
        login_btn.click()
    
        element = WebDriverWait(driver, 30, 0.5).until(EC.element_to_be_clickable((By.CLASS_NAME, 'gt_guide_tip')))
        slide_btn = driver.find_element_by_class_name('gt_guide_tip')
        slide_btn.click()
    
    
    
        image1 = get_image(driver, '//div[@class="gt_cut_bg gt_show"]/div')
        image2 = get_image(driver, '//div[@class="gt_cut_fullbg gt_show"]/div')
        # 图片上 缺口的位置的x坐标
    
        # 2 对比两张图片的所有RBG像素点,得到不一样像素点的x值,即要移动的距离
        l = get_distance(image1, image2)
        print('l=',l)
    
        # 3 获得移动轨迹
        track_list = get_track(l)
        print('第一步,点击滑动按钮')
        element = WebDriverWait(driver, 30, 0.5).until(EC.element_to_be_clickable((By.CLASS_NAME, 'gt_slider_knob')))
        ActionChains(driver).click_and_hold(on_element=element).perform()  # 点击鼠标左键,按住不放
        import time
        time.sleep(0.4)
        print('第二步,拖动元素')
        for track in track_list[0]:
             ActionChains(driver).move_by_offset(xoffset=track, yoffset=0).perform()  # 鼠标移动到距离当前位置(x,y)
        #time.sleep(0.4)
        for track in track_list[1]:
              ActionChains(driver).move_by_offset(xoffset=track, yoffset=0).perform()  # 鼠标移动到距离当前位置(x,y)
              time.sleep(0.1)
        import time
        time.sleep(0.6)
        # ActionChains(driver).move_by_offset(xoffset=2, yoffset=0).perform()  # 鼠标移动到距离当前位置(x,y)
        # ActionChains(driver).move_by_offset(xoffset=8, yoffset=0).perform()  # 鼠标移动到距离当前位置(x,y)
        # ActionChains(driver).move_by_offset(xoffset=2, yoffset=0).perform()  # 鼠标移动到距离当前位置(x,y)
        print('第三步,释放鼠标')
        ActionChains(driver).release(on_element=element).perform()
        time.sleep(1)
    
    def main_check_slider(driver):
        """
        检查滑动按钮是否加载
        :param driver:
        :return:
        """
        while True:
            try :
                driver.get('https://www.huxiu.com/')
                element = WebDriverWait(driver, 30, 0.5).until(EC.element_to_be_clickable((By.CLASS_NAME, 'js-login')))
                if element:
                    return element
            except TimeoutException as e:
                print('超时错误,继续')
                time.sleep(5)
    
    if __name__ == '__main__':
    
        try:
            count = 3  # 最多识别3次
            driver = webdriver.Chrome()
            while count > 0:
                # 等待滑动按钮加载完成
                element = main_check_slider(driver)
                main_check_code(driver,element)
                try:
                    success_element = (By.CSS_SELECTOR, '.gt_success')
                    # 得到成功标志
                    success_images = WebDriverWait(driver,3).until(EC.presence_of_element_located(success_element))
                    if success_images:
                        print('成功识别!!!!!!')
                        count = 0
                        import sys
                        sys.exit()
                except Exception as e:
                    print('识别错误,继续')
                    count -= 1
                    time.sleep(1)
            else:
                print('too many attempt check code ')
                exit('退出程序')
        finally:
            driver.close()
    View Code
  • 相关阅读:
    Maidsafe-去中心化互联网白皮书
    The Top 20 Cybersecurity Startups To Watch In 2021 Based On Crunchbase
    Top 10 Blockchain Security and Smart Contract Audit Companies
    The 20 Best Cybersecurity Startups To Watch In 2020
    Blockchain In Cybersecurity: 11 Startups To Watch In 2019
    004-STM32+BC26丨260Y基本控制篇(阿里云物联网平台)-在阿里云物联网平台上一型一密动态注册设备(Android)
    涂鸦开发-单片机+涂鸦模组开发+OTA
    000-ESP32学习开发-ESP32烧录板使用说明
    03-STM32+Air724UG远程升级篇OTA(阿里云物联网平台)-STM32+Air724UG使用阿里云物联网平台OTA远程更新STM32程序
    03-STM32+Air724UG远程升级篇OTA(自建物联网平台)-STM32+Air724UG实现利用http/https远程更新STM32程序(TCP指令,单片机程序检查更新)
  • 原文地址:https://www.cnblogs.com/xiaohei001/p/9728347.html
Copyright © 2011-2022 走看看