使用Scrapy Splash爬取京东手机信息
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跳转到下一页。
代码实现:
对setting.py进行配置
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,
}
添加splash运行地址
SPLASH_URL = 'http://localhost:8050/'
添加DUPEFILTER_CLASS去重
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
主代码:
# -*- coding: utf-8 -*-
import re
import scrapy
from scrapy_splash import SplashRequest
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 ExampleSpider(scrapy.Spider):
name = 'jd'
allowed_domains = ['jd.com']
# start_urls = ['https://search.jd.com/Search?keyword=Python&enc=utf-8&wq=Python']
def start_requests(self):
scrapy = open("taobao.lua").read()
url = "https://search.jd.com/Search?keyword=Python&enc=utf-8&wq=Python"
yield SplashRequest(url=url,callback=self.parse,meta={"page":1},endpoint="execute",args={"lua_source":scrapy,"url":url})
def parse_url2(self, response):
scrapy = open("taobao.lua").read()
url = response.body_as_unicode()
# 加载剩余item
yield SplashRequest(url, meta={'page': response.meta['page']}, endpoint='execute',args={'lua_source': scrapy})
def parse(self, response):
# with open("jj.html","a",encoding="utf-8") as f:
# f.write(response.text)
pagenum = int(response.meta['page'])
posts = response.css("li.gl-item")
for post in posts:
books = post.css("div.p-name > a > em").extract_first()
bookname=re.compile(r'<.*?>').sub("",books)
prices = post.css("div.p-price strong").extract_first()
price = re.compile(r'<.*?>').sub("", prices)
print(bookname)
print(price)
# 翻页 判断是否到最后一页
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)}, dont_filter=True)
lua文件:lua文件一定要和你创建的项目同级
taobao.lua文件:
代码如下:
function main(splash)
splash:set_user_agent("Mozilla/5.0 Chrome/69.0.3497.100 Safari/537.36")
splash:go(splash.args.url)
splash:wait(5)
splash:runjs('document.getElementById("J-global-toolbar").scrollIntoView()')
splash:wait(5)
return {html=splash:html()}
end
扩展部分:京东的二段加载采用上面方法,如果不是二段加载则采用下面的lua方法。一直下拉加载就采用下面方法。
代码如下:
function main(splash, args)
splash:set_user_agent("Mozilla/5.0 Chrome/69.0.3497.100 Safari/537.36")
splash:go(args.url)
local scroll_to = splash:jsfunc("window.scrollTo")
scroll_to(0, 2800)
splash:set_viewport_full()
splash:wait(5)
return {html=splash:html()}
end