動態頁面和靜態頁面
比較常見的頁面形式:
動態頁面
靜態頁面
例如:
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