scrapy结合selenium抓取武汉市环保局空气质量日报

1.前言

        目标网站:武汉市环境保护局(http://hbj.wuhan.gov.cn/viewAirDarlyForestWaterInfo.jspx)。scrapy对接selenium模块抓取空气质量日报数据,需要搭建selenium运行的相应环境,大概搭建方法参见:selenium基本使用;主要是模块的安装和驱动的下载与安装,windows下好像不支持Chorme的无头浏览器,但是有界面的浏览器速度会相对较慢,有条件搭建linux的,用linux下的Chorme headless驱动会快很多;其他的,像火狐等主流浏览器也有对应的驱动,环境搭建差不多,本文用的就是windows下谷歌的驱动(慢就慢点吧);Phantomjs无头浏览器好像现在不能用了。

注意:Chorme浏览器的驱动下载需要结合自己电脑的浏览器版本下载对应的驱动,不然可能驱动用不了。

2.任务分析

抓取武汉市环境保护局的空气质量日报,该网站数据是采用异步加载的;抓包分析可知,整个过程都是对同一个url进行抓取(注:这是一个点,后续编码需要考虑的);因为是用selenium点开网页,所以不需要考虑POST还是GET请求。

3.代码逻辑

3.1 创建scrapy项目

基础的项目创建、爬虫创建及创建后项目的文件结构等内容,就不一一写了,基本使用网上有很多博文,直接上正文了。

3.2 明确抓取字段

来到items.py文件,明确待抓取字段。

# -*- coding: utf-8 -*-
import scrapy

class EnvprotectItem(scrapy.Item):

    # 日期
    date = scrapy.Field()
    # 点位
    loca = scrapy.Field()
    # SO2
    SO_2 = scrapy.Field()
    # NO2
    NO_2 = scrapy.Field()
    # 吸入颗粒
    PMIO = scrapy.Field()
    # CO
    CO_1 = scrapy.Field()
    # O3
    O3_d = scrapy.Field()
    # 细颗粒物
    PM25 = scrapy.Field()
    # 空气质量指数
    AQIe = scrapy.Field()
    # 首要污染物
    prmy = scrapy.Field()
    # AQI级别
    AQIl = scrapy.Field()
    # AQI类别
    AQIt = scrapy.Field()

3.3 编写爬虫逻辑

到spiders文件夹下的爬虫文件中,开始编写爬虫逻辑。

从第一次selenium请求后的结果中,解析出共多少条数据,以此确定共多少个页面;

从返回的网页源代码中解析数据;

模拟点击“下一页”,获取数据后,继续解析数据,直至解析完所有页面;

selenium模拟点击操作的代码都在middlewares.py的下载中间件中编写;

scrapy会默认过滤掉重复请求(即目标url相同),我们是对同一目标url爬取,因此注意重复请求的设置。

# -*- coding: utf-8 -*-
import math
import scrapy
from EnvProtect.items import EnvprotectItem

class ProtectenvSpider(scrapy.Spider):
    name = 'ProtectEnv'
    # allowed_domains = ['hbj.wuhan.gov.cn']
    # start_urls = ['http://hbj.wuhan.gov.cn/']
    page=1
    pages=1
    # 目标url
    base_url = 'http://hbj.wuhan.gov.cn/viewAirDarlyForestWaterInfo.jspx'

    def start_requests(self):
yield scrapy.Request( url=self.base_url, callback=self.parse, dont_filter=True, # 设置不过滤重复请求,scrapy默认过滤重复请求 meta={'index':1} # 该参数判断是否为第一次请求 ) def parse(self, response): """ 第一次请求返回结果中解析出,指定时间段(在middlewares.py文件中指定,后续介绍)内一共有多少条数据; 由于一直是对同一个页面进行爬取(翻页时url没变,数据变了),数据共多少条(页)确定一次就够了 :param response: :return: """ if response.meta['index']: counts = response.xpath("//div[@class='serviceitempage fr']/span[@class='fl']/text()").extract_first() counts = int(counts.split(' ')[0]) self.pages = math.ceil(counts / 22) # 确定一共多少个页面 # 解析数据 node_list = response.xpath('//*[@id="tableForm"]/div/div[3]/table/tbody/tr')[1:] for node in node_list: item = EnvprotectItem() item['date'] = node.xpath("./td[1]/text()").extract_first() item['loca'] = node.xpath("./td[2]/text()").extract_first() item['SO_2'] = node.xpath("./td[3]/text()").extract_first() item['NO_2'] = node.xpath("./td[4]/text()").extract_first() item['PMIO'] = node.xpath("./td[5]/text()").extract_first() item['CO_1'] = node.xpath("./td[6]/text()").extract_first() item['O3_d'] = node.xpath("./td[7]/text()").extract_first() item['PM25'] = node.xpath("./td[8]/text()").extract_first() item['AQIe'] = node.xpath("./td[9]/text()").extract_first() item['prmy'] = node.xpath("./td[10]/text()").extract_first() item['AQIl'] = node.xpath("./td[11]/text()").extract_first() item['AQIt'] = node.xpath("./td[12]/text()").extract_first() yield item # 编写爬虫停止运行逻辑 if self.page < self.pages: self.page += 1 yield scrapy.Request( url = self.base_url, callback=self.parse, dont_filter=True, # 不过滤重复请求,scrapy默认过滤重复请求 meta={'index':0} )

3.4 编写下载中间件

selenium的所有操作的代码都写在下载中间件中。

# -*- coding: utf-8 -*-
import time
import scrapy
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

from EnvProtect.settings import USER_AGENTS as ua

class EnvprotectDownloaderMiddleware(object):

    def __init__(self):
        """
        第一页时,不需要点击跳转;其他页面需要模拟点击跳转来获取数据
        """
        self.index = 1  

    def process_request(self, request, spider):
    
        if request.url == 'http://hbj.wuhan.gov.cn/viewAirDarlyForestWaterInfo.jspx':
            
            self.driver = webdriver.Chrome()  # 实例化一个谷歌浏览器
            self.driver.get(request.url)  # 请求页面
            wait = WebDriverWait(self.driver, 30)  # 等待页面数据加载,等待30s
            try:
                # 选择城区
                wait.until(EC.frame_to_be_available_and_switch_to_it((By.ID, "iframepage"))) # 等待iframe标签出现
                options = self.driver.find_element_by_xpath("//select[@id='typedictionary']/option[2]")
                options.click()

                # 选择时间
                self.driver.find_element_by_id('cdateBeginDic').send_keys('2018-11-01')
                self.driver.find_element_by_id('cdateEndDic').send_keys('2019-01-20')

                # 点击查询
                self.driver.find_element_by_xpath("//a[@href='#' and @onclick='toQuery(2);']").click()
                time.sleep(5)

                # 指定页面
                if not self.index == 1:
                    self.index += 1  # 第一个页面不用跳转,其他页面需要跳转过去
                    self.driver.find_element_by_id('goPag').send_keys(str(self.index))
                    self.driver.find_element_by_id('_goPag').click()  # 跳转到该页面
            except:
                print("Error!")
                self.driver.quit()

            # 构造返回response
            html = self.driver.page_source
            self.driver.quit()
            response = scrapy.http.HtmlResponse(url=request.url, body=html, request=request, encoding='utf-8')

            return response

3.5 数据保存逻辑

在pipelines文件中编写数据保存逻辑,此处将数据保存为excel文件。

# -*- coding: utf-8 -*-
from openpyxl import Workbook

class EnvprotectPipeline(object):

    def __init__(self):
        # 创建excel表格保存数据
        self.workbook = Workbook()
        self.booksheet = self.workbook.active
        self.booksheet.append(['日期', '检测点位', '二氧化硫',
                          '二氧化氮', '可吸入颗粒物', '一氧化碳',
                          '臭氧', '细颗粒物', '空气质量指数',
                          '首要污染物', 'AQI指数级别', 'AQI指数类别'])

    def process_item(self, item, spider):

        DATA = [
            item['date'], item['loca'], item['SO_2'],
            item['NO_2'], item['PMIO'], item['CO_1'],
            item['O3_d'], item['PM25'], item['AQIe'],
            item['prmy'], item['AQIl'], item['AQIt']
        ]

        self.booksheet.append(DATA)
        self.workbook.save('./results.xls')
        return item

3.6  其他

1.在settings.py文件中打开对应的pipe通道;

2.关闭robot.txt协议

4.完整代码

参见:github地址

原文地址:https://www.cnblogs.com/pythoner6833/p/10308457.html