scrapy結合selenium進行動態加載頁面內容爬取

動態頁面和靜態頁面

比較常見的頁面形式:

動態頁面

靜態頁面

例如:

import requests
response = requests.get('https://www.baidu.com')
print(response.text.encode('raw_unicode_escape').decode())

但是動態頁面使用上述操作後發現,獲取到的內容與實際相差很大。

例如我們打開如下頁面:

https://www.aqistudy.cn/historydata/monthdata.php?city=北京

綜上基本可以明白靜態頁面和動態頁面的區別了。

有兩種方式可以獲取動態頁面的內容:

  • 破解JS,實現動態渲染
  • 使用瀏覽器模擬操作,等待模擬瀏覽器完成頁面渲染

由於第一個比較困難所以選擇方法二

需求分析

獲取各個城市近年來每天的空氣質量

  • 日期
  • 城市
  • 空氣質量指數
  • 空氣質量等級
  • pm2.5
  • pm10
  • so2
  • co
  • no2
  • o3

使用scrapy

scrapy操作的基本流程如下:

1.創建項目:scrapy startproject 項目名稱
2.新建爬蟲:scrapy genspider 爬蟲文件名 爬蟲基礎域名
3.編寫item
4.spider最後return item
5.在setting中修改pipeline配置
6.在對應pipeline中進行數據持久化操作

創建

打開命令行,輸入scrapy startproject air_history ,創建一個名爲air_history的scrapy項目

進入該文件夾,輸入scrapy genspider area_spider "aqistudy.cn",可以發現在spiders文件夾下多了一個名爲area_spider的py文件

文件目錄結構大概如下:

編寫item

根據需求編寫item如下,spider最後return item,把值傳遞給它

import scrapy

class AirHistoryItem(scrapy.Item):
    # define the fields for your item here like:
    data = scrapy.Field() #日期
    city = scrapy.Field() #城市
    aqi = scrapy.Field() #空氣質量指數
    level = scrapy.Field() #空氣質量等級
    pm2_5 = scrapy.Field() #pm2.5
    pm10 = scrapy.Field() #pm10
    so2 = scrapy.Field() #so2
    co = scrapy.Field() #co
    no2 = scrapy.Field() #no2
    o3 = scrapy.Field()  #o3

編寫爬蟲

首先可以得知首頁是https://www.aqistudy.cn/historydata/

所以將它賦值給一個名爲base_url的變量,方便後續使用

自動創建的爬出中攜帶了爬蟲的名字,這個name在啓動爬蟲的時候需要用到,現在暫時用不到

城市信息

進入首頁之後可以看到一大批的城市信息,所以我們第一步就是獲取有哪些城市

隨意點擊一個地區可以發現url變爲https://www.aqistudy.cn/historydata/monthdata.php?city=北京

所以url_list獲取到的是需要進行拼接的內容monthdata.php?city=城市名稱

city_list的最後部分是text()所以它拿到的是具體的文本信息

將獲取到的url_list和city_list逐個傳遞給scrapy.Request其中url是需要繼續爬取的頁面地址,city是item中需要的內容,所以將item暫時存放在meta中傳遞給下個回調函數self.parse_month

此步操作獲取了每個城市的全部月份信息,並拿到了每個月份的url地址。把上面傳遞下來的city繼續向下傳遞

最終數據

獲取到最終的URL之後,把item實例化,然後完善item字典並返回item

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

class AreaSpiderSpider(scrapy.Spider):
    name = 'area_spider'
    allowed_domains = ['aqistudy.cn']
    start_urls = ['https://www.aqistudy.cn/historydata/']
    base_url = 'https://www.aqistudy.cn/historydata/'

    def parse(self, response):
        print('爬取城市信息....')
        url_list = response.xpath("//div[@class='all']/div[@class='bottom']/ul/div[2]/li/a/@href").extract()  # 全部鏈接
        city_list = response.xpath("//div[@class='all']/div[@class='bottom']/ul/div[2]/li/a/text()").extract()  # 城市名稱
        for url, city in zip(url_list, city_list):
            url = self.base_url + url
            print('url:%s, city:%s'%(url, city))
            yield scrapy.Request(url=url, callback=self.parse_month, meta={'city': city})

    def parse_month(self, response):
        print('爬取{}月份...'.format(response.meta['city']))
        url_list = response.xpath('//tbody/tr/td/a/@href').extract()
        for url in url_list:
            url = self.base_url + url
            yield scrapy.Request(url=url, callback=self.parse_day, meta={'city': response.meta['city']})

    def parse_day(self, response):
        print('爬取最終數據...')
        item = AirHistoryItem()
        node_list = response.xpath('//tr')
        node_list.pop(0)  # 去除第一行標題欄
        for node in node_list:
            item['data'] = node.xpath('./td[1]/text()').extract_first()
            item['city'] = response.meta['city']
            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'] = node.xpath('./td[9]/text()').extract_first()
            yield item

使用中間件實現selenium操作

打開中間件文件middlewares.py

由於我是在服務器上進行爬取,所以我選擇使用谷歌的無界面瀏覽器chrome-headless

然後進行頁面渲染後的源碼獲取

request.url是傳遞到中間件的url,由於首頁是靜態頁面,所以首頁不進行selenium操作

middlewares全部代碼

from scrapy import signals
import scrapy
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time


class AreaSpiderMiddleware(object):
    def process_request(self, request, spider):
        chrome_options = Options()
        chrome_options.add_argument('--headless')  # 使用無頭谷歌瀏覽器模式
        chrome_options.add_argument('--disable-gpu')
        chrome_options.add_argument('--no-sandbox')
        
        # 指定谷歌瀏覽器路徑
        self.driver = webdriver.Chrome(chrome_options=chrome_options, executable_path='/usr/bin/chromedriver')
        if request.url != 'https://www.aqistudy.cn/historydata/':
            self.driver.get(request.url)
            time.sleep(1)
            print(self.driver.title)
            html = self.driver.page_source
            self.driver.quit()
            return scrapy.http.HtmlResponse(url=request.url, body=html.encode('utf-8'), encoding='utf-8',
                                            request=request)

使用下載器保存item內容

修改pipelines.py進行文件的存儲

import json

class AirHistoryPipeline(object):
    def open_spider(self, spider):
        self.file = open('area.json', 'w')

    def process_item(self, item, spider):
        context = json.dumps(dict(item),ensure_ascii=False) + '\n'
        self.file.write(context)
        return item

    def close_spider(self,spider):
        self.file.close()

修改settings文件使中間件,下載器生效

打開settings.py文件

修改以下內容:DOWNLOADER_MIDDLEWARES使剛纔寫的middlewares中間件中的類,ITEM_PIPELINES是pipelines中的類

BOT_NAME = 'air_history'
SPIDER_MODULES = ['air_history.spiders']
NEWSPIDER_MODULE = 'air_history.spiders'

USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'

DOWNLOADER_MIDDLEWARES = {
   'air_history.middlewares.AreaSpiderMiddleware': 543,
}

ITEM_PIPELINES = {
   'air_history.pipelines.AirHistoryPipeline': 300,
}

運行

使用scrapy crawl area_spider就可以運行爬蟲

spider全部代碼

# -*- coding: utf-8 -*-
import scrapy
from air_history.items import AirHistoryItem

class AreaSpiderSpider(scrapy.Spider):
    name = 'area_spider'
    allowed_domains = ['aqistudy.cn']  # 爬取的域名,不會超出這個頂級域名
    base_url = "https://www.aqistudy.cn/historydata/"
    start_urls = [base_url]

    def parse(self, response):
        print('爬取城市信息....')
        url_list = response.xpath("//div[@class='all']/div[@class='bottom']/ul/div[2]/li/a/@href").extract()  # 全部鏈接
        city_list = response.xpath("//div[@class='all']/div[@class='bottom']/ul/div[2]/li/a/text()").extract()  # 城市名稱
        for url, city in zip(url_list, city_list):
            url = self.base_url + url
            yield scrapy.Request(url=url, callback=self.parse_month, meta={'city': city})

    def parse_month(self, response):
        print('爬取{}月份...'.format(response.meta['city']))
        url_list = response.xpath('//tbody/tr/td/a/@href').extract()
        for url in url_list:
            url = self.base_url + url
            yield scrapy.Request(url=url, callback=self.parse_day, meta={'city': response.meta['city']})

    def parse_day(self, response):
        print('爬取最終數據...')
        item = AirHistoryItem()
        node_list = response.xpath('//tr')
        node_list.pop(0)  # 去除第一行標題欄
        for node in node_list:
            item['data'] = node.xpath('./td[1]/text()').extract_first()
            item['city'] = response.meta['city']
            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'] = node.xpath('./td[9]/text()').extract_first()
            yield item

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章