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) + '\n'
        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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM