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

一、什么是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技术只是动态渲染网页最常见的一种,还有采用其他方式的动态渲染网页。

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

推荐阅读更多精彩内容