Python快乐编程—网络爬虫—抓取动态网页内容

介绍了SQLite和Mongo DB两种数据库的基本用法( Python快乐编程—网络爬虫—数据库存储 ),在爬虫工作中会碰到爬取到的数据与网页中看到的内容不一致的情况,出现这种情况很可能是因为抓取的网页是动态的,因此使用抓取静态页面的方法是行不通的。对于动态网页的抓取,在Python中通常有两种方法:一种是直接从JavaScript中采集加载的数据,这种方法需要手动分析Ajax请求来采集信息;另一种方法是使用Python第三方库运行动态网页中的JavaScript代码,从而采集到网页内容,主要讲解第二种方法的使用。

JavaScript简介

动态HTML(DynamicHTML,简称DHTML)是指通过结合HTML、客户端脚本语言(比如JavaScript)、层叠样式表(CSS)和文档对象(Document Object Model,DOM)来创建的一种网页。其中客户端脚本语言是运行在浏览器而非服务器上的语言,浏览器执行脚本语言的前提是浏览器具有正确执行和解释这类语言的能力。

常见的客户端脚本语言只有两种:ActionScript(开发Flash)和JavaScript。其中ActionScript常用于流媒体文件播放以及在线游戏平台,并且ActionScript的使用率也很低,而采集Flash页面的需求并不多,因此本章主要介绍JavaScript。

Ø JS语言特性

JavaScript是一种运行在浏览器中的解释型编程语言,JavaScript非常值得学习,它既适合作为学习编程的入门语言,也适合当作日常开发的工作语言。

每种编程语言都有自己的语言特性,只有了解语言的独到之处,才能更好的理解这门语言,JavaScript语言也有其特性。

1. 解释型

编译型语言在计算机运行代码前,先把代码翻译成计算机可以理解的文件,如Java、C++等;而解释型语言则不同,解释型语言在运行程序时才编译,如JavaScript、PHP等。

解释型语言的优点是可移植性较好,只要有解释环境就可在不同的操作系统上运行,无须编译,上手方便快速。缺点是需要解释环境,运行起来比编译型语言慢,占用资源多,代码效率低。

2. 弱类型

弱类型言是相对强类型语言而言。在强类型语言中,变量类型有多种,如int、char、float、boolean等,不同的类型相互转换时可能需要强制转换。而JavaScript只有一种类型var,为变量赋值时会自动判断类型并进行转换,因此JavaScript是弱语言。

弱类型语言的优点是学习简单、语言表达简单易懂、代码更优雅、开发周期更短、更加偏向逻辑设计,缺点是程序可靠性差、调试烦琐、变量不规范、性能低下等。


3. 动态性

动态性语言在变量定义时可不进行赋值操作,在使用时作赋值操作即可。这种方式使得代码更灵活、方便。在JavaScript中有多处用到动态性,如获取元素、原型等。


4. 事件驱动

JavaScript可以直接对用户或客户输入作出响应,无须经过Web程序。它对用户的响应以事件驱动的方式进行,即由某种操作动作引起相应的事件响应,如单击鼠标、移动窗口、选择菜单等。


5. 跨平台

JavaScript依赖于浏览器本身,与操作环境无关。只要计算机能运行浏览器,且浏览器支持JavaScript,即可正确执行,从而实现“编写一次,到处执行”。


Ø JS简单示例

JavaScript可以收集用户的跟踪数据,不需要重载页面即可直接提交表单,可在页面中嵌入多媒体文件,甚至可以运行网页游戏等。在很多看起来非常简单的页面背后通常使用了许多JavaScript文件,通常是在网页源代码的<script>标签中,具体如下所示:

JavaScript的语法通常与C++和Java作对比,虽然语法中的一些元素,比如操作符、循环条件和数组等都和Java、C++语法接近,但JavaScript的弱类型和脚本形式在开发时常常被人诟病。

例如,下面的JavaScript程序通过递归方式计算Fibonacci序列,最后将结果打印在浏览器的开发者控制台中:

需要注意的是,在JavaScript里所有的变量都用var关键字进行定义,这与Java和C++里的类型声明类似,但在Python中没有这种显式的变量声明。

在JavaScript中有一个非常好的特性,就是把函数作为变量使用,如下所示:

将上述代码放入后缀名为.html的文件中,使用Firefox浏览器打开该代码,按F12键打开调试界面切换到控制台中,结果如图所示。

