动态渲染页面爬取
前面了解到了Ajax的分析和爬取方式,这其实也是JavaScript动态渲染页面的一种方式,通过直接分析Ajax,仍然可以借助requests或urllib来实现数据爬取
但是JavaScript的动态渲染页面不止Ajax这一种,例如有些网站的分页是由JavaScript生成的,并非是原始的HTML代码,其中也并不包含Ajax
Selenium的使用
Selenium是一个自动化测试工具,利用它可以驱动浏览器执行指定的动作,如点击,下来等操作,同时还可以获取浏览器当前呈现的页面的源代码,做到可见即可爬
基本使用
1 | from selenium import webdriver |
再执行上条指令后,会自动弹出一个Chrome浏览器,浏览器首先会跳转到百度,然后在搜索框中输出Python,接着跳转到搜索结果页面
这样我们就可以直接拿到JavaScript渲染的结果了
声明浏览器对象
Selenium支持非常多的浏览器,如Chrome,Firefox,Edge等,还有Android,BlackBerry等手机端浏览器,还支持无界面浏览器PhantomJS
1 | from selenium import webdriver |
这样就完成了浏览器对象的初始化,并将其赋值为browser对象
访问页面
我们可以使用get()方法来请求网页,参数传URL即可1
2
3
4
5
6from selenium import webdriver
browser=webdriver.Chrome()
browser.get('https://www.taobao.com')
print(browser.page_source)
browser.close()
运行后发现,弹出了Chrome浏览器并且自动访问了淘宝,然后控制台输出了淘宝页面的源代码,随后浏览器关闭
查找结点
selenium可以驱动浏览器完成各种操作,比如填充表单,模拟点击等。比如,我们想要完成向某个输入框输入文字的操作,需要知道这个输入框在哪里,而selenium提供了一系列查找节点的方法
单个节点
比如,想要从淘宝页面中提取搜索框这个节点,首先观察它的源代码
可以发现它的id是q,name也是q,此外还有许多其他属性,我们就可以以多种方式获取了
比如find_element_by_name()是根据name获取,find_element_by_id()是根据id获取
另外,还有根据XPath,CSS选择器等获取方式1
2
3
4
5
6
7
8
9
10
11
12
13
14from 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()
输出结果:
<selenium.webdriver.remote.webelement.WebElement (session="b332cd2cb2191a08286eada1aceb4b4e", element="1b3bf42c-4780-4c65-9b6f-fca9190503f4")>
<selenium.webdriver.remote.webelement.WebElement (session="b332cd2cb2191a08286eada1aceb4b4e", element="1b3bf42c-4780-4c65-9b6f-fca9190503f4")>
<selenium.webdriver.remote.webelement.WebElement (session="b332cd2cb2191a08286eada1aceb4b4e", element="1b3bf42c-4780-4c65-9b6f-fca9190503f4")>
我们发现结果一至,可以看到这三个节点都是WebElement类型
获取单个节点的方法:1
2
3
4
5
6
7
8find_element__id
find_element__name
find_element__xpath
find_element__link_text
find_element__partial_link_text
find_element__tag_name
find_element__class_name
find_element__css_selector
此外还提供了通用方法find_element(),它需要传入两个参数,查找方法By和值
实际就是上述方法的通用版本
1 | from selenium import webdriver |
多个节点
如果查找的目标在网页中只有一个,那么完全可以使用find_element()方法,但是如果有多个节点,就需要使用find_elements()方法了
比如淘宝导航条的所有条目
先查看这些条目,发现都在class为service-bd的ul节点的li节点中1
2
3
4
5
6
7
8
9
10from 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()
输出结果:
[<selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="7237cd9e-6812-41b2-839c-5a5ef0790077")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="cd8c7478-6486-4376-8c25-a562dad5be68")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="01ac2aac-0431-49f5-9e9b-244fb11a5b99")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="5f10b89b-f4f2-4efb-a084-3ce219e81aa2")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="b99fefe4-b5d0-42c9-84d2-068ba0738d53")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="4ce8e958-225e-480b-9e43-d0510bd6b7ee")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="c37b736b-8d99-4424-a38c-f5270bdc274c")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="a1c19196-5d1e-414b-ae4b-44ad67938651")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="387c966a-29d2-4ced-a1bf-ae11d24f3252")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="f74ecd4d-68eb-4be6-b50a-6ad5f792b9af")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="5c413df1-2c09-4a10-88ee-c27287535d6e")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="73ee7920-b2dd-44dd-b5b5-97a36b5684e1")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="5ef053b7-90a1-4a0d-b504-284d8d9ed497")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="555d8a92-90bd-4724-99ab-fcdb88397077")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="98329539-7cf1-404a-8903-d3ea72f3bb6d")>, <selenium.webdriver.remote.webelement.WebElement (session="cb088f704736092b306130aab2abf8f1", element="df6d8c38-5f1d-4872-8a30-d6e0e1ca02ad")>]
这样就获取到了所有条目的源代码
获取多个节点1
2
3
4
5
6
7
8find_elements__id
find_elements__name
find_elements__xpath
find_elementst__link_text
find_elements__partial_link_text
find_elements__tag_name
find_elements__class_name
find_elementst__css_selector
同时还有通用方法1
lis=browser.find_elements(By.CSS_SELECTOR,'.service-bd li')
节点交互
我们还可以让浏览器执行一些操作,比较常见的如输入文字时用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')
input=browser.find_element_by_id('q')
input.send_keys('iphone')
time.sleep(1)
input.clear()
input.send_keys('ipad')
button=browser.find_element_by_class_name('btn-search')
button.click()
这里首先驱动浏览器打开淘宝,然后用find_element_by_id()方法获取输入框,然后用send_keys()方法输入iphone文字,等待一秒后用clear()方法清空输入框,再次调用send_keys()方法输入ipad文字,之后再用find_element_by_class_name()方法获取搜索按钮,最后调用click()方法完成搜索动作
更多交互请参考文档:
中文文档:
https://python-selenium-zh.readthedocs.io/zh_CN/latest/
官方文档:
https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement
动作链
在上面的实例中,发现一些交互动作都是针对某个节点执行的。比如,对于输入框,我们就是调用它的输入文字和清空文字方法,对于按钮,就调用它的点击方法.其次,还有另外一些操作,它们没有特定的执行对象,比如鼠标拖拽,键盘按键等
1 | from selenium import webdriver |
运行后会将小方块拖拽到大方块中
更多动作链操作请参考文档:
中文文档:
https://python-selenium-zh.readthedocs.io/zh_CN/latest/
官方文档:
https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement
执行JavaScript
对于某些操作,Selenium API并没有提供。比如,下拉进度条,它可以直接模拟运行JavaScript,此时使用execute_script()方法即可实现1
2
3
4
5
6from 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")')
这里就利用execute_script()方法将进度条下拉到最底部,然后弹出alert提示框
所以说有了这个方法,基本上API没有提供的所有功能都可以用执行JavaScript的方式来实现了
获取节点信息
前面通过Beautiful Soup,pyquery等提取信息
Selenium中提供了选择节点的方法,返回的是WebElement类型,那么它也有相关的方法和属性来直接提取节点信息,如属性,文本等。这样的话,我们就可以不用通过解析源代码来提取信息了
获取属性
我们可以通过get_attribute()方法来获取节点的属性,前提是先选中这个节点1
2
3
4
5
6
7
8from selenium import webdriver
browser=webdriver.Chrome()
url='https://www.zhihu.com/explore'
browser.get(url)
logo=browser.find_element_by_id('zh-top-link-logo')
print(logo)
print(logo.get_attribute('class'))
通过get_attribute()方法,然后传入想要获取的属性名,就可以得到它的值了
获取文本值
每个WebElement节点都有text属性,直接调用这个属性就可以得到节点内部的文本信息,这相当于Beautiful Soup中的get_text()方法,pyquery的text()方法1
2
3
4
5
6
7
8
9
10from selenium import webdriver
browser=webdriver.Chrome()
url='https://www.zhihu.com/explore'
browser.get(url)
input=browser.find_element_by_class_name('ExploreHomePage-ContentSection-header')
print(input.text)
输出结果:
最新专题
这样就获得了最新专题节点的内容
获取id,位置,标签名和大小
WebElement节点还有一些其他的属性,比如id属性可以获取节点的id,location属性可以获取该节点在页面中的相对位置,tag_name属性可以获取标签名称,size属性可以获取节点的大小,也就是宽高1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18from selenium import webdriver
browser=webdriver.Chrome()
url='https://www.zhihu.com/explore'
browser.get(url)
input=browser.find_element_by_class_name('ExploreHomePage-ContentSection-header')
print(input.text)
print(input.id)
print(input.location)
print(input.tag_name)
print(input.size)
输出结果:
最新专题
b74d780d-b4f2-4a35-9221-cb40a5e48902
{'x': 10, 'y': 84}
div
{'height': 36, 'width': 1000}
这样就获取到了id,位置,标签名和大小
切换Frame
网页中有一个节点叫做iframe,也就是子Frame,相当于页面中的子页面,它的结构和外部网页的结构完全一致。Selenium打开页面后,它默认是在父级Frame里操作,而此时如果页面中还有子Frame,它是不能获取到子Frame里面的节点的,这时就需要switch_to.frame()方法来切换Frame
1 | import time |
首先通过switch_to.frame()方法切换到子Frame里面,然后尝试获取子Frame里的logo节点,如果找不到的话,就会抛出NoSuchElementException异常。接下来,重新切换到父级Frame,再次重新获取节点,就可以成功捕获了
延时等待
get()方法会在网页框架加载结束后结束执行,此时如果获取page_source,可能并不是浏览器完全加载完成的页面,如果某些页面有额外的Ajax请求,我们在网页源代码中也不一定能获取到。所以,这里需要延时等待一定时间,确保节点已经加载出来
隐式等待
当使用隐式等待执行测试的时候,如果selenium没有在DOM中找到节点,将继续等待,超出设定时间后,则抛出找不到节点的异常。换句话说,当查找节点而节点并没有立即出现的时候,隐式等待将等待一段时间再查找DOM,默认事件为01
2
3
4
5
6
7from selenium import webdriver
browser=webdriver.Chrome()
browser.implicitly_wait(10)
browser.get('https://www.zhihu.com/explore')
input=browser.find_element_by_class_name('ExploreHomePage-ContentSection-header')
print(input)
这里使用了隐式等待implicitly_wait()
显式等待
隐式等待的效果其实并没有那么好,因为我们只规定了一个固定时间,而页面加载时间还会受到网络条件的影响
这里还有一种更合适的显式等待方法,它指定要查找的节点,然后指定一个最长等待时间。如果在规定时间内加载出来了节点,就返回查找的节点,反之
1 | from selenium import webdriver |
首先引入WebDriverWait这个对象,指定最长等待时间,然后调用它的until()方法,传入要等待条件expected_conditions。比如,这里传入了presence_of_element_located这个条件,代表节点出现的意思,其参数是节点的定位元组,也就是ID为q的节点搜索框
这样可以做到的效果就是,在10秒内如果ID为q的节点成功加载出来,就返回该节点;如果超过10秒还没有加载出来,就抛出异常。
对于按钮,可以更改一下等待条件,比如改为element_to_be_clickable,也就是可点击,所以查找按钮时查找CSS选择器为.btn-search的按钮,如果10秒内它是可点击的,也就是成功加载出来了,就返回这个按钮节点,反之
更多操作请参考文档
中文文档:
https://python-selenium-zh.readthedocs.io/zh_CN/latest/
官方文档:
https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement
前进和后退
平时使用浏览器时都有前进和后退功能,selenium也可以完成这个操作,它使用back()方法后退,使用forward()方法前进。1
2
3
4
5
6
7
8
9
10
11import time
from selenium import webdriver
browser=webdriver.Chrome()
browser.get('https://www.baidu.com')
browser.get('https://www.taobao.com')
browser.get('https://www.python.org')
browser.back()
time.sleep(1)
browser.forward()
browser.close()
这样我们连续访问3个页面,然后调用back方法回到第二个页面,接下来再调用forward()方法又可以前进到第三个页面
cookies
使用selenium,还可以方便地对Cookies进行操作,例如获取,添加,删除Cookies等1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from 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())
输出结果:
[{'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'KLBRSID', 'path': '/', 'secure': False, 'value': 'cdfcc1d45d024a211bb7144f66bda2cf|1587952898|1587952896'}, {'domain': '.zhihu.com', 'expiry': 1619488898, 'httpOnly': False, 'name': 'Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'secure': False, 'value': '1587952898'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': 'Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'secure': False, 'value': '1587952898'}, {'domain': '.zhihu.com', 'expiry': 1682560896.073367, 'httpOnly': False, 'name': 'd_c0', 'path': '/', 'secure': False, 'value': '"AIBbIT5rLhGPTpMVZJCLnmYZXYPvh8AXzpM=|1587952896"'}, {'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'SESSIONID', 'path': '/', 'secure': False, 'value': 'Ns5jG8TIaSE1aAjeOzckOayn6V6nDAubFTUf1DfCC2H'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'secure': False, 'value': '6e9342c2-b0fd-4474-9099-193fef3e5595'}, {'domain': '.zhihu.com', 'expiry': 1651024896.073016, 'httpOnly': False, 'name': '_zap', 'path': '/', 'secure': False, 'value': '2dbb5fb5-9f25-44ae-ac01-5183941f56df'}]
[{'domain': '.www.zhihu.com', 'httpOnly': False, 'name': 'name', 'path': '/', 'secure': True, 'value': 'germey'}, {'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'KLBRSID', 'path': '/', 'secure': False, 'value': 'cdfcc1d45d024a211bb7144f66bda2cf|1587952898|1587952896'}, {'domain': '.zhihu.com', 'expiry': 1619488898, 'httpOnly': False, 'name': 'Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'secure': False, 'value': '1587952898'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': 'Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'secure': False, 'value': '1587952898'}, {'domain': '.zhihu.com', 'expiry': 1682560896.073367, 'httpOnly': False, 'name': 'd_c0', 'path': '/', 'secure': False, 'value': '"AIBbIT5rLhGPTpMVZJCLnmYZXYPvh8AXzpM=|1587952896"'}, {'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'SESSIONID', 'path': '/', 'secure': False, 'value': 'Ns5jG8TIaSE1aAjeOzckOayn6V6nDAubFTUf1DfCC2H'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'secure': False, 'value': '6e9342c2-b0fd-4474-9099-193fef3e5595'}, {'domain': '.zhihu.com', 'expiry': 1651024896.073016, 'httpOnly': False, 'name': '_zap', 'path': '/', 'secure': False, 'value': '2dbb5fb5-9f25-44ae-ac01-5183941f56df'}]
[]
可以看到我们添加的cookie被添加到了cookies中
选项卡管理
访问网页的时候,会开启一个个选项卡。在selenium中,我们可以对选项卡进行操作1
2
3
4
5
6
7
8
9
10
11
12import time
from selenium import webdriver
browser=webdriver.Chrome()
browser.get('https://www.baidu.com')
browser.execute_script('window.open()')
print(browser.window_handles)
browser.switch_to.window(browser.window_handles[1])
browser.get('https://www.taobao.com')
time.sleep(1)
browser.switch_to.window(browser.window_handles[0])
browser.get('https://python.org')
先访问了百度,然后调用了execute_script()方法,这里传入window.open()这个JavaScript语句开启一个选项卡。然后用windows_handles属性获取当前开启地所有选项卡,返回的是选项卡的代码列表。想要切换,只需要使用switch_to.window()方法,其中是选项卡的代号即可。
总结
1.安装selenium之后,还需要根据浏览器的不同,安装对应的webdriver,例如Chrome的webdriver
2.webdriver需要放在浏览器的根目录中,并且在环境变量中引入,如果在管理员模式下cmd中,输入对应webdriver的名字,有正常显示就表示环境变量成功
3.如果需要通过python来对浏览器进行操作,还需要将对应的webdriver放入到python的根目录下
4.selenium可以操作浏览器自动完成一些操作,所以需要注意一些危险操作,以免操作信息泄露
5.selenium功能强大,可以使用动作链和节点交互功能完成一系列重复性操作,例如抢课等