在上一节咱们实现了Scrapy对接Selenium抓取淘宝商品的过程,这是一种抓取JavaScript动态渲染页面的方式。除了Selenium,Splash也能够实现一样的功能。本节咱们来了解Scrapy对接Splash来进行页面抓取的方式。
html
请确保Splash已经正确安装并正常运行,同时安装好Scrapy-Splash库。
git
首先新建一个项目,名为scrapysplashtest,命令以下所示:
github
scrapy startproject scrapysplashtest复制代码
新建一个 Spider,命令以下所示:json
scrapy genspider taobao www.taobao.com复制代码
能够参考Scrapy-Splash的配置说明进行一步步的配置,连接以下:https://github.com/scrapy-plugins/scrapy-splash#configuration。
bash
修改settings.py,配置SPLASH_URL
。在这里咱们的Splash是在本地运行的,因此能够直接配置本地的地址:服务器
SPLASH_URL = 'http://localhost:8050'复制代码
若是Splash是在远程服务器运行的,那此处就应该配置为远程的地址。例如运行在IP为120.27.34.25的服务器上,则此处应该配置为:dom
SPLASH_URL = 'http://120.27.34.25:8050'复制代码
还须要配置几个Middleware,代码以下所示:异步
DOWNLOADER_MIDDLEWARES = {
'scrapy_splash.SplashCookiesMiddleware': 723,
'scrapy_splash.SplashMiddleware': 725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}
SPIDER_MIDDLEWARES = {
'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}复制代码
这里配置了三个Downloader Middleware和一个Spider Middleware,这是Scrapy-Splash的核心部分。咱们再也不须要像对接Selenium那样实现一个Downloader Middleware,Scrapy-Splash库都为咱们准备好了,直接配置便可。scrapy
还须要配置一个去重的类DUPEFILTER_CLASS
,代码以下所示:ide
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'复制代码
最后配置一个Cache存储HTTPCACHE_STORAGE
,代码以下所示:
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'复制代码
配置完成以后,咱们就能够利用Splash来抓取页面了。咱们能够直接生成一个SplashRequest
对象并传递相应的参数,Scrapy会将此请求转发给Splash,Splash对页面进行渲染加载,而后再将渲染结果传递回来。此时Response的内容就是渲染完成的页面结果了,最后交给Spider解析便可。
咱们来看一个示例,以下所示:
yield SplashRequest(url, self.parse_result,
args={
# optional; parameters passed to Splash HTTP API
'wait': 0.5,
# 'url' is prefilled from request url
# 'http_method' is set to 'POST' for POST requests
# 'body' is set to request body for POST requests
},
endpoint='render.json', # optional; default is render.html
splash_url='<url>', # optional; overrides SPLASH_URL
)复制代码
这里构造了一个SplashRequest
对象,前两个参数依然是请求的URL和回调函数。另外咱们还能够经过args
传递一些渲染参数,例如等待时间wait
等,还能够根据endpoint
参数指定渲染接口。更多参数能够参考文档说明:https://github.com/scrapy-plugins/scrapy-splash#requests。
另外咱们也能够生成Request对象,Splash的配置经过meta
属性配置便可,代码以下:
yield scrapy.Request(url, self.parse_result, meta={
'splash': {
'args': {
# set rendering arguments here
'html': 1,
'png': 1,
# 'url' is prefilled from request url
# 'http_method' is set to 'POST' for POST requests
# 'body' is set to request body for POST requests
},
# optional parameters
'endpoint': 'render.json', # optional; default is render.json
'splash_url': '<url>', # optional; overrides SPLASH_URL
'slot_policy': scrapy_splash.SlotPolicy.PER_DOMAIN,
'splash_headers': {}, # optional; a dict with headers sent to Splash
'dont_process_response': True, # optional, default is False
'dont_send_headers': True, # optional, default is False
'magic_response': False, # optional, default is True
}
})复制代码
SplashRequest
对象经过args
来配置和Request对象经过meta
来配置,两种方式达到的效果是相同的。
本节咱们要作的抓取是淘宝商品信息,涉及页面加载等待、模拟点击翻页等操做。咱们能够首先定义一个Lua脚本,来实现页面加载、模拟点击翻页的功能,代码以下所示:
function main(splash, args)
args = {
url="https://s.taobao.com/search?q=iPad",
wait=5,
page=5
}
splash.images_enabled = false
assert(splash:go(args.url))
assert(splash:wait(args.wait))
js = string.format("document.querySelector('#mainsrp-pager div.form > input').value=%d;document.querySelector('#mainsrp-pager div.form > span.btn.J_Submit').click()", args.page)
splash:evaljs(js)
assert(splash:wait(args.wait))
return splash:png()
end复制代码
咱们定义了三个参数:请求的连接url
、等待时间wait
、分页页码page
。而后禁用图片加载,请求淘宝的商品列表页面,经过evaljs()
方法调用JavaScript代码,实现页码填充和翻页点击,最后返回页面截图。咱们将脚本放到Splash中运行,正常获取到页面截图,以下图所示。
翻页操做也成功实现,以下图所示即为当前页码,和咱们传入的页码page
参数是相同的。
咱们只须要在Spider里用SplashRequest
对接Lua脚本就行了,以下所示:
from scrapy import Spider
from urllib.parse import quote
from scrapysplashtest.items import ProductItem
from scrapy_splash import SplashRequest
script = """ function main(splash, args) splash.images_enabled = false assert(splash:go(args.url)) assert(splash:wait(args.wait)) js = string.format("document.querySelector('#mainsrp-pager div.form > input').value=%d;document.querySelector('#mainsrp-pager div.form > span.btn.J_Submit').click()", args.page) splash:evaljs(js) assert(splash:wait(args.wait)) return splash:html() end """
class TaobaoSpider(Spider):
name = 'taobao'
allowed_domains = ['www.taobao.com']
base_url = 'https://s.taobao.com/search?q='
def start_requests(self):
for keyword in self.settings.get('KEYWORDS'):
for page in range(1, self.settings.get('MAX_PAGE') + 1):
url = self.base_url + quote(keyword)
yield SplashRequest(url, callback=self.parse, endpoint='execute', args={'lua_source': script, 'page': page, 'wait': 7})复制代码
咱们把Lua脚本定义成长字符串,经过SplashRequest
的args
来传递参数,接口修改成execute
。另外,args
参数里还有一个lua_source
字段用于指定Lua脚本内容。这样咱们就成功构造了一个SplashRequest
,对接Splash的工做就完成了。
其余的配置不须要更改,Item、Item Pipeline等设置与上节对接Selenium的方式相同,parse()
回调函数也是彻底一致的。
接下来,咱们经过以下命令运行爬虫:
scrapy crawl taobao复制代码
运行结果以下图所示。
因为Splash和Scrapy都支持异步处理,咱们能够看到同时会有多个抓取成功的结果。在Selenium的对接过程当中,每一个页面渲染下载是在Downloader Middleware里完成的,因此整个过程是阻塞式的。Scrapy会等待这个过程完成后再继续处理和调度其余请求,这影响了爬取效率。所以使用Splash的爬取效率比Selenium高不少。
最后咱们再看看MongoDB的结果,以下图所示。
结果一样正常保存到MongoDB中。
本节代码地址为:https://github.com/Python3WebSpider/ScrapySplashTest。
所以,在Scrapy中,建议使用Splash处理JavaScript动态渲染的页面。这样不会破坏Scrapy中的异步处理过程,会大大提升爬取效率。并且Splash的安装和配置比较简单,经过API调用的方式实现了模块分离,大规模爬取的部署也更加方便。