在上述示例中,变量fibonacci被定义成一个函数,函数值返回一个递增序列中较大的值。每次当变量fibonacci被调用时就会返回fibonacci函数,程序继续执行序列计算,并增加函数变量的值。

在处理用户行为和回调函数时,把函数作为变量进行传递是非常方便的,大家在阅读JavaScript代码时必须适应这种编程方式。


Ø JavaScript库

当前,在大型互联网公司的不断推广下,JavaScript生态圈也在不断的完善,各种类库、API接口层出不穷。在Python中执行原生JavaScript代码的效率非常低,尤其是在处理规模较大的JavaScript代码中体现得更明显,此时可以选择一种第三方库来直接解析JavaScript进而获取需要的数据。

jQuery是一个快速、简洁的JavaScript框架,是继Prototype之后又一个优秀的JavaScript代码库(或JavaScript框架)。jQuery设计的宗旨是“Write Less, Do More”,即倡导写更少的代码,做更多的事情。它封装了JavaScript常用的功能代码,提供一种简便的JavaScript设计模式,优化HTML文档操作、事件处理、动画设计和Ajax交互。

jQuery的核心特性包括:具有独特的链式语法和短小清晰的多功能接口,具有高效灵活的CSS选择器,并且可对CSS选择器进行扩展,拥有丰富的插件和便捷的插件扩展机制。jQuery兼容各种主流浏览器,如IE 6.0+、Safari 2.0+、Opera 9.0+等。

如果一个网站使用了jQuery,那么源代码中必然包含了jQuery的入口,具体如下所示:

若一个网站使用了jQuery,那么在采集数据时要格外注意,jQuery可以动态地创建HTML内容,该内容只有在JavaScript代码执行之后才会显示。如果使用传统的方法采集页面数据,只能获取JavaScript代码执行之前页面上的内容。

另外,这些页面还可能包含动画、用户交互内容和嵌入式媒体等,这些内容对网络数据采集都是挑战。

Ø Ajax简介

通过 JavaScript 加载数据,在不刷新网页的情况下,更新网页内容的技术,称为 Ajax(Asynchronous JavaScript and XML,异步JavaScript和XML)。

Ajax 是一种用于快速创建动态网页的技术。到目前为止,客户端与网站服务器通信的唯一方式是发出HTTP请求获取新页面。如果提交表单之后,或从服务器获取信息之后,网站的页面不需要重新刷新,那么访问的网站就在使用Ajax技术。在现实生活中,有很多使用 Ajax的应用程序案例,例如新浪微博、Google地图等。

需要注意的是,“这个网站是Ajax写的”这个说法并不准确,正确的说法应该是“这个表单是用Ajax与网络服务器通信”。

与Ajax一样,动态HTML(DHTML)也是一系列用于解决网络问题的技术集合。DHTML是客户端语言改变页面的HTML元素(HTML、CSS,或二者皆被改变)。比如,页面上的按钮只有当用户鼠标移动后才出现,背景色可能每次点击都会改变,或者用一个Ajax请求触发页面加载一段新内容。从上可以看出,动态HTML与Ajax联系很紧密,甚至可以说,使用了Ajax的网页绝大部分都是动态HTML。

爬取动态网页的工具

爬取动态网页时,常常使用Selenium库。Selenium库是一个强大的网络数据采集工具,其最初是为了网站自动化测试而开发。近几年,它还被广泛用于获取精确的网站快照,因为它可以直接运行在浏览器上。

Ø Selenium库

Selenium(官网 seleniumhq.org/ )可以让浏览器自动加载页面,获取需要的数据,甚至页面截屏,或者判断网站上某些动作是否发生。

安装Selenium的过程很简单,在DOS命令窗口中输入如下命令即可安装成功:

安装时默认Selenium的最新版本,即Selenium3.x。Selenium3.x相比Selenium2.x能兼容高版本浏览器,但在调用浏览器时需要下载一些文件,比如调用Firefox时需安装geckodriver,调用Chrome时需安装chromedriver。

Selenium自身不带浏览器,它需要与第三方浏览器结合在一起使用。如果在Firefox浏览器上运行Selenium,可以看见Firefox窗口被自动打开,进入网站,并执行代码中设置的动作。

