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

image

4 页面结构分析

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

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

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的使用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容