前面的爬虫案例爬取的网站都是静态页面内容,即内容都是预先生成的。但很多网站页面的内容都是动态生成的,即由浏览器运行页面中的JS脚本动态生成。由JS脚本生成的动态内容,前面的爬虫案例程序会爬取失败。
1、 爬取失败的案例
先来看一个简单爬取动态页面的案例,在Google Chrome浏览器中打开下面的页面:
http://milihua.com/sample/dynamic.aspx
页面会显示两张图片,按下F12键,浏览器会打开网页代码调试窗口,在代码调试窗口,单击Sources选项卡,会看到网页的源代码,如下图所示:

从代码中可以看出,在id值为content元素下面有<ul><li><img>标签,<img>标签的src属性指向图片的URL链接。
若在浏览器中查看网页源码,会看到下面的源代码:

从浏览器看到的源代码,在id值为content元素下面没有任何内容,那么这些内容是怎样产生的呢?
下面的JS代码可以让我们了解页面动态内容的创建细节:
<script type="text/javascript">
$(document).ready(function () {
gecontent();
})
function gecontent() {
$.getJSON("/Handle/getcontent.ashx", function (json) {
if (json == null || json == 'undefined') {
return;
}
var content = "";
content += "<ul>";
content += "<li>"
content += "<img src=" + json.imgurl1 + ">";
content += "</li>";
content += "<li>"
content += "<img src=" + json.imgurl2 + ">";
content += "</li>";
content += "</ul>";
$("#content").append(content);
}, "json");
}
</script>JS函数gecontent()在网页元素加载完成后被执行,函数使用Ajax技术向服务端发送getcontent.ashx请求,getcontent.ashx返回JSON数据,函数拼接HTML字符串和JSON数据,最后将拼接完成的HTML字符串添加到id值为content的元素内。
下面使用scrapy shell环境来爬取dynamic.aspx网页,打开Windows命令窗口,在命令行窗口输入下面的命令:
scrapy shell http://milihua.com/sample/dynamic.aspx
爬取成功后,在shell环境下输入下面的命令(输出爬取的内容):
print(response.text)
从输出的爬取内容可以看出,id值为content的元素下面没有任何内容,爬虫爬取失败。
爬取此类动态网页,需要先执行网页内的JavaScript代码渲染页面,然后再爬取,这就用到了JavaScript渲染引擎Selenium。
2、 使用Selenium执行网页脚本
Selenium是用于WEB应用程序的测试工具,它可以在浏览器中启动WEB程序(网页也属于WEB应用程序),并执行网页中的脚本内容,返回执行结果。
要让Scrapy爬虫使用Selenium执行网页脚本,需要对原来的爬虫流程进行修改,原来的爬取过程如下图所示:

Scrapy引擎将爬取的Request请求提交给下载管理器,下载器管理器下载网页数据,将网页数据封装到Response内并返回Response,Scrapy引擎会回调在Request对象设置的回调函数,并传入Responses对象。若Request对象没有设置回调函数,将会调用Spider的parse()方法。
若Request请求的页面是动态页面,Responses对象存储的页面内容没有动态加载的数据,要想获取页面动态加载的数据,则需要在下载管理器内部调用Selenium执行网页脚本,将Selenium返回的内容封装到response,然后将response对象返回给Scrapy引擎。

3、 下载中间件
中间件(Middleware)是Scrapy框架的一个核心概念,使用中间件可以钩住Scrapy请求和响应处理,对请求和响应的数据进行定制化修改,从而开发出适应不同情况的爬虫。
下载中间件的作用是钩住(拦截)请求和响应,在请求中下载中间件可以修改求头信息(User-Agent)、设置相关请求对象的代理ip等内容,在响应中下载中间件可以对响应进行一系列处理,例如执行动态网页的脚本,封装Response并传递给引擎。
Scrapy允许开发者可以使用多个下载中间件钩住Scrapy的请求和响应处理。一个下载中间件是一个Python类,它定义了一个或多个方法,用来处理请求和响应。

注释(1)
Scrapy引擎调用下载管理器发送request请求时,该方法被调用。开发者可以在该方法内修改Request请求对象,并返回新的request请求对象。
若该方法返回None,Scrapy将继续处理这个请求,调用后续的中间件来处理请求。若该方法返回Reponse响应对象,Scrapy会停止调用后续中间件的process_request()方法,但会调用后续中间件的process_response()方法。
参数request是请求对象。参数spider是发送该请求的爬虫实例对象。
注释(2)
网页下载完成后,该方法被调用。开发者可以在该方法内修改Reponse响应对象,并返回新的Reponse响应对象。
若该方法返回Reponse响应对象,Scrapy将继续处理这个响应对象,调用后续的中间件来处理响应对象。若该方法返回Request请求对象,Scrapy会暂停中间件链的调用,并将返回的请求对象加入请求队列。
参数request是请求对象。参数response是待处理的响应对象。参数spider是待处理响应对象的爬虫实例对象。
如何使用下载中间件?
Scrapy框架在创建爬虫程序时,已经生成了一个默认的中间件Python类文件,文件名称是middlewares.py。文件定义了两个Python类:一个Python类是爬虫中间件类;一个Python类是下载中间件类。这两个类都给出了默认方法,开发者可以修改这些默认方法的代码,以定制化自己的需求。
要启用下载中间件,需要修改settings配置文件,去掉下载中间件的注释:
DOWNLOADER_MIDDLEWARES = {
'matplotlib.middlewares.MatplotlibDownloaderMiddleware': 543,
}
DOWNLOADER_MIDDLEWARES是下载中间件的配置项,配置项内容是matplotlib爬虫项目的内容。
4、 安装Selenium和Chorme浏览器
安装Selenium
在Python环境下安装Selenium非常简单,在Windows命令行窗口输入命令:
pip3 install selenium
pip3会自动下载Selenium并安装。
安装Chorme浏览器
Chorme浏览器是谷歌浏览器,在搜索引擎搜索Chorme浏览器可以找到Chorme浏览器的下载地址,建议下载谷歌官方发布的Chorme浏览器。
使用Selenium驱动chrome浏览器需要下载chromedriver,chromedriver是Chorme浏览器的驱动程序。chromedriver版本需要与Chromee的版本对应,若版本不一致,会导致Selenium启动Chorme浏览器失败。
查看Chorme浏览器的版本,可以通过Chorme浏览器“帮助”菜单的子菜单项“关于Google Chrome(G)”查看。下图是Chorme浏览器的一个版本:

