JavaSript动态渲染页面的方式不止Ajax一种。
例如中国青年网,分页部分由JS生成,这其中并不包含Ajax请求;淘宝的页面虽然是Ajax抓取的数据,但是Ajax接口含有很多加密参数,很难找到规律,所以也很难直接分析Ajax来抓取。
对于以上情况,可以模拟浏览器运行,就可以做到在浏览器中看到是什么样,抓取的源码就是什么样,“可见即可爬”。这样,我们就不用在意JS用了什么算法渲染页面,网页后台的Ajax接口有哪些参数。
python中模拟浏览器运行的库有很多,比如Selenium、Splash、PyV8、Ghost等,这里重点介绍Selenium和Splash。
Selenium
Selenium是一个自动化测试工具,可以驱动浏览器执行特定动作,同时可以获取浏览器当前呈现页面的源代码。
首先要确保已经安装好了Chrome以及对应的ChromeDriver,这里我的Chrome版本号是V70,选用的ChromeDriver是V2.43
常见用法
1.声明浏览器对象1
2
3
4from selenium import webdriver
browser = webdriver.Chrome()
browser = webdriver.Firefox()
browser = webdriver.Edge()
声明完浏览器对象后,只要让该对象执行各个动作模拟浏览器操作
2.访问页面1
2
3
4
5from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
print(browser.page_source)
browser.close()
用get()方法请求网页,传入URL参数,打印出源代码,关闭浏览器
3.查找节点
Selenium可以驱动浏览器完成各种操作,比如填写表单、模拟点击等,但前提是得知道该节点的位置
- 单个节点
例如,我们要从淘宝页面中提取搜索框这个节点,首先观察源代码:
可以看到id=‘q’,name=’q’1
2
3
4
5
6
7
8from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.taobao.com')
input_first = browser.find_element_by_id('q')
input_second = browser.find_element_by_css_selector('#q')
input_third = browser.find_element_by_xpath('//*[@id="q"]')
print(input_first,input_second,input_third)
browser.close()
下面是获取单个节点的所有方法:1
2
3
4
5
6
7
8find_element_by_id
find_element_by_name
find_element_by_xpath
find_element_by_link_text
find_element_by_partial_link_text
find_element_by_tag_name
find_element_by_class_name
find_element_by_css_selector
还有一个通用方法 find_element():
要传入两个参数,第一个参数是查找方式,第二个参数就是值1
2
3
4
5
6
7from selenium import webdriver
from selenium.webdriver.common.by import By
browser = webdriver.Chrome()
browser.get('https://www.taobao.com')
input_1 = browser.find_element(By.ID,'q')
print(input_1)
browser.close()
- 多个节点
find_element只能找到第一个节点,如果要查找所有满足的节点,就要用find_elements()
例如,要查找某宝的左侧导航栏的所有条目:1
2
3
4
5
6from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.taobao.com')
lis = browser.find_elements_by_css_selector('.service-bd li')
print(lis)
browser.close()
结果以列表形式展示,每个节点都是WebElement类型
所有获取多个节点的方式与单个节点几乎一致,只是变成了elements
4.节点交互
模拟浏览器执行一些动作,输入文字用send_keys(),清空文字用clear(),点击按钮用click()1
2
3
4
5
6
7
8
9
10
11
12from selenium import webdriver
import time
browser = webdriver.Chrome()
browser.get('https://www.taobao.com')
my_input = browser.find_element_by_id('q')
my_input.send_keys('iPhone')
time.sleep(1)
my_input.clear()
my_input.send_keys('iPad')
button = browser.find_element_by_class_name('btn-search')
button.click()
browser.close()
这里首先模拟输入‘iPhone’,过1秒后,清空输入框,输入‘iPad’,然后模拟点击搜索按钮
5.动作链
还有一些操作,是没有特定执行对象的,比如鼠标的拖曳、键盘按键等,如果要模拟这些动作,则要使用动作链来执行
例如下图,试图将左边的小方块拖拽到右边的大方块中:1
2
3
4
5
6
7
8
9
10from selenium import webdriver
from selenium.webdriver import ActionChains
browser = webdriver.Chrome()
browser.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
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.drag_and_drop(source,target)
actions.perform()
执行结果如下:
6.执行JS
使用execute_script()
下面就是一个模拟下拉进度条的方法:1
2
3
4
5
6#移动到页面最底部
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
browser.execute_script('alert("To Bottom")')
7.获取节点信息
既然Sselenium有选择节点的方法,那肯定也有方法和属性来提取节点信息。
- 获取属性
1 | from selenium import webdriver |
- 获取文本值
1 | from selenium import webdriver |
- 获取id、位置、标签名和大小
1 | from selenium import webdriver |
8.切换Frame
网页中有一种节点叫‘iframe’,即子Frame,相当于页面的子页面。Selenium默认在父Frame操作,如果页面含有iframe,是无法获取到里面的节点的,所以需要切换Frame1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
browser = webdriver.Chrome()
browser.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
browser.switch_to.frame('iframeResult')
try:
logo = browser.find_element_by_class_name('logo')
except NoSuchElementException:
print('No Logo')
browser.switch_to.parent_frame() #切换回父级Frame
logo = browser.find_element_by_class_name('logo')
print(logo)
print(logo.text)
# No Logo
# <selenium.webdriver.remote.webelement.WebElement (session="68ac17f7a88cf12243a84562a2fd036a", element="0.6314598896265449-2")>
# RUNOOB.COM
9.延时等待
Selenium中,get()方法在网页框架加载结束后结束执行,如果这时候获取page_source,得到的不一定是浏览器完全加载后的页面,可能获取不到额外的Ajax请求,所以需要延时,保证节点已经完全加载出来了
- 隐式等待
1 | from selenium import webdriver |
如果Selenium没有在DOM中找到节点,就会继续等待,超出设定的事件后,抛出找不到节点的异常,默认为0
- 显式等待
隐式等待方法会受到网络状况的影响,一般我们跟更多的选择显式等待方法1
2
3
4
5
6
7
8
9
10from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
browser = webdriver.Chrome()
browser.get('https://www.taobao.com')
wait = WebDriverWait(browser,10)
entity = wait.until(EC.presence_of_element_located((By.ID,'q')))
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,'.btn-search')))
print(entity,button)
WebDriverWait对象指定最长等待时间,然后调用until方法传入等待条件。presence_of_element_located这个条件,表示节点出现的意思(ID为q的节点搜索框),如果十秒钟内,ID为q的节点成功加载,就返回该节点;如果没有加载,就抛出异常;element_to_be_clickable表示CSS选择器为.btn-search的按钮是否是可点击的。
下表是部分等待条件及其含义:
等待条件 | 含义 |
---|---|
title_is | 标题是某内容 |
title_contains | 标题包含某内容 |
presence_of_element_located | 节点加载出来,传入定位元组,如(By.ID,’q’) |
visibility_of_element_Located | 节点可见,传入定位元组 |
visibility_of | 可见,传入节点对象 |
presence_of_all_elements_located | 所有的节点加载出来 |
text_to_be_present_in_element | 某个节点文本包含某文字 |
text_to_be_present_in_element_value | 某个节点值包含某文字 |
frame_to_be_available_and_switch_to_it | 加载并切换 |
imvisibility_of_element_located | 节点不可见 |
10.前进和后退
浏览器都有前进和后退的功能,Selenium用back()和forward()实现1
2
3
4
5
6
7
8
9
10from selenium import webdriver
import time
browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
browser.get('https://www.taobao.com')
browser.get('https://www.zhihu.com/explore')
browser.back()
time.sleep(1)
browser.forward()
browser.close()
11.Cookies
实现获取、添加、删除Cookies等操作1
2
3
4
5
6
7
8
9
10
11from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
# 获取Cookies
print(browser.get_cookies())
# 添加Cookies
browser.add_cookie({'name':'name','domain':'www.zhihu.com','value':'germey'})
print(browser.get_cookies())
# 删除所有的Cookies
browser.delete_all_cookies()
print(browser.get_cookies()) #[]
12.选项卡管理
访问网页的时候会开启选项卡,可以利用Selenium对选项卡进行操作1
2
3
4
5
6
7
8
9
10
11from selenium import webdriver
import time
browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
browser.execute_script('window.open()')
print(browser.window_handles)
browser.switch_to.window(browser.window_handles[1])
browser.get('https://www.baidu.com')
time.sleep(1)
browser.switch_to.window(browser.window_handles[0])
browser.get('https://www.taobao.com')
调用execute_script(),传入window.open()这个JS语句开启一个新的选项卡。用switch_to.window()切换选项卡,调用window_handles属性获取当前开启的所有选项卡。
13.异常处理
用try except捕获异常1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from selenium import webdriver
from selenium.common.exceptions import TimeoutException,NoSuchElementException
browser = webdriver.Chrome()
try:
browser.get('https://www.baidu.com')
except TimeoutError:
print('Time Out')
try:
browser.find_element_by_id('hello')
except NoSuchElementException:
print('No Element')
finally:
browser.close()
# No Element