下面通过一个简单示例示范Selenium的用法,因为示例中要用到Firefox,并且还要能调动它,所以需要大家在计算机上事先安装好Firefox浏览器,并下载安装好geckodriver,geckodriver是Firefox浏览器的内核,下载地址为:“ github.com/mozilla/geck ”,注意下载时需对应本地安装的Firefox版本。

本书安装的Firefox版本为61,geckodriver版本为v0.21.0。解压geckodriver完成后,最好将其配置到系统环境变量中,具体配置方法为:右键单击“计算机”à属性à高级系统设置à高级à环境变量,然后在环境变量中选择Path并编辑,在后面添加上geckodriver所在文件目录即可。

在确保Firefox以及geckodriver都安装成功后,在PyCharm中运行以下示例代码:

运行上面程序后可以看到,浏览器Firefox自动打开了百度搜索网页,并自动搜索关键字“网络爬虫”,结果如图所示。

需要注意的是,如果没有配置geckodriver的环境变量,就需要在代码中指明geckodriver的位置,比如webdriver.Firefox("C:\python\geckodriver.exe")。

在开发工作中,如果每次运行类似的爬虫程序都会打开一个Firefox浏览器窗口,势必会干扰桌面上的其他工作,因此Selenium经常与无界面浏览器结合使用,在之前Selenium的低版本中,经常与PhantomJS浏览器结合使用,不过在高版本的Selenium中已不支持PhantomJS浏览器,可使用Firefox或Chrome的headless模式来代替。

Ø PhantomJS浏览器

PhantomJS是一个“无头”(headless)浏览器,它会把网页加载到内存并执行网页中的JavaScript,但是它不会向用户展示网页的图形界面。将Selenium和PhantomJS结合可以处理Cookie、JavaScript以及header等。

PhantomJS的官方下载网站是 phantomjs.org/download. 。因为PhantomJS是一个功能完善的浏览器,并不是一个库,所以不能用安装第三方库的方法来安装PhantomJS,只需要在官网上下载并解压即可,需要注意的是安装目录,因为在开发中会使用到。

在当下的互联网环境中,很多网页都是用Ajax动态来加载数据。下面通过爬取腾讯体育新闻网页“ sports.qq.com/articleLi ”的内容,来演示Selenium与PhantomJS的结合用法,参考。

需要注意的是,这里安装的Selenium版本是Selenium2.x,因为新版本的Selenium不再支持PhantomJS,使用时会报“UserWarning: Selenium support for PhantomJS has been deprecated, please use headless versions of Chrome or Firefox instead”的错误。该错误指引用户使用“无头”的Chrome或者Firefox浏览器来代替PhantomJS,下面讲解“无头”的Firefox浏览器与Selenium的结合使用。

Ø Firefox的headless模式

Firefox的headless模式与PhantomJS类似,也看不到用户界面。实现Firefox的headless模式需要确保本地已安装Firefox以及geckodriver。

下面演示Firefox的headless模式的使用。

从上面程序可看出,设置Firefox为headless模式的方式很简单,只需创建Options()对象并添加参数“-headless”即可。


利用上面的结论,例7-1也可使用Firefox浏览器实现。


爬取动态网页的工具、爬取动态网页实例

已经学习了JavaScript的基本知识、Selenium库、PhantomJS浏览器及Firefox的headless模式,下面将学习Selenium选择器、Selenium等待方式、客户端重定向及爬取动态网页实例的相关内容。

Ø Selenium选择器

在例7-1中,使用BeautifulSoup选择器选择页面中的元素,其实Selenium有自己的选择器,它在WebDriver的DOM中使用了全新的选择器来查找网页元素,具体如下所示:

如果选取页面上具有多个同样特征的元素,可以用elements(换成复数)返回一个Python列表,具体如下所示:

具体的页面元素选取方法如表所示。


定位一个元素 定位多个元素 含义
find_element_by_id find_elements_by_id 通过元素id进行定位
find_element_by_name find_elements_by_name 通过元素name进行定位
find_element_by_xpath find_elements_by_xpath 通过XPath表达式进行定位
find_element_by_link_text find_elements_by_link_text 通过完整超链接文本进行定位
find_element_by_partial_ link_text find_elements_by_ partial_link_text 通过部分超链接文本进行定位
find_element_by_tag_name find_elements_by_tag_name 通过标记名称进行定位
find_element_by_class_name find_elements_by_class_name 通过类名进行定位
find_element_by_css_selector find_elements_by_css_selector 通过CSS选择器进行定位


