• scrapy下载中间件结合selenium抓取全国空气质量检测数据


    1、所需知识补充

    1.下载中间件常用函数

    •  process_request(self, request, spider):
      • 当每个request通过下载中间件是,该方法被调用
      • process_request()函数必须返回一下其中之一:一个None,一个Response对象,一个Request对象或raise IgnoreRequest。
        如果返回None,Scrapy将继续处理该request,执行其他的中间件中相应的方法,直达合适的下载器处理函数(download handler)被调用,该request被执行(其response被下载);
        如果返回的是Response对象,scrapy将不会调用任何其他的process_request()或process_exception()方法,或相应的下载函数,其将返回该response,已安装的中间件的process_response()方法则会在每个response返回时被调用;
        如果其返回Request对象,scrapy则停止调用process_request()方法并重新调度返回的request。当心返回的request被执行后,相应的中间件链将会更具下载的response被调用。
        如果其raise一个IgnoreRequest异常,则安装的下载中间件的process_exception()方法会被调用。如果没有任何一个方法处理该异常,则request的errback(Request.errback)方法会被调用,如果没有代码吹抛出的异常,则该异常被忽略且不记录(不同于其他异常那样)
      • 参数:
        request(Request对象)--处理的request
        spider(Spider对象)--该request对应的spider
    • process_response(self, request, spider):
      • 当下载器完成http请求,传递响应给引擎的时候调用
      • process_response()必须返回以下其中之一:返回一个Request对象或raise一个IgnorRequest异常
        如果其返回一个Response(可以与传入的response相同,也可以是全新的对象),该response会被在链中其他中间件的process_response()方法处理。
        如果其返回一个Request对象,则中间件链停止,返回的request会被重新调度下载,处理类似于process_request()返回request所做的那样。
        如果其抛出一个IgnorRequest异常,则调用request的errback(Request.errback)。如果没有代码处理抛出的异常,则该异常被忽略且不记录。
      • 参数:
        request(Request对象)--response所对应的request
        response(Response对象)--被处理的response对象
        spider(Spider对象)--response所对应的spider

    2.scrapy对接selenium

    scrapy通过设置setting.py文件里的DOWNLOADER_MIDDLEWARES添加自己编写的下载中间件,通常将运用到的selenium相关内容写在这个下载中间件中,具体后面会有代码说明。

    selenium的基本使用参见:http://www.cnblogs.com/pythoner6833/p/9052300.html

    3.常用settings的内置设置

    • BOT_NAME
      默认:“scrapybot”,使用startproject命令创建项目时,其被自动赋值
    • CONCURRENT_ITEMS
      默认为100,Item Process(即Item Pipeline)同时处理(每个response的)item时最大值
    • CONCURRENT_REQUEST
      默认为16,scrapy downloader并发请求(concurrent requests)的最大值
    • LOG_ENABLED
      默认为True,是否启用logging
    • DEFAULT_REQUEST_HEADERS
      默认如下:{'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en',}
      scrapy http request使用的默认header
    • LOG_ENCODING
      默认utt-8,logging中使用的编码
    • LOG_LEVEL
      默认“DEBUG”,log中最低级别,可选级别有:CRITICAL,ERROR,WARNING,DEBUG
    • USER_AGENT
      默认:“Scrapy/VERSION(....)”,爬取的默认User-Agent,除非被覆盖
    • COOKIES_ENABLED=False,禁用cookies
    • PEOXIE:代理设置
      例如:
      PROXIES = [
        {'ip_port': '111.11.228.75:80', 'password': ''},
        {'ip_port': '120.198.243.22:80', 'password': ''},
        {'ip_port': '111.8.60.9:8123', 'password': ''},
        {'ip_port': '101.71.27.120:80', 'password': ''},
        {'ip_port': '122.96.59.104:80', 'password': ''},
        {'ip_port': '122.224.249.122:8088', 'password':''},
      ]

    参考链接:

    2、案例分析

    分析:

    一共需要抓取三个页面,首先抓取第一个页面的所有城市名及对应的链接,地址:https://www.aqistudy.cn/historydata/

    然后抓取具体的,每个城市,每个月份的信息(就是年月),地址:https://www.aqistudy.cn/historydata/monthdata.php?city=%E5%AE%89%E5%BA%B7,这里只是其中一个城市

    最后抓取每个月份中,每一天的数据,示例地址:https://www.aqistudy.cn/historydata/daydata.php?city=%E5%AE%89%E5%BA%B7&month=2015-01

    其中,第一个页面为静态页面,直接抓取上面的城市信息即可;第二个和第三页面时动态页面,采用selenium结合Phantomjs抓取(也可以用Google浏览器。)

    1. 创建一个项目

    scrapy startproject ChinaAir

    2.明确需要抓取的字段

    在items.py文件中定义需要抓取的字段,编写相关代码。

    # -*- coding: utf-8 -*-
    
    # Define here the models for your scraped items
    #
    # See documentation in:
    # https://doc.scrapy.org/en/latest/topics/items.html
    
    import scrapy
    
    class ChinaairItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        """
        首先明确抓取目标,包括城市,日期,指标的值
        """
        # 城市
        city = scrapy.Field()
        # 日期
        date = scrapy.Field()
        # 空气质量指数
        AQI = scrapy.Field()
        # 空气质量等级
        level = scrapy.Field()
        # pm2.5的值
        PM2_5 = scrapy.Field()
        # pm10
        PM10 = scrapy.Field()
        # 二氧化硫
        SO2 = scrapy.Field()
        # 一氧化碳
        CO = scrapy.Field()
        # 二氧化氮
        NO2 = scrapy.Field()
        # 臭氧浓度
        O3_8h = scrapy.Field()
    
        # 数据源(数据来源)
        source = scrapy.Field()
        # 抓取时间
        utc_time = scrapy.Field()

     3.生成爬虫文件

    创建名为airChina的爬虫,并给定初始地址。

    scrapy genspider airChina https://www.aqistudy.cn/historydata/

    来到爬虫文件,开始编写爬虫部分的代码。

    4.编写爬虫

    # -*- coding: utf-8 -*-
    import scrapy
    from ChinaAir.items import ChinaairItem
    
    class AirchinaSpider(scrapy.Spider):
        name = 'airChina'
        allowed_domains = ['aqistudy.cn']
        base_url = "https://www.aqistudy.cn/historydata/"
        # 抓取首页
        start_urls = [base_url]
    
        def parse(self, response):
    
            # 拿到页面的所有城市名称链接
            url_list = response.xpath('//div[@class="all"]/div[@class="bottom"]//a/@href').extract()# 拿到页面的所有城市名
            city_list = response.xpath('//div[@class="all"]/div[@class="bottom"]//a/text()').extract()# 将城市名及其对应的链接,进行一一对应
            for city, url in zip(city_list, url_list):
    
                # 拼接该城市的链接
                link = self.base_url + url
                yield scrapy.Request(url=link, callback=self.parse_month, meta={"city": city})
      def parse_month(self, response):
        pass

    在yield后,来到下载中间件文件,由于每一个请求都要经过下载中间件,因此,从第一个页面中解析到的url,请求时,可以在下载中间件中进行一定的操作,如利用selenium进行请求。

    来到middlerwares.py文件,删掉所有已写好的内容,重新编写我们需要的内容。

    # -*- coding: utf-8 -*-
    
    # Define here the models for your spider middleware
    #
    # See documentation in:
    # https://doc.scrapy.org/en/latest/topics/spider-middleware.html
    
    import random
    # 导入User-Agent列表
    from ChinaAir.settings import USER_AGENT as ua_list
    
    # class UserAgentMiddlerware(object):
    #     """
    #     定义一个中间件,给每一个请求随机选择USER_AGENT
    #     注意,不要忘了在setting文件中打开DOWNLOADER_MIDDLERWARE的注释
    #     """
    #     def process_request(self, request, spider):
    #
    #         # 从ua_list中随机选择一个User-Agent
    #         user_agent = random.choice(ua_list)
    #         # 给请求添加头信息
    #         request.headers['User-Agent'] = user_agent
    #         # 当然,也可以添加代理ip,方式如下,此处不用代理,仅说明代理使用方法
    #         # request.meta['proxy'] = "..."
    #         print(request.headers['User-Agent'])
    
    import time
    import scrapy
    from selenium import webdriver
    
    class SeleniumMiddlerware(object):
        """
        利用selenium,获取动态页面数据
        """
        def process_request(self, request, spider):
    
            # 判断请求是否来自第二个页面,只在第二个页面调用浏览器
            if not request.url == "https://www.aqistudy.cn/historydata/":
                # 实例化。selenium结合谷歌浏览器,
                self.driver = webdriver.PhantomJS() # 实在受不了每次测试都打开浏览器界面,所以换成无界面的了
                # 请求
                self.driver.get(request.url)
                time.sleep(2)
    
                # 获取请求后得到的源码
                html = self.driver.page_source
                # 关闭浏览器
                self.driver.quit()
    
                # 构造一个请求的结果,将谷歌浏览器访问得到的结果构造成response,并返回给引擎
                response = scrapy.http.HtmlResponse(url=request.url, body=html, request=request, encoding='utf-8')
                return response

    其中,注释部分为,下载中间件给每一个请求分配一个随机的User-Agent及代理IP的方法,当然,此处用不上,因此,不用管。

    由于每一次产生request请求,都要经过下载中间件,因此,写一个判定条件,只有是来自第二个页面的请求时,才采用selenium来执行。

    代码的最后一行,下载中间件将selenium请求后的结果,再构造成一个response,返回给引擎,继续后续处理。注意,要在settings.py文件中将下载中间件的注释打开。

    拿到第二页返回的response时,继续来到爬虫文件,对response进行解析和提取第三页中需要的url,代码如下:

    class AirchinaSpider(scrapy.Spider):
        name = 'airChina'
        allowed_domains = ['aqistudy.cn']
        base_url = "https://www.aqistudy.cn/historydata/"
        # 抓取首页
        start_urls = [base_url]
    
        def parse(self, response):
    
            # 拿到页面的所有城市名称链接
            url_list = response.xpath('//div[@class="all"]/div[@class="bottom"]//a/@href').extract()[:1]
            # 拿到页面的所有城市名
            city_list = response.xpath('//div[@class="all"]/div[@class="bottom"]//a/text()').extract()[:1]
    
            # 将城市名及其对应的链接,进行一一对应
            for city, url in zip(city_list, url_list):
    
                # 拼接该城市的链接
                link = self.base_url + url
                yield scrapy.Request(url=link, callback=self.parse_month, meta={"city": city})
    
        def parse_month(self, response):
            """
            拿到每个城市的,每个月份的数据
            此页面为动态页面,这里利用selenium结合浏览器获取动态数据
            因此在下载中间件中添加中间件代码
            :param response:
            :return:
            """
            # 获取城市每个月份的链接
            url_list = response.xpath('//tr/td/a/@href').extract()[:1]
    
            for url in url_list:
                url = self.base_url + url  # 构造该url
                yield scrapy.Request(url=url, meta={'city': response.meta['city']}, callback=self.parse_day)

    拿到第二页的数据后,解析出第三页请求的url后,回调,并提取出需要抓取的数据,就完成了爬虫部分的代码。因此,整个爬虫文件的代码如下:

    # -*- coding: utf-8 -*-
    import scrapy
    from ChinaAir.items import ChinaairItem
    
    class AirchinaSpider(scrapy.Spider):
        name = 'airChina'
        allowed_domains = ['aqistudy.cn']
        base_url = "https://www.aqistudy.cn/historydata/"
        # 抓取首页
        start_urls = [base_url]
    
        def parse(self, response):
    
            # 拿到页面的所有城市名称链接
            url_list = response.xpath('//div[@class="all"]/div[@class="bottom"]//a/@href').extract()[:1]
            # 拿到页面的所有城市名
            city_list = response.xpath('//div[@class="all"]/div[@class="bottom"]//a/text()').extract()[:1]
    
            # 将城市名及其对应的链接,进行一一对应
            for city, url in zip(city_list, url_list):
    
                # 拼接该城市的链接
                link = self.base_url + url
                yield scrapy.Request(url=link, callback=self.parse_month, meta={"city": city})
    
        def parse_month(self, response):
            """
            拿到每个城市的,每个月份的数据
            此页面为动态页面,这里利用selenium结合浏览器获取动态数据
            因此在下载中间件中添加中间件代码
            :param response:
            :return:
            """
            # 获取城市每个月份的链接
            url_list = response.xpath('//tr/td/a/@href').extract()[:1]
    
            for url in url_list:
                url = self.base_url + url  # 构造该url
                yield scrapy.Request(url=url, meta={'city': response.meta['city']}, callback=self.parse_day)
    
        def parse_day(self, response):
            """
            获取每一天的数据
            :param response:
            :return:
            """
            node_list = response.xpath('//tr')
    
            node_list.pop(0)
            for node in node_list:
                # 解析目标数据
                item = ChinaairItem()
                item['city'] = response.meta['city']
                item['date'] = node.xpath('./td[1]/text()').extract_first()
                item['AQI'] = node.xpath('./td[2]/text()').extract_first()
                item['level'] = node.xpath('./td[3]/text()').extract_first()
                item['PM2_5'] = node.xpath('./td[4]/text()').extract_first()
                item['PM10'] = node.xpath('./td[5]/text()').extract_first()
                item['SO2'] = node.xpath('./td[6]/text()').extract_first()
                item['CO'] = node.xpath('./td[7]/text()').extract_first()
                item['NO2'] = node.xpath('./td[8]/text()').extract_first()
                item['O3_8h'] = node.xpath('./td[9]/text()').extract_first()
                yield item

    5.编写pipelines文件

    抓取到数据后,就可以开始写保存数据的逻辑了,这里仅仅将数据写成json格式的数据。

    # -*- coding: utf-8 -*-
    
    # Define your item pipelines here
    #
    # Don't forget to add your pipeline to the ITEM_PIPELINES setting
    # See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
    import json
    from datetime import datetime
    
    class ChinaAirPipeline(object):
        def process_item(self, item, spider):
            item["source"] = spider.name
            item['utc_time'] = str(datetime.utcnow())
            return item
    
    class ChinaAirJsonPipeline(object):
        def open_spider(self, spider):
            self.file = open('air.json', 'w', encoding='utf-8')
    
        def process_item(self, item, spider):
            content = json.dumps(dict(item), ensure_ascii=False) + '
    '
            self.file.write(content)
    
        def close_spider(self, spider):
            self.file.close()

    ChinaAirPipeline是在接收到管道丢过来的item后,继续添加两个自读,抓取时间和数据的来源,并在添加后,继续通过管道丢给下面的ChinaAirJsonPipelines文件,进行保存。

    其中,不要忘了在settings.py文件中注册管道信息。

    6.运行爬虫,抓取数据

    scrapy crawl airChina

    3、完整代码

    参见:https://github.com/zInPython/ChinaAir/tree/master/ChinaAir

  • 相关阅读:
    如何在Element 使用正则表达式校验
    Vs Code 微信小程序 神兵利器合集
    分享CSS公共类库(能在项目快捷使用CSS类)
    Element+Axios上传图片 OR 文件
    两种简洁的数组对象去重姿势
    VScode 格式化代码保存时使用ESlint修复代码
    如何在linux上部署vue项目
    Vue 2.x 3.x 配置项目开发环境跟线上环境
    在Vue文件中引入外部URL链接
    博客目录
  • 原文地址:https://www.cnblogs.com/pythoner6833/p/9049498.html
Copyright © 2020-2023  润新知