随着网页技术的发展,动态网页的比例越来越高,原来抓取静态网页的许多方法变得不再适用;再加上越来越多的网站添加了各种复杂的反爬虫策略,导致直接通过网络请求的方式去抓取页面的方式已经有些落伍了。
而Selenium可以通过模拟浏览器的真实行为来访问网页并将页面源码缓存下来,从而实现所见即所得的效果。Selenium本身广泛应用于测试领域,但是它所见即所得的特性基本上满足了我们抓取绝大多数页面的需求,因此今天我们就看一下如何通过Selenium访问页面并通过不同的方式定位到我们需要的元素,从而完成页面抓取。
我们以今日头条的官网首页(https://www.toutiao.com)来进行演示。
欢迎大家关注我的个人博客【数洞】 【备用站】
一、通过ID
定位
首先我们看下如何通过元素id
来定位。在头条首页,有一个右边栏,通过检查页面元素我们发现,这是一个id="rightModule"
的<div>
标签,那么我们就通过这个ID
来定位到这个元素,并且打印出该元素的class
属性。
# -*- coding:utf-8 -*-
from selenium import webdriver
if __name__ == '__main__':
driver = webdriver.Chrome()
driver.get('https://www.toutiao.com')
element_class = driver.find_element_by_id('rightModule').get_attribute('class')
print(element_class)
输出为:
bui-right index-right-bar
在这里,我们先通过driver = webdriver.Chrome()
启动一个Chrome Driver,这里需要保证我们的环境变量目录中已经包含了与我们的Chrome浏览器版本对应的chromedriver,大家可以自行搜索下载并安装。
然后我们通过driver.get('https://www.toutiao.com'
来控制浏览器打开头条的首页;接下来,find_element_by_id('rightModule')
的作用就是在页面源码中定位到包含了id="rightModule"
属性的元素,也就是上边提到的右边栏对应的元素;最后我们使用get_attribute('class')
方法,取出该元素class
属性的值。
很简单,不是吗?
另外提一句,所有的find_element_by_XXX
方法都会返回定位到的第一个元素,加个s后,即所有的find_elements_by_XXX
方法则会以列表形式返回符合条件的所有元素。比如上边这个例子,如果我们改成driver.find_elements_by_id('rightModule')
则会以列表形式返回所有符合条件的元素(当然,在这个例子中,事实上列表的长度也只有1)。
二、通过name
定位
跟ID
类似,selenium
提供了find_element_by_name
和find_elements_by_name
方法来通过name
属性定位元素。
头条首页右侧,有一个淘宝广告的模块,里边有一些轮播图,接下来我们就通过name
属性定位到它,并打印出它的ad_name
属性的值。
element_ad_name = driver.find_element_by_name('home_right*top_1')\
.get_attribute('ad_name')
print(element_ad_name)
输出为:
h_300*250_TB_314
使用方法和find_element_by_id
如出一辙。
三、通过class
定位
聪明如你可能会猜测,通过class
定位元素的方法应该是find_element_by_class
和find_elements_by_class
,那么,“恭喜你,答错了!”(这一句是我上学的时候最痛恨的被老师嘲讽的话,今天说出来给别人,感觉很爽!)
事实上,这两个方法应该是find_element_by_class_name
和find_elements_by_class_name
,比我们猜测的多了个后缀(是的,最开始我也猜错了),他们会定位到class
取值包含某个字符串的所有元素。
我们注意到头条首页左侧有一个边栏,列出了一些频道,那么我们如何定位并打印出这些频道名称呢?
观察页面源码,我们发现频道名称隐藏在一个<span>
标签中,而这个<span>
标签,有一个class="channel-item"
的<a>
父标签。同时我们也注意到,第一个频道——“推荐”的标签属性与其他的频道标签属性不同,它的class
取值为channel-item active
。
channel_element_list = driver.find_elements_by_class_name('channel-item')
channel_list = [x.text for x in channel_element_list]
print(channel_list)
输出为:
['推荐', '阳光宽频', '热点', '图片', '科技', '娱乐', '游戏', '体育', '汽车', '财经', '搞笑', '更多', '', '', '', '', '', '', '', '', '', '', '']
我们看到除了页面上看到的频道以外,还有一些空的字符串。这是因为“更多”这里在悬浮的时候会产生更多的可选频道,我们在没有进行悬浮操作的时候,暂时看不到这些频道。这个问题以后我们可以通过一系列的动作等方式来解决。
四、通过tag
名称定位
通过标签名称定位的方法为find_element_by_tag_name
和find_elements_by_tag_name
,这个方法可以直接选择标签。
仍以上例来说明,我们看到,上边的例子中,我们直接对选择到的<a>
标签选择了它的text
属性,并获取了文本,但事实上文本存在于<a>
的子标签<span>
中。那么我们为什么能成功呢?这是因为.text
属性查询的是所有子孙节点中的文本。
考虑另一种情况,假如上述的<a>
标签有多个子孙标签,且都有不同的文本,而我们只要想<span>
标签中的文本时,应该如何操作?
text = driver.find_elements_by_class_name('channel-item')[5].find_element_by_tag_name('span').text
print(text)
输出为:
'娱乐'
可以看到,我们在定位到<a>
标签后,选择了它的子标签<span>
并成功打印出对应的文本。
五、通过XPath
定位
这是一大利器,XPath在定位页面元素上的强大不容置疑,不了解XPath的同学可以阅读我的另一篇文章:《Python3使用Xpath解析网易云音乐歌手页面》。这篇文章通过实战演练,讲解了如何快速上手XPath并解析网易云音乐的歌手页面。
selenium
中提供了对XPath
的支持,我们可以通过find_element_by_xpath
和find_elements_by_xpath
来灵活地运用XPath
进行元素定位。
头条首页中间,有一些内容列表,那我们就看看如何获取这些内容的标题。
观察页面源码,可以看到这些内容都藏在一个class="title-box"
的<div>
标签的子标签<a>
中。
title_elements = driver.find_elements_by_xpath('//div[@class="title-box"]/a')
titles = [x.text for x in title_elements]
print(titles)
输出为(输出较长,故隐藏部分内容):
['中共中央政治局召开会议 习近平主持', '五次出席G20峰会,习近平提出哪些“中国主张”?', ...]
事实上,XPath
是支持直接获取文本列表的,但是目前selenium
对XPath
的支持还不够丰富。想要体验更强大的XPath
的朋友可以读一下上边提到的那篇文章,十分钟即可入门。
其他定位方式
目前selenium
支持的元素定位方式还有三种:
- CSS选择器:
find_element_by_css_selector
和find_elements_by_css_selector
,支持以层叠样式表(CSS)的方式定位元素; - Link:
find_element_by_link_text
和find_elements_by_link_text
,支持通过链接文本来定位元素; - Partial Link:
find_element_by_partial_link_text
和find_elements_by_partial_link_text
,支持通过链接文本的一部分来定位元素。
不过这三种我自己用的比较少,它们的使用也非常简单,有CSS基础或者对这三种方法感兴趣的朋友可以自行检索资料学习。事实上,上述的五种方法已经足够我们选取任何页面元素了。