除了上面具有确定选择功能的方法外,还有两个通用方法find_element和find_elements,可以通过传入参数来指定功能,具体如下所示:

上面示例代码通过XPath表达式来查找,方法中第一个参数是指定选取元素的方式,第二个参数是选取元素需要传入的值或表达式。其中第一个参数还可以传入By类中的以下值:

By.ID By.XPATH By.LINK_TEXT

By.PARTIAL_LINK_TEXT By.NAME By.TAG_NAME

By.CLASS_NAME By.CSS_SELECTOR


下面通过选取一个HTML文档的部分元素来讲解如何使用以上方法提取内容,HTML代码如下所示:

选取方法如表所示。


选取方式 代码示例
通过元素id进行定位 login_form = driver.find_element_by_id('loginForm')
通过元素name进行定位 username = driver.find_element_by_name('username')password = driver.find_element_by_name('password')
通过XPath表达式进行定位 login_form = driver.find_element_by_xpath("//form[@id= 'loginForm']")clear_button = driver.find_element_by_xpath("//input[@type= 'button']")
通过链接文本定位超链接 register_link = driver.find_element_by_link_text('Register')register_link = driver.find_element_by_partial_link_text('Reg')
通过标记名称进行定位 h1 = driver.find_element_by_tag_name('h1')
通过类名进行定位 content = driver.find_element_by_class_name('content')
通过CSS选择器进行定位 content = driver.find_element_by_css_selector('p.content')


另外,如果使用BeautifulSoup解析网页内容,可使用WebDriver的page_source函数返回页面的源代码字符串,具体如下所示:



Ø Selenium等待方式

现在很多网站采用Ajax技术动态加载数据,采集数据也只能等待数据加载完成之后。Selenium中设置了三种等待方式来确定数据加载完成,一种是显式等待,一种是隐式等待,还有一种强制等待。

显式等待是一种条件触发式的等待方式,只有达到设置好的条件后才能继续执行程序,可设置超时时间,若超时后元素依然没加载完成,则抛出异常,具体如下所示:

除了presence_of_all_elements_located()方法外,Selenium还提供了很多内置方法用于显示等待,位于expected_conditions类中,方法名称如表所示。


内置方法名 功能
title_is 判断当前页面的title是否等于预期内容
title_contains 判断当前页面的title是否包含预期字符串
presence_of_element_located 判断某个元素是否被加到了DOM中,并不代表该元素一定可见
visibility_of_element_located 判断某个元素是否可见
presence_of_all_elements_located 判断是否至少有1个元素存在于DOM中
text_to_be_present_in_element 判断某个元素中的text是否包含了预期的字符串
text_to_be_present_in_element_value 判断某个元素中的value属性是否包含了预期的字符串
frame_to_be_available_and_switch_to_it 判断该frame是否可以切换进去,如果可以则返回true并切换进去,否则返回false
invisibility_of_element_located 判断某个元素中是否不存在于DOM或不可见
element_to_be_clickable 判断某个元素是否可见并且enable
staleness_of 等待某个元素从DOM中移除
element_to_be_selected 判断某个元素是否被选中,传入元素对象
element_located_to_be_selected 判断某个元素是否被选中,传入定位元组
element_selection_state_to_be 判断某个元素的选中状态是否符合预期,相等返回True,否则返回False
element_located_selection_state_to_be 判断某个定位元组的选中状态是否符合预期,相等返回True,否则返回False
alert_is_present 判断页面上是否存在alert框


相比于显示等待,隐式等待的时间通常比较长。隐式等待是在尝试发现某个元素时,如果没有立刻发现,就等待固定长度的时间。一旦设置了隐式等待时间,它的作用范围是Webdriver对象实例的整个生命周期。隐式等待时间可随时进行修改。

隐式等待相比显式等待来说很不智能。因为随着Ajax技术的广泛应用,页面的元素往往都是局部加载,很可能在整个页面没有加载完时,需要的元素已经加载完成,此时就没有必要等待整个页面的加载,而隐式等待显然不是这样的。

强制等待是设置等待最简单的方法,其实就是time.sleep()方法,让程序暂停运行一定时间再继续执行。这种方法相比于隐式等待来说更加不智能,如果设置的时间太短,元素还没有加载出来,程序照样会报错;如果设置的时间太长则会浪费时间。

Ø 客户端重定向

