Scrapy Splash 用来爬取动态网页,其效果和scrapy selenium phantomjs一样,都是通过渲染js得到动态网页然后实现网页解析,selenium+phantomjs是用selenium的Webdriver操作浏览器,然后用phantomjs执行渲染脚本得到结果,一般再用BeautifulSoup处理。Splash是官推的js渲染引擎,和Scrapy结合比较好,使用的是webkit开发的轻量级无界面浏览器,渲染之后结果和静态爬取一样可以直接用xpath处理。只是splash是在docker中运行。
scrapy-splash package网址:https://pypi.python.org/pypi/scrapy-splash
splash官网:http://splash.readthedocs.io/en/stable/scripting-ref.html
1 开发环境
windows 10
python3
vscode
docker
2 docker 安装
下载:https://store.docker.com/editions/community/docker-ce-desktop-windows
3 安装scrapy-splash
pip install scrapy-splash
运行splash
$ docker run -p 8050:8050 scrapinghub/splash
运行无异常之后,可以在浏览器中输入网址,看到运行效果,可以在右边自己写lua脚本测试是否达到效果,也自带了部分lua脚本可以查看
4 页面结构分析
demo是爬取京东图书关于python的图书,包含书名,价格,购买链接。京东页面在对商品的价格做了动态加载,页面切换也是用js去完成,所以在静态爬取无法实现。
先分析一下页面,用firefowx开发工具查看页面,京东在进入商品页面时只有30个item,但是当把页面拉到底部时,会再次加载剩余部分30个item。我们在debug控制中输入js代码:
document.getElementById("J-global-toolbar").scrollIntoView()
实现自动设置可视到底部,达到页面自动加载全部item,这里用注意的是用 getElementsByClassName时没有scrollIntoView()方法。
在切换到下一页时,页面是调用的一个js方法 SEARCH.page(3, true),我们会调用此js进行自动换页,当切换到到最后一页时,class=‘pn-next disabled’,这个作为我们判断是否已经爬取完所有页面。
综合上面分析,爬取过程是需先用lua脚本执行js加载完成整个页面,完成爬取之后判断是否已经是最后一页,再执行js跳转到下一页。
5 代码实现
按部就班,用命令新建一个基本的spider
scrapy startproject jdscrapy
cd jdscrapy
scrapy genspider jd jd.com
然后对setting.py进行配置
添加splash运行地址
SPLASH_URL = 'http://localhost:8050/'
DOWNLOADER_MIDDLEWARES 中添加Splash middleware,未防止被发现发现是爬虫而被封,这里有添加User-Agent信息。
DOWNLOADER_MIDDLEWARES = {
'scrapy_splash.SplashCookiesMiddleware': 723,
'scrapy_splash.SplashMiddleware': 725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
'jdscrapy.RandomUserAgent.RandomUserAgent':400
}
添加SPIDER_MIDDLEWARES
SPIDER_MIDDLEWARES = {
'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}
添加DUPEFILTER_CLASS去重
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
jd.py具体代码
# -*- coding: utf-8 -*-
import scrapy
from scrapy import Request
from scrapy_splash import SplashRequest
from jdscrapy.items import SplashdemoItem
import re
#设置加载完整的页面
lua_loadall="""
function main(splash)
splash:go(splash.args.url)
splash:wait(10)
splash:runjs('document.getElementById("J-global-toolbar").scrollIntoView()')
splash:wait(10)
return splash:html()
end
"""
#跳转下一页,返还下一页的url地址
def Getlua_next(pageNum):
lua_next= """
function main(splash)
splash:go(splash.args.url)
splash:wait(2)
splash:runjs("SEARCH.page(%s, true)")
splash:wait(2)
return splash:url()
end
"""%(str(pageNum))
return lua_next
class JdSpider(scrapy.Spider):
name = "jd"
allowed_domains = ["jd.com"]
start_urls = ['https://search.jd.com/Search?keyword=Python&enc=utf-8&wq=Python']
#开始页面 初始化page参数为1
def start_requests(self):
for url in self.start_urls:
#开始
yield Request(url,callback=self.parse_url,meta={'page':1},dont_filter=True)
#第一次单独处理
def parse_url(self,response):
url =response.url
metadata={'page':response.meta["page"]}
#第一次加载剩余item-
yield SplashRequest(url,meta=metadata,endpoint='execute',args={'lua_source':lua_loadall},cache_args=['lua_source'])
#根据跳转页面返回回来的url地址进行页面完整加载
def parse_url2(self,response):
url=response.body_as_unicode()
#加载剩余item
yield SplashRequest(url,meta={'page':response.meta['page']},endpoint='execute',args={'lua_source':lua_loadall},cache_args=['lua_source'])
#处理数据并判断是否继续爬取
def parse(self, response):
#处理数据
pagenum=int(response.meta['page'])
for book in response.xpath('.//li[@class="gl-item"]'):#获取所有item
url=book.xpath('.//div[@class="p-name"]/a/@href').extract_first()#获取item的url
bookname = book.xpath('.//div[@class="p-name"]/a/em').extract_first()
#正则提取替换得到书名
rebookname=re.compile(r'<.*?>')
price =book.xpath('.//div[@class="p-price"]/strong/i/text()').extract_first()#获取价格
if 'https'not in url:#争对自营的部分 添加http
url=response.urljoin(url)
item= SplashdemoItem()
item['BookName']=rebookname.sub('',bookname)#根据正则进行反向替换,去掉书名中的一些元素
item['Price']=price
item['BuyLink']=url
yield item
#翻页 判断是否到最后一页
if len(response.xpath('.//div[@class="pn-next disabled"]/em/b/text()').extract())<= 0 :
yield SplashRequest(response.url,meta={'page':pagenum+1},callback=self.parse_url2,endpoint='execute',args={'lua_source': Getlua_next(2*pagenum+1)},cache_args=['lua_source'],dont_filter=True)
items.py
# -*- coding: utf-8 -*-
import scrapy
class SplashdemoItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
BookName =scrapy.Field()
Price=scrapy.Field()
BuyLink=scrapy.Field()
下次会在此基础上配置 Scrapy_redis的使用