图中Chorme浏览器的版本号是76.0.3809.87,下载chromedriver驱动时,也需要选择与Chorme浏览器版本一致或接近的chromedriver版本下载。
下载chromedriver驱动可通过下面的网址下载:
http://npm.taobao.org/mirrors/chromedriver/
5、 建立爬取动态网页爬虫项目
启动PyCharm建立项目,项目名称为dynamic。在项目终端窗口使用Scrapy创建项目命令创建爬虫项目,在终端窗口输入下面的Scrapy命令并回车执行命令:
scrapy startproject dynamic
在终端窗口将当前工作目录切换到dynamic爬虫项目目录,输入创建爬虫命令:
scrapy genspider spider_dynamic www.milihua.com
命令执行成功后,Scrapy会在项目的spiders目录创建爬虫spider_ dynamic.py模块文件。
(1)编辑items.py文件,存储dynamic.aspx网页内图片路径。
import scrapy class DynamicItem(scrapy.Item): # 图片链接 img_url = scrapy.Field()
(2)编辑middlewares.py中间件文件,该中间件文件是Scrapy默认生成的,文件内定义了DynamicSpiderMiddleware爬虫中间件类和下载中间件DynamicDownloaderMiddleware类,当前主要编辑下载中间件类的process_request()方法。
# 修改process_request()方法
def process_request(self, request, spider):
# 获取Chrome浏览器实例对象
bro = spider.bro
# 若request.url在请求列表中
if request.url in spider.start_urls:
# 调用Chrome实例对象get方法向浏览器发送url请求
bro.get(request.url)
# 休眠5秒,等待Chrome浏览器加载request.url
time.sleep(5)
# page_source是Chrome实例对象执行URL后的网页代码
html = bro.page_source
# 关闭Chrome浏览器
bro.quit()
# 返回Response对象,通知Scrapy引擎停止调用后续调用后续中间件的process_request()方法
return scrapy.http.HtmlResponse(url=request.url, body=html.encode('utf-8'), encoding='utf-8',
request=request)
return None下载中间件类重写了process_request()方法,该方法传入request和spider参数,request是发送的请求,spider参数是爬虫类的实例对象。
在方法内部调用Chrome浏览器对象的get()方法,执行request对象内的url,并调用time模块的sleep()方法让程序进入休眠,等待Chrome浏览器对象执行url任务的完成。page_source是Chrome实例对象的属性,该属性存储了Chrome实例对象执行URL后的网页代码。最后构建一个新的HtmlResponse实例对象并返回。
(3)编辑spider_dynamic爬虫文件。
import scrapy
from selenium import webdriver
# 导入scrapy选择器
from scrapy.selector import Selector
# 导入scrapy选择器
from dynamic.items import DynamicItem
class SpiderDynamicSpider(scrapy.Spider):
name = 'spider_dynamic'
allowed_domains = ['www.milihua.com']
# 爬取动态网页
start_urls = ['http://milihua.com/sample/dynamic.aspx']
# 重写init方法
# 实例Chrome对象,启动本地安装的Chrome浏览器
# Chrome实例对象名称为bro,下载中间件会使用到该对象
def __init__(self):
# 获取Chrome浏览器启动选项
options = webdriver.ChromeOptions()
# 配置Chrome以管理员权限运行
options.add_argument('--no-sandbox')
# 配置Chrome以无界面方式运行
options.add_argument('--headless')
# 以配置的启动选项实例化Chrome对象
self.bro = webdriver.Chrome(chrome_options=options)
def parse(self, response):
# 使用XPath获取img节点
item_nodes = response.xpath("//img").extract()
# 遍历节点
for item_node in item_nodes:
item = DynamicItem()
# 使用xpath表达式获取节点的src属性值
item["img_url"] = Selector(text=str(item_node)).xpath('//@src').extract()
# 使用yield语句返回item给parse的调用者
yield item爬虫类SpiderDynamicSpider重写了__init__()方法,该方法在爬虫实例化时被调用,在__init__()方法内实例化Chrome浏览器对象,并赋值给类属性bro,该属性在下载中间件被使用。
(4)要启用下载中间件,需要修改settings配置文件,去掉下载中间件的注释:
DOWNLOADER_MIDDLEWARES = {
'dynamic.middlewares.DynamicDownloaderMiddleware': 543,
}(5)运行爬虫
在PyCharm环境中,打开终端窗口,在终端窗口将当前工作目录切换到爬虫项目目录,输入Scrapy运行爬虫命令:
scrapy crawl spider_dynamic -o items.json
爬虫运行完成后,会将下载的图片文件存储到爬虫项目目录下的images目录。