Scrapy Splash

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脚本可以查看

Scrapy Splash_第1张图片
image

4 页面结构分析

demo是爬取京东图书关于python的图书,包含书名,价格,购买链接。京东页面在对商品的价格做了动态加载,页面切换也是用js去完成,所以在静态爬取无法实现。

先分析一下页面,用firefowx开发工具查看页面,京东在进入商品页面时只有30个item,但是当把页面拉到底部时,会再次加载剩余部分30个item。我们在debug控制中输入js代码:
document.getElementById("J-global-toolbar").scrollIntoView()
实现自动设置可视到底部,达到页面自动加载全部item,这里用注意的是用 getElementsByClassName时没有scrollIntoView()方法

Scrapy Splash_第2张图片
image

在切换到下一页时,页面是调用的一个js方法 SEARCH.page(3, true),我们会调用此js进行自动换页,当切换到到最后一页时,class=‘pn-next disabled’,这个作为我们判断是否已经爬取完所有页面。

image
image

综合上面分析,爬取过程是需先用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的使用

你可能感兴趣的:(Scrapy Splash)