Python爬虫之页面js渲染(scrapy+scrapy-splash+Docker实现)

​发现问题 

    早些时候,笔者初学网络爬虫,想要做一个小爬虫,小试牛刀。分析网页时,用Chrome或者FireFox浏览器(个人推荐用FireFox,抓包效果更好)获取网页数据进行分析,这时数据都是非常完整的,如下图所示:

 

Python爬虫之页面js渲染(scrapy+scrapy-splash+Docker实现)_第1张图片

 

     然后屁颠颠去写代码,什么requests、urllib的各种各样的库,再用xpath、正则表达式、beautifulsoup去解析网页,结果发现屁数据也没有,如下图所示:

Python爬虫之页面js渲染(scrapy+scrapy-splash+Docker实现)_第2张图片

 

    然后就只能辛辛苦苦地去抓包,看数据都放哪儿了,而且还不一定能找到,简直无奈啊,真是气skr人。

找到原因

    这种网站的内容由前端的JS动态生成,由于呈现在网页上的内容是由JS生成而来,我们能够在浏览器上看得到,但是在HTML源码中却发现不了。

解决办法

    有三种方法,分别是:

(1)Selenium+webdriver(Chrome或者FireFox):这要求你系统有对应浏览器,并且过程中要全程开浏览器。说白了,就是你通过浏览器能看到啥,就能抓到啥。一般遇到特别复杂的验证码时,这个方法是有必要的,当然,开着浏览器爬虫的效率可想而知。

 

(2)selenium+phantomjs。PhantomJS是一个WebKit,他的使用方法和webdriver一样,但是他不需要开浏览器,简单地讲,它PhantomJS就是一个无界面的浏览器,你可以直接跑在无需GUI的linux服务器上,这点很赞。

 (3)第三种方法就是本文主要阐述的内容,那就是基于Docker容器的scrapy-splash JS渲染服务和python分布式爬虫框架Scrapy,scrapy-splash作为js渲染服务,是基于Twisted和QT开发的轻量浏览器引擎,并且提供直接的http api,快速、轻量的特点使其容易进行分布式开发。 splash和scrapy融合,两种互相兼容彼此的特点,抓取效果甚佳。

    那么接下来笔者就手把手教大家第三种方法

首先,安装Docker容器,以及安装、开启Splash服务

    如何安装笔者就不详述,自行百度教程,非常简单。安装好Docker,进入有小鲸鱼的终端,输入如下指令安装splash服务(安装过程比较慢,请大家耐心等待)

dock pull scrapinghub/splash

如下图:

Python爬虫之页面js渲染(scrapy+scrapy-splash+Docker实现)_第3张图片

 

安装成功后,输入如下指令开启splash(指定服务端口为8050):

docker run -p 8050:8050 scrapinghub/splash

编写爬虫

    笔者在这里以爬取去哪儿旅行官网南昌到杭州的火车票的详细数据,使用的开发工具为pycharm,指定框架为Scrapy。

首先,我们配置settings.py一些重要参数,如下代码,相应的参数解释已在代码中注明。设置渲染服务的url一定不能写错,协议(http),IP(开启Docker默认的IP),端口号(8050,开启splash服务指定的端口号),如果写错,电脑会积极拒绝。

