美文网首页Python全栈技术
Ajax数据爬取及selenium的使用详解

Ajax数据爬取及selenium的使用详解

作者: 哈耶卡卡 | 来源:发表于2018-11-03 16:39 被阅读0次

    一、什么是AJAX

    AJAX[1],全称Asynchronous JavaScript And XML,即异步的JavaScript和XML。通过在后台与服务器进行少量的数据交换,AJAX可以使网页实现异步更新,实现网页的动态渲染。

    这意味着可以在不加载整个网页的情况下,对网页的某部分进行更新。而传统的网页如果需要更新内容,则必须重载整个网页页面。

    使用AJAX加载的网页数据,虽然将数据渲染到了浏览器中,但在网页源代码中还是看不到通过ajax加载的数据,只能看到通过url加载的数据,这使得通过requests、urllib无法正常获取网页的全部数据。

    获取ajax数据的两种方式:

    1. 直接分析ajax调用的接口,然后通过代码请求这个接口
      优点:可以直接请求到数据,不需要做一些解析工作,代码量少,性能高。
      缺点:分析接口比较复杂,特别是一些通过js混淆的接口,需要一定的js功底,容易被发现是爬虫。
    1. 使用selenium+driver(浏览器驱动)模拟浏览器行为获取数据
      优点:浏览器能请求到的数据,使用selenium也能请求到,爬虫 更稳定,且适用于所有类型的动态渲染网页[2]
      缺点:代码量多,性能低。

    二、selenium+chromedriver获取动态数据

    selenium是一个自动化测试工具,可以模拟人类在浏览器上的一些行为,自动处理浏览器上的一些行为,比如点击、填充数据等。还可以获取浏览器当前呈现的页面源码,解决动态渲染网页的数据抓取,做到可见即可爬

    而chromedriver是一个驱动Chrome浏览器的驱动程序,selenium使用它才能够驱动Chrome浏览器。
    针对不同的浏览器有不同的驱动(driver),比如Firefox的驱动geckodriver、IE的驱动IEdriver等,都可以配合selenium驱动对应的浏览器,本文以Chrome浏览器为例。

    安装Selenium+chromedriver:

    1. 安装selenium模块:pip install selenium
    2. 安装chromedriver驱动:下载chromedriver驱动后,放到不需要权限的纯英文目录下即可,但每次使用都需要指定驱动的绝对路径。也可以放到python的Scripts目录下(推荐),或者将其路径添加到环境变量中,这样每次使用也都无需指定路径。

    三、selenium使用详解

    入门检测:

    from selenium import webdriver
    
    #声明浏览器对象
    #chromedriver驱动已放入Python的Scripts目录,故无需再指定路径
    driver= webdriver.Chrome()
    #请求网页(适用所有请求类型)
    driver.get('https://www.baidu.com/')
    #获取当前浏览器渲染后的HTML代码
    print( driver.page_source )  
    #关闭当前页面
    driver.close()
    #关闭整个浏览器
    driver.quit()   
    

    selenium的常用操作:

    (一)、定位元素:

    定位单个元素:find_element
    1. find_element_by_id:根据id来查找某个元素。
      第一种方式:div_tag = driver.find_element_by_id('su')
      第二种方式:div_tag = driver.find_element(By.ID, 'su')
      注意:二者效果等价,推荐使用第一种。
      使用第二种方式需要先导入By类
      from selenium.webdriver.common.by import By

    1. find_element_by_class_name:根据类名来查找某个元素。driver.find_element_by_class_name('su')
      driver.find_element(By.CLASS_NAME, 'su')

    1. find_element_by_name:根据name属性值查找某个元素。
      driver.find_element_by_name('emile')
      driver.find_element(By.NAME, 'emile')

    1. find_element_by_tag_name:根据标签名来查找某个元素。
      driver.find_element_by_tag_name('div')
      driver.find_element(By.TAG_NAME, 'div')

    1. find_element_by_xpath:根据xpath语法查找某个元素。
      driver.find_element_by_xpath('//div')
      driver.find_element(By.XPATH, '//div')

    1. find_element_by_css_selector:根据css选择器选取元素。
      driver.find_element_by_css_selector('//div')
      driver.find_element(By.CSS_SELECTOR, '//div')
    定位多个元素:find_elements

    find_element_by_id:根据id来查找某个元素
    find_elements_by_class_name:根据类名来查找某个元素
    find_elements_by_name:根据name属性的值来查找某个元素
    find_elements_by_tag_name:根据标签名来查找某个元素
    find_elements_by_xpath:根据xpath语法来查找某个元素
    find_elements_by_css_selector:根据css选择器来选取某个元素

    注意:
    1. find_elements与find_element用法一致,只是多了一个s!
    2. find_element是获取第一个满足条件的元素,直接返回获取到的元素对象,如果找不到,则报错!
    3. find_elements是获取所有满足条件的元素,返回获取到的所有元素对象组成的列表,如果找不到,返回空列表!

    (二)、操作表单元素

    常见的表单元素:

    • 输入框:input type='text/password/email/number'
    • 按钮:button、input type='submit'
    • 复选框:checkbox、input type='checkbox'
    • 下拉列表:select
    1. 操作输入框input:
      第一步:定位标签;
      第二步:使用send_keys(value)将数据填入输入框。

    2. 操作按钮button:
      第一步:定位标签;
      第二步:执行click()点击事件。

    3. 操作复选框checkbox:
      第一步:定位标签;
      第二步:执行click()点击事件。
      如果默认是未选中,则点击后选中,再次点击则取消选中!

    4. 操作下拉列表select:
      select的元素不能直接点击,因为需要先选中元素。
      第一步:定位select标签
      第二步:导入类 selenium.webdriver.support.ui.Select
      第三步:将获取到的元素当做参数传入这个类中,创建Select对象
      第四步:使用这个对象选择下拉列表中的选项

    示例1:

    from selenium import webdriver
    from selenium.webdriver.support.ui import Select
    import time
    
    driver = webdriver.Chrome() 
    #请求网页
    driver.get('https://hao.360.cn/') 
    
    #选中输入框
    input_tag = driver.find_element_by_id('search-kw') 
    #发送数据
    input_tag.send_keys('python')
    time.sleep(3)
    #清除输入框数据
    input_tag.clear()
    
    #选中按钮
    button_tag = driver.find_element_by_id('search-btn')
    #点击按钮
    button_tag.click()   
    time.sleep(2)
    
    #注意:此时select元素处于隐藏状态的div元素内,
    #需要点击激活div元素,然后才能定位到select元素
    div_tag = driver.find_element_by_id('email')
    div_tag.click()
    select_tag = driver.find_element_by_id('mail-opts')
    
    #将选中的下拉列表元素,传入Select类中,创建Select对象
    select = Select( select_tag  )
    
    #根据索引选择下拉列表的选项(注意:选中相当于点击!)
    select_index = select .select_by_index(1)
    #根据value属性选择
    select .select_by_value('@163.com 网易')
    #根据可见文本选择
    select .select_by_visible_text('@126.com 网易')
    
    #取消所有选中项  
    select .deselect_all() 
    
    driver.quit()
    

    示例2:

    from selenium import webdriver
    driver = webdriver.Chrome() 
    driver.get('https://www.jianshu.com/sign_in') 
    #选中复选框
    check_tag =driver.find_element_by_id('session_remember_me')
    #点击复选框
    check_tag.click() 
    driver.quit()
    

    (三)、行为链ActionChains

    有时候在页面中的操作需要多个步骤,这个时候可以使用鼠标行为链类ActionChains来统一完成。

    比如将鼠标移动到某个元素上并执行点击事件,示例代码如下:

    import time
    from selenium import webdriver
    from selenium.webdriver.common.action_chains import ActionChains
    
    driver = webdriver.Chrome()
    driver.get('https://baidu.com/')
    
    #选中输入框
    input_tag = driver.find_element_by_id('kw')  
    #选中按钮
    button_tag = driver.find_element_by_id('su')  
    
    #创建行为链对象
    actions = ActionChains(driver)
    
    #移动到某个元素
    actions.move_to_element(input_tag) 
    #向某个元素发送数据
    actions.send_keys_to_element(input_tag,  'python') 
    #移动鼠标到某个元素
    actions.move_to_element(button_tag)  
    #点击某个元素
    actions.click(button_tag) 
    
    #执行这个行为链
    actions.perform() 
    
    time.sleep(3)
    driver.quit()
    

    其他操作
    actions.click_and_hold(on_element=None) 点击但不松开鼠标
    actions.context_click(on_element=None) 右击
    actions.double_click(on_element=None) 双击
    actions.drag_and_drop(source, target) 拖拽
    更多见官方文档api


    (四)、网页的Cookie操作

    注意:只能获取当前网页的Cookie信息,而获取不到其他网页的cookie信息。

    示例代码:

    from selenium import webdriver
    
    driver = webdriver.Chrome()
    driver.get('https://baidu.com/')
    
    # 1. 获取所有的cookies
    for cookie in driver.get_cookies():
        print(cookie)
    
    # 2. 根据cookie的name获取某条cookie
    print( driver.get_cookie(name='delPer') )
    
    # 3. 根据cookie的name删除某条cookie
    driver.delete_cookie('delPer')
    print( driver.get_cookie(name='delPer') )
    
    # 4. 删除所有的cookie
    driver.delete_all_cookies()
    
    # 5. 添加cookie
    driver.add_cookie({'name' : 'foo', 'value' : 'bar', 'path' : '/'})
    print( driver.get_cookies() )
    
    

    (五)、页面等待

    现在网页越来越多的采用了Ajax技术,这样程序便不能确定某个元素何时才能完全加载出来。如果程序定位某个元素时,实际页面中这个元素还未完全加载进来,此时便会抛出NoSuchElementException异常。为了解决这个问题,selenium提供了两种等待方式:隐式等待、显示等待。

    1.隐式等待:

    调用driver.implicitly_wait(n),在定位元素之前直接等待n秒,如果等待n秒之后仍然定位不到元素,则报错。

    2.显式等待:

    在指定时间内,一直等待某个条件成立,条件成立后立即执行定位元素的操作;如果超过这个时间条件仍然没有成立,则会抛出异常!

    显式等待需要使用selenium.webdriver.support.excepted_conditions期望条件
    selenium.webdriver.support.ui.WebDriverWait类来配合完成。

    示例代码:

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.support.ui import WebDriverWait
    
    driver = webdriver.Chrome()
    driver.get('https://www.douban.com/')
    
    # 隐式等待:直接等待10秒钟
    driver.implicitly_wait(10) 
    # 执行定位元素操作
    driver.find_element(By.ID, 'hahahah')
    
    # 显式等待:条件成立,立即执行;超过指定时间则报错
    element = WebDriverWait(driver, 10).until(
          EC.presence_of_element_located( (By.ID, 'form_email') ) 
          #注意:传入的定位条件必须是元组形式
    )
    

    常见的等待条件
    presence_of_element_located:某个元素已经加载出来了
    presence_of_all_elements_located :所有符合条件的元素都加载完毕了
    element_to_be_clickable:某个元素可以被点击了
    visibility_of_element_located :某个元素可以看见了
    frame_to_be_available_and_switch_to_it:某个子网页一旦可以切换了就切换过去。
    更多见官网


    (六)、切换窗口:switch_to.window

    有时候浏览器存在多个窗口,如果需要在不同窗口进行操作,就需要在程序中切换窗口。selenium提供了switch_to.window方法来切换窗口,而具体切换到哪个窗口,可以通过driver.window_handles查看所有窗口对象。

    示例代码:

    from selenium import webdriver
    
    driver = webdriver.Chrome()
    # 1.打开百度首页
    driver.get('https://baidu.com/') 
    # 2.在当前浏览器中打开一个新的窗口,并打开豆瓣网。
    driver.execute_script( 'window.open("https://www.douban.com/")')
    
    print( '当前url:', driver.current_url )
    #注意:列表会按照打开窗口的顺序存储窗口对象!
    print( '所有窗口对象:', driver.window_handles ) 
    print( '当前窗口对象:', driver.current_window_handle )
    
    # 切换窗口:
    # 注意:窗口对象必须在相应的页面,driver才能操作对应的页面。
    # 从窗口对象列表中索引出需要切换的窗口对象,传入即可。
    driver.switch_to.window( driver.window_handles[1] )
    print( '当前url:', driver.current_url )
    print( '当前窗口对象:', driver.current_window_handle )
    

    (七)、切换子网页iframe:switch_to.frame

    在网页中可能嵌套着子网页,父网页和子网页是相对独立的,查找某个网页的元素必须切换到相应网页中才能找到!
    比如:如果在父网页中查找子网页的元素,则必须切换到子网页,否则找不到,反之亦然。
    示例代码

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    
    driver = webdriver.Chrome()
    url = 'https://graph.qq.com/oauth2.0/show?which=Login&display=pc&client_id=100290348&response_type=code&state=ab183433jKFQqXF6b25lX3Nuc6FToKFO2UxodHRwczovL3Nzby50b3V0aWFvLmNvbS9hdXRoL2xvZ2luX3N1Y2Nlc3MvP3NlcnZpY2U9aHR0cHM6Ly93d3cudG91dGlhby5jb20voVYBoUkAoUQAoUEYoU0YoUivd3d3LnRvdXRpYW8uY29toVIEolBMAKZBQ1RJT06g&redirect_uri=http%3A%2F%2Fapi.snssdk.com%2Fauth%2Flogin_success%2F&scope=get_user_info,add_share,add_t,add_pic_t,get_info,get_other_info,get_fanslist,get_idollist,add_idol,get_repost_list'
    driver.get(url) 
    
    # 切换到主网页(默认iframe)
    driver.switch_to.default_content()
    
    # 多个iframe,切换到子网页
    driver.switch_to.frame("ptlogin_iframe")
    
    # 注意:返回的子网页的HTML代码,而不是整个页面
    print( driver.page_source )  
    # 点击账号密码登录
    driver.find_element_by_id("switcher_plogin").click()
    
    切换到当前网页的父网页
    driver.switch_to.parent_frame()
    
    

    注意:切换子网页有三种方式:

    • 根据iframe标签的name属性值切换:driver.switch_to.frame('frame_name')
    • 根据多个子网页frame的顺序编号切换:driver.switch_to.frame(1)
    • 找到所有的iframe标签,然后通过索引切换到对应子网页driver.switch_to.frame( driver.find_elements_by_tag_name("iframe")[1] )

    (八)、执行JavaScript代码:execute_script

    有些动作可能selenium没有提供api,比如进度条下拉、打开新的窗口等,这时我们可以通过代码执行JavaScript执行相应操作。

    from selenium import webdriver
    driver= webdriver.Chrome()
    driver.get('https://www.zhihu.com/explore')
    
    #进度条滚动到底
    driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
    #弹出警告框
    driver.execute_script('alert("To Bottom")')
    #打开一个新的窗口
    driver.execute_script('window.open()')
    

    (九)、在selenium中设置代理

    无认证代理:

    from selenium import webdriver
    
     #设置浏览器选项
    options = webdriver.ChromeOptions() 
    #设置无验证代理
    options.add_argument('--proxy-server=https://106.75.226.36:808')   
    
    driver = webdriver.Chrome( chrome_options=options )
    driver.get("https://httpbin.org/ip")
    print(driver.page_source)
    

    认证代理:

    # 生成Chrome浏览器的代理认证插件
    import string
    import zipfile
    
    def create_proxyauth_extension(proxy_host, proxy_port,
                                   proxy_username, proxy_password,
                                   scheme='http', plugin_path=None):
    
        """代理认证插件
    
        args:
            proxy_host (str): 你的代理地址或者域名(str类型)
            proxy_port (int): 代理端口号(int类型)
            proxy_username (str):用户名(字符串)
            proxy_password (str): 密码 (字符串)
        kwargs:
            scheme (str): 代理方式 默认http
            plugin_path (str): 扩展的绝对路径
    
        return str -> plugin_path
        """
    
        import string
        import zipfile
    
        if plugin_path is None:
            plugin_path = 'vimm_chrome_proxyauth_plugin.zip'
    
        manifest_json = """
        {
            "version": "1.0.0",
            "manifest_version": 2,
            "name": "Chrome Proxy",
            "permissions": [
                "proxy",
                "tabs",
                "unlimitedStorage",
                "storage",
                "<all_urls>",
                "webRequest",
                "webRequestBlocking"
            ],
            "background": {
                "scripts": ["background.js"]
            },
            "minimum_chrome_version":"22.0.0"
        }
        """
    
        background_js = string.Template(
            """
            var config = {
                    mode: "fixed_servers",
                    rules: {
                      singleProxy: {
                        scheme: "${scheme}",
                        host: "${host}",
                        port: parseInt(${port})
                      },
                      bypassList: ["foobar.com"]
                    }
                  };
    
            chrome.proxy.settings.set({value: config, scope: "regular"}, function() {});
    
            function callbackFn(details) {
                return {
                    authCredentials: {
                        username: "${username}",
                        password: "${password}"
                    }
                };
            }
    
            chrome.webRequest.onAuthRequired.addListener(
                        callbackFn,
                        {urls: ["<all_urls>"]},
                        ['blocking']
            );
            """
        ).substitute(
            host=proxy_host,
            port=proxy_port,
            username=proxy_username,
            password=proxy_password,
            scheme=scheme,
        )
        with zipfile.ZipFile(plugin_path, 'w') as zp:
            zp.writestr("manifest.json", manifest_json)
            zp.writestr("background.js", background_js)
    
        return plugin_path
    
    if __name__ == '__main__':
        from selenium import webdriver
    
        proxyauth_plugin_path = create_proxyauth_extension(
            proxy_host="47.92.24.143",
            proxy_port=16817,
            proxy_username="dsc0206",
            proxy_password="ng1lgio8"
        )
    
        # 设置浏览器选项
        options = webdriver.ChromeOptions()
        #设置验证代理
        options.add_argument("--start-maximized")
        options.add_extension(proxyauth_plugin_path)
    
        driver = webdriver.Chrome(chrome_options=options)
        driver.get("https://httpbin.org/ip")
        print(driver.page_source)
    

    (十)、WebElement类

    from selenium.webdriver.remote.webelement import WebElement

    获取的元素logo、input是WebElement类的实例对象;而driver则是WebDriver类的对象,但WebDriver类继承了WebElement类。所以二者有很多相同属性!(详情见源码)

    可以将driver理解为BS4的BeautifulSoup对象(根节点);将获取的logo、input元素理解为BS4的tag对象(节点)。

    from selenium import webdriver
    driver = webdriver.Chrome()
    print( type(driver ) )
    
    driver.get(url='https://www.zhihu.com/explore')
    
    #获取网站logo。
    logo = driver.find_element_by_id('zh-top-link-logo')  
    print('元素', logo)
    print( '元素类型', type(logo) )
    print('元素属性', logo.get_attribute('class'))   #获取元素class属性
    
    input = driver.find_element_by_class_name('zu-top-add-question')
    print('元素文本', input.text)  #获取元素文本值
    print('元素ID', input.id)  #获取元素id
    print('元素标签名', input.tag_name)   #获取元素标签名
    print('元素大小', input.size)   #获取元素大小
    
    driver.save_screenshot('知乎.png')   #截屏
    driver.close()  #关闭当前页面
    driver.quit()  #关闭浏览器
    

    注意:虽然通过driver对象也可以解析HTML文档,获取信息,但是不建议这样做,效率低;使用driver对象获取整个网页的HTML代码,再使用lxml库+xpath进行解析,效率高。


    (十一)、Chrome的Headless模式(无界面模式)

    Selenium运行时,必须要启动浏览器,浏览器的启动与关闭必然会影响执行效率,而Chrome-headless 模式则可以不打开浏览器UI界面的情况下使用 Chrome 浏览器。

    代码示例

    from selenium import webdriver
    
    # 设置浏览器选项
    options= webdriver.ChromeOptions()
    
    # 把chrome设置成无界面模式
    options.add_argument('--headless')
    
    driver = webdriver.Chrome(chrome_options=options)
    driver.get('https://baidu.com/')
    driver.save_screenshot('baidu.png')   #截屏
    

    (十二)、操作浏览器的前进与后退

    代码示例

    import time
    from selenium import webdriver
    
    browser = webdriver.Chrome()
    browser.get('https://www.baidu.com/')
    time.sleep(1)
    browser.get('https://www.taobao.com/')
    
    time.sleep(1)
    browser.back()   #后退
    
    time.sleep(1)
    browser.forward()   #前进
    
    browser.close()
    

    (十三)、selenium的异常处理

    常用异常
    TimeoutException:超时
    NoSuchElementException:找不到元素
    NoSuchAttributeException:元素的属性找不到
    NoSuchFrameException:要切换的子网页不存在
    NoSuchWindowException:要切换的窗口不存在
    WebDriverException:WebDriver异常的基类

    代码示例

    from selenium import webdriver
    from selenium.common.exceptions import TimeoutException  #超时异常
    from selenium.common.exceptions import NoSuchElementException  #找不到元素异常
    
    browser = webdriver.Chrome()
    
    try:
        browser.get('https://www.baidu.com')
    except TimeoutException:
        print('Time Out')
    
    try:
        browser.find_element_by_id('hello')
    except NoSuchElementException:
        print('No Element')
    finally:
        browser.close()
    

    参考文献:

    Selenium官方文档
    Selenium用法详解
    Selenium 配置代理
    Chrome的headless模式
    selenium的常见异常


    1. 因为在以前传输数据格式使用的是XML,因此叫做AJAX,而现在数据交换基本上都是使用JSON(故现在应该叫做AJAJ才更合适)。

    2. AJAX技术只是动态渲染网页最常见的一种,还有采用其他方式的动态渲染网页。

    相关文章

      网友评论

        本文标题:Ajax数据爬取及selenium的使用详解

        本文链接:https://www.haomeiwen.com/subject/uhutxqtx.html