标准意义上的“重定向”指的是HTTP重定向,它是HTTP协议规定的一种机制。该机制的工作过程为:当client(客户端)向server(服务器)发送一个请求,要求获取一个资源时,在server接收到请求后发现资源实际存放于另一个位置,于是server在返回的response中写入请求该资源的正确URL,并设置response状态码为301(表示要求浏览器重定向的response),当client接收到这个response后就会根据新的URL重新发起请求。这也是客户端重定向的原理,客户端重定向的工作过程如图所示。

客户端重定向有一个典型的特征,当一个请求被重定向以后,最终浏览器地址栏上显示的URL往往不再是开始时请求的URL。

与客户端重定向对应的是服务器间跳转(即服务器重定向),服务器间的跳转在浏览器中并不会显示,请求的URL也不会有任何变化,但由于客户端重定向执行很快,加载页面时甚至感觉不到任何延迟,因此有时很难区分这两种重定向。但在网络数据采集时,这两种重定向的差异是非常明显的。服务器重定向一般都可以通过Python的urllib库解决,不需要使用Selenium,而客户端重定向则必须借助工具来执行JavaScript代码。

Selenium可以用来处理客户端重定向,执行方式和处理其他JavaScript的方式一样。解决了如何处理重定向后,还有一个主要问题是如何识别一个页面已经完成了重定向。下面通过“ pythonscraping.com/page ”示例页面演示客户端重定向的处理,该网址中示例了2秒延迟的重定向。具体代码参考。

Ø 爬取动态网页实例

本节通过爬取百度图片( image.baidu.com )来演示动态网页数据的抓取过程及程序设计。

打开百度图片主页并搜索相应关键字,比如输入“表情包”,观察搜索出来的页面中会发现,该网页中没有页数可以选择,而且随着鼠标往下拉,搜索的图片数量会自动增加。这是因为该网页在加载图片时与服务器交互,动态的加载出网页页数与图片链接从而实现该效果。

为了方便地分析出每个网页的链接以及每页中每个图片的链接,在搜索出来的网页中通过F12调出调试界面,并切换到“网络”中的XHR下。

XHR英文全称为XmlHttpRequest,Xml即可扩展标记语言,Http即超文本传输协议,Request即请求,中文可以解释为可扩展超文本传输请求。XmlHttpRequest对象可以在不向服务器提交整个页面的情况下,实现局部更新网页。当页面全部加载完毕后,客户端通过该对象向服务器请求数据,服务器端接收数据并处理后,向客户端反馈数据。

在页面中将鼠标往下滑,发现在XHR中会一直出现一个名为“acjson?tn=resultjson&ipn=…”的请求,单击该请求查看消息头,如图所示。


将其中的请求网址复制出来如下:


通过对比上面六个网址,发现除了参数pn与gsm以及最后一串数字变化外,其余参数字段均没有变化。经过测试,pn字段为图片数量,每次有规律的增加30,代表每次增加30张图片数量,gsm为两个十六进制数,最后一串数字为Unix时间戳,是从当前时间转化而来。构造网页url时可将gsm字段与时间戳忽略,因此每个网页的url地址构造如下所示:

获得每页网页地址后,还需要得到网页中每张图片的地址,将调试界面切换到查看器中,并且定位到第一张图片,如图所示。

从图中的源码处会发现有一个“objURL”字段,其对应的值就是图片的地址。第一张图片的objURL对应的值如下所示:



上述图片地址并不是真正的图片地址,甚至都不是正确的网址。这是因为图片的地址都经过了编码,所以在程序中需要对该地址进行解码,解码过程直接在程序中体现,这里不做讲解。

在D盘的pythonSpiderFile目录下新建img目录用于存放下载下来的图片。接下来展示具体代码,参考。

打开pythonSpiderFile目录下的img目录,如图所示。

小结:Python快乐编程—网络爬虫—抓取动态网页内容

https://www.zhihu.com/video/1565338679635316736

本文主要为大家介绍了如何爬取动态网页内容,首先介绍了动态网页的概念以及如何判断是否为动态网页,接着介绍了爬取动态网页内容的组合工具——Selenium与PhantomJS;由于在新版本的Selenium中不再支持PhantomJS浏览器,因此又介绍了如何使用Firefox的headless模式与Selenium配合使用来爬取动态网页内容,最后介绍了客户端重定向的概念。学完本章,大家需要动手练习本章案例,务必掌握爬取动态网页内容的方法。

编辑于 2022-10-17 16:11