# 最好将机器协议修改为False,如果为true,大概率爬取不到东西
ROBOTSTXT_OBEY = False
# 开启ITEM_PIPELINES,使其可以ITEM_PIPELINES处理item数据
ITEM_PIPELINES = {
   'GoWhere.pipelines.GowherePipeline': 300,
}
# 设置爬虫中间件
SPIDER_MIDDLEWARES = {
#    'Douban.middlewares.DoubanSpiderMiddleware': 543,
     'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}
​
#设置渲染服务的url,这是js渲染的关键之所在,这里的url即为刚刚开启Docker的Ip加splash服务指定的端口
SPLASH_URL="http://192.168.99.100:8050"
​
​
# 添加Splash中间件,指定相应的优先级
#这里配置了三个下载中间件( DownloadMiddleware),是scrapy-splash的核心部分,我们不需要
#像对接selenium那样自己定制中间件,scrapy-splash已经为我们准备好了,直接配置即可
DOWNLOADER_MIDDLEWARES = {
   # 'Douban.middlewares.DoubanDownloaderMiddleware': 543,
    'scrapy_splash.SplashCookiesMiddleware': 723,
    'scrapy_splash.SplashMiddleware': 725,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}
​
HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 0
HTTPCACHE_DIR = 'httpcache'
​
# 去重过滤器
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
#使用splash的http缓存
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'

接下来,编写items.py文件

class GowhereItem(scrapy.Item):
    # 车次
    train = scrapy.Field()
    # 起始站
    where = scrapy.Field()
    # 终点站
    to = scrapy.Field()
    # 发车起始时间
    time_from = scrapy.Field()
    # 到站时间
    time_to = scrapy.Field()
    # 乘车时间
    time = scrapy.Field()
    # 票价详情
    price1 = scrapy.Field()
    num1 = scrapy.Field()
    price2 = scrapy.Field()
    num2 = scrapy.Field()
    price3 = scrapy.Field()
    num3 = scrapy.Field()
    price4 = scrapy.Field()
    num4 = scrapy.Field()

然后,爬虫的核心,编写spider,请求网页,清洗数据。强调一点,start_request需要重写,而且请求方法必须使用SplashRequest,否则前功尽弃。:

import scrapy
from scrapy_splash import SplashRequest
from GoWhere.items import GowhereItem
import re
​
class GowhereSpider(scrapy.Spider):
    name = 'gowhere'
    allowed_domains = ['qunar.com']
    start_urls = ['https://train.qunar.com/stationToStation.htm?fromStation=%E5%8D%97%E6%98%8C&toStation=%E6%9D%AD%E5%B7%9E&date=2019-07-27']
​
    def start_requests(self):
        headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36'}
        for url in self.start_urls:
            yield SplashRequest(url,callback=self.parse,headers = headers ,args={"wait":5})
​
    def parse(self, response):
        # 获取列车所有信息列表
        results = response.xpath("//div[@class='js_listinfo']")
        for result in results:
            list = GowhereItem()
            # 获取车次
            train = result.xpath('./div[1]/h3/text()').extract_first()
            # 获取出发站
            where = result.xpath('./div[2]/p[1]/span/text()').extract_first()
            # 获取终点站
            to = result.xpath('./div[2]/p[2]/span/text()').extract_first()
            # 发车时间
            time_form = result.xpath('./div[3]/time[1]/text()').extract_first()
            # 到站时间
            time_to = result.xpath('./div[3]/time[2]/text()').extract_first()
            # 乘车时间
            time = result.xpath('./div[4]/time/text()').extract_first()
            # 票价
            price1 =result.xpath("./div[5]/p[1]/text()").extract_first() +" ¥" + result.xpath('./div[5]/p[1]/span/text()').extract_first()
            num1 = result.xpath('./div[6]/p[1]/text()').extract_first()
            price2 =result.xpath('./div[5]/p[2]/text()').extract_first() +" ¥" + result.xpath('./div[5]/p[2]/span/text()').extract_first()
            num2 = result.xpath('./div[6]/p[2]/span/text()').extract_first()
            list['train'] = train
            list['where'] = where
            list['to'] = to
            list['time_from'] = time_form
            list['time_to'] = time_to
            list['time'] = time
            list['price1'] = price1
            list['num1'] = num1
            list['price2'] = price2
            list['num2'] = num2
            ok = result.xpath('./div[5]/p[3]/text()').extract_first()
            if ok:
                price3 = result.xpath('./div[5]/p[3]/text()').extract_first()+ " ¥" + result.xpath(
                './div[5]/p[3]/span/text()').extract_first()
                num3 = result.xpath('./div[6]/p[3]/span/text()').extract_first()
                list['price3'] = price3
                list['num3'] = num3
            flag = result.xpath('./div[5]/p[4]/text()').extract_first()
            if flag:
                price4 = result.xpath('./div[5]/p[4]/text()').extract_first() + " ¥" + result.xpath(
                './div[5]/p[4]/span/text()').extract_first()
                num4 = result.xpath('./div[6]/p[4]/text()').extract_first()
                list['price4'] = price4
                if num4:
                    list['num4'] = num4
​
            yield list

最后,那就是编写Item pipelines,对item数据进行储存,笔者在这里使用本地储存(指定文件格式为csv)和mysql存储,代码具体如下:

class GowherePipeline(object):
    def __init__(self):
        # 以写模式打开csv格式的文件,若不存在,代码会自动创建该文件,利用第三个参数将把写入的数据项产生的空格删除
        self.file = open("Go.csv",'w',newline='',encoding="utf-8")
        # 设置文件第一行的名称,即数据列名称,要与spider传过来的字典key名称一致
        self.fieldnames = ['train','where','to','time_from','time_to','time','price1','num1','price2','num2','price3','num3','price4','num4']
        # 指定文件的写入方式为csv字典写入,参数1为指定具体文件,参数2为指定字段名
        self.writer = csv.DictWriter(self.file,fieldnames=self.fieldnames)
        # 写入第一行数据(列名称)
        self.writer.writeheader()
​
        self.db = connect('localhost','root','1234','book',charset = 'utf8')
​
​
​
    def process_item(self, item, spider):
        self.writer.writerow(item)
        print(type(item['train']))
        print(type(item['where']))
        cursor = self.db.cursor()
        sql = """ INSERT INTO test(train,address) VALUES ('{}','{}')""".format(item['train'],item['where'])
        cursor.execute(sql)
        self.db.commit()
        print("数据存储成功")
​
    def spider_close(self):
        self.file.close()
        self.db.close()

最后那就是输入指令运行爬虫了:

scrapy crawl gowhere

数据效果展示:Python爬虫之页面js渲染(scrapy+scrapy-splash+Docker实现)_第4张图片

Python爬虫之页面js渲染(scrapy+scrapy-splash+Docker实现)_第5张图片

如有阐述不清楚的地方,欢迎来我的订阅号提问,个人订阅号:鱼少侠

 

你可能感兴趣的:(python爬虫)