一、什么是AJAX
AJAX[1],全称Asynchronous JavaScript And XML,即异步的JavaScript和XML。通过在后台与服务器进行少量的数据交换,AJAX可以使网页实现异步更新,实现网页的动态渲染。
这意味着可以在不加载整个网页的情况下,对网页的某部分进行更新。而传统的网页如果需要更新内容,则必须重载整个网页页面。
使用AJAX加载的网页数据,虽然将数据渲染到了浏览器中,但在网页源代码中还是看不到通过ajax加载的数据,只能看到通过url加载的数据,这使得通过requests、urllib无法正常获取网页的全部数据。
获取ajax数据的两种方式:
- 直接分析ajax调用的接口,然后通过代码请求这个接口
优点:可以直接请求到数据,不需要做一些解析工作,代码量少,性能高。
缺点:分析接口比较复杂,特别是一些通过js混淆的接口,需要一定的js功底,容易被发现是爬虫。
- 使用selenium+driver(浏览器驱动)模拟浏览器行为获取数据
优点:浏览器能请求到的数据,使用selenium也能请求到,爬虫 更稳定,且适用于所有类型的动态渲染网页[2]。
缺点:代码量多,性能低。
二、selenium+chromedriver获取动态数据
selenium是一个自动化测试工具,可以模拟人类在浏览器上的一些行为,自动处理浏览器上的一些行为,比如点击、填充数据等。还可以获取浏览器当前呈现的页面源码,解决动态渲染网页的数据抓取,做到可见即可爬。
而chromedriver是一个驱动Chrome浏览器的驱动程序,selenium使用它才能够驱动Chrome浏览器。
针对不同的浏览器有不同的驱动(driver),比如Firefox的驱动geckodriver、IE的驱动IEdriver等,都可以配合selenium驱动对应的浏览器,本文以Chrome浏览器为例。
安装Selenium+chromedriver:
- 安装selenium模块:pip install selenium
- 安装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
- 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
- find_element_by_class_name:根据类名来查找某个元素。driver.find_element_by_class_name('su')
driver.find_element(By.CLASS_NAME, 'su')
- find_element_by_name:根据name属性值查找某个元素。
driver.find_element_by_name('emile')
driver.find_element(By.NAME, 'emile')
- find_element_by_tag_name:根据标签名来查找某个元素。
driver.find_element_by_tag_name('div')
driver.find_element(By.TAG_NAME, 'div')
- find_element_by_xpath:根据xpath语法查找某个元素。
driver.find_element_by_xpath('//div')
driver.find_element(By.XPATH, '//div')
- 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选择器来选取某个元素
注意:
- find_elements与find_element用法一致,只是多了一个s!
- find_element是获取第一个满足条件的元素,直接返回获取到的元素对象,如果找不到,则报错!
- find_elements是获取所有满足条件的元素,返回获取到的所有元素对象组成的列表,如果找不到,返回空列表!
(二)、操作表单元素
常见的表单元素:
- 输入框:input type='text/password/email/number'
- 按钮:button、input type='submit'
- 复选框:checkbox、input type='checkbox'
- 下拉列表:select
操作输入框input:
第一步:定位标签;
第二步:使用send_keys(value)将数据填入输入框。操作按钮button:
第一步:定位标签;
第二步:执行click()点击事件。操作复选框checkbox:
第一步:定位标签;
第二步:执行click()点击事件。
如果默认是未选中,则点击后选中,再次点击则取消选中!操作下拉列表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的常见异常