基于scrapy爬虫的天气数据采集(python)
基于scrapy爬蟲的天氣數(shù)據(jù)采集(python)
一、實(shí)驗(yàn)介紹
1.1. 知識(shí)點(diǎn)
本節(jié)實(shí)驗(yàn)中將學(xué)習(xí)和實(shí)踐以下知識(shí)點(diǎn):
二、實(shí)驗(yàn)效果
三、項(xiàng)目實(shí)戰(zhàn)
3.1. 安裝Scrapy
安裝 scrapy-0.24:
# 安裝依賴的包 $ sudo apt-get update $ sudo apt-get install python-lxml python-dev libffi-dev# 更新系統(tǒng)默認(rèn)的 six 包 $ sudo pip install six --upgrade# 安裝指定版本的scrapy $ sudo pip install scrapy==0.24.4完成這步后,可以用下面的命令測試一下安裝是否正確:
$ scrapy version如果正常,效果如圖所示:
3.2. 創(chuàng)建項(xiàng)目
在開始爬取之前,必須創(chuàng)建一個(gè)新的Scrapy項(xiàng)目。進(jìn)入您打算存儲(chǔ)代碼的目錄中,運(yùn)行下列命令:
$ scrapy startproject weather如果正常,效果如圖所示:
這些文件分別是:
- scrapy.cfg: 項(xiàng)目的配置文件
- weather/: 該項(xiàng)目的python模塊。之后將在此加入代碼。
- weather/items.py: 項(xiàng)目中的item文件.
- weather/pipelines.py: 項(xiàng)目中的pipelines文件.
- weather/settings.py: 項(xiàng)目的設(shè)置文件.
- weather/spiders/: 放置spider代碼的目錄.
3.3. 定義Item
Item 是保存爬取到的數(shù)據(jù)的容器;其使用方法和python字典類似,并且提供了額外保護(hù)機(jī)制來避免拼寫錯(cuò)誤導(dǎo)致的未定義字段錯(cuò)誤。
首先根據(jù)需要從weather.sina.com.cn獲取到的數(shù)據(jù)對(duì)item進(jìn)行建模。 我們需要從weather.sina.com.cn中獲取當(dāng)前城市名,后續(xù)9天的日期,天氣描述和溫度等信息。對(duì)此,在item中定義相應(yīng)的字段。編輯 weather 目錄中的 items.py 文件:
# -*- coding: utf-8 -*-# Define here the models for your scraped items # # See documentation in: # http://doc.scrapy.org/en/latest/topics/items.html import scrapy class WeatherItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() # demo 1 city = scrapy.Field() date = scrapy.Field() dayDesc = scrapy.Field() dayTemp = scrapy.Field() pass3.4. 編寫獲取天氣數(shù)據(jù)的爬蟲(Spider)
Spider是用戶編寫用于從單個(gè)網(wǎng)站(或者一些網(wǎng)站)爬取數(shù)據(jù)的類。
其包含了一個(gè)用于下載的初始URL,如何跟進(jìn)網(wǎng)頁中的鏈接以及如何分析頁面中的內(nèi)容, 提取生成 item 的方法。
為了創(chuàng)建一個(gè)Spider,必須繼承 scrapy.Spider 類, 且定義以下三個(gè)屬性:
-
name: 用于區(qū)別Spider。該名字必須是唯一的,您不可以為不同的Spider設(shè)定相同的名字。
-
start_urls: 包含了Spider在啟動(dòng)時(shí)進(jìn)行爬取的url列表。因此,第一個(gè)被獲取到的頁面將是其中之一。后續(xù)的URL則從初始的URL獲取到的數(shù)據(jù)中提取。
-
parse() 是spider的一個(gè)方法。 被調(diào)用時(shí),每個(gè)初始URL完成下載后生成的 Response 對(duì)象將會(huì)作為唯一的參數(shù)傳遞給該函數(shù)。 該方法負(fù)責(zé)解析返回的數(shù)據(jù)(response data),提取數(shù)據(jù)(生成item)以及生成需要進(jìn)一步處理的URL的 Request 對(duì)象。
我們通過瀏覽器的查看源碼工具先來分析一下需要獲取的數(shù)據(jù)網(wǎng)源代碼:
<h4 class="slider_ct_name" id="slider_ct_name">武漢</h4> ... <div class="blk_fc_c0_scroll" id="blk_fc_c0_scroll" style="width: 1700px;"> <div class="blk_fc_c0_i"> <p class="wt_fc_c0_i_date">01-28</p> <p class="wt_fc_c0_i_day wt_fc_c0_i_today">今天</p> <p class="wt_fc_c0_i_icons clearfix"> <img class="icons0_wt png24" src="http://www.sinaimg.cn/dy/weather/main/index14/007/icons_42_yl/w_04_27_00.png" alt="雨夾雪" title="雨夾雪"> <img class="icons0_wt png24" src="http://www.sinaimg.cn/dy/weather/main/index14/007/icons_42_yl/w_04_29_01.png" alt="中雪" title="中雪"> </p> <p class="wt_fc_c0_i_times"> <span class="wt_fc_c0_i_time">白天</span> <span class="wt_fc_c0_i_time">夜間</span> </p> <p class="wt_fc_c0_i_temp">1°C / -2°C</p> <p class="wt_fc_c0_i_tip">北風(fēng) 3~4級(jí)</p> <p class="wt_fc_c0_i_tip">無持續(xù)風(fēng)向 小于3級(jí)</p> </div> <div class="blk_fc_c0_i"> <p class="wt_fc_c0_i_date">01-29</p> <p class="wt_fc_c0_i_day ">星期四</p> <p class="wt_fc_c0_i_icons clearfix"> <img class="icons0_wt png24" src="http://www.sinaimg.cn/dy/weather/main/index14/007/icons_42_yl/w_04_29_00.png" alt="中雪" title="中雪"> <img class="icons0_wt png24" src="http://www.sinaimg.cn/dy/weather/main/index14/007/icons_42_yl/w_07_25_01.png" alt="陰" title="陰"> </p> <p class="wt_fc_c0_i_times"> <span class="wt_fc_c0_i_time">白天</span> <span class="wt_fc_c0_i_time">夜間</span> </p> <p class="wt_fc_c0_i_temp">1°C / -2°C</p> <p class="wt_fc_c0_i_tip">無持續(xù)風(fēng)向 小于3級(jí)</p> </div> ... </div>我們可以看到:
- 城市名可以通過獲取id為slider_ct_name的h4元素獲取
- 日期可以通過獲取id為blk_fc_c0_scroll下的class為wt_fc_c0_i_date的p元素獲取
- 天氣描述可以通過獲取id為blk_fc_c0_scroll下的class為icons0_wt的img元素獲取
- 溫度可以通過獲取id為blk_fc_c0_scroll下的class為wt_fc_c0_i_temp的p元素獲取
因此,我們的Spider代碼如下,保存在 weather/spiders 目錄下的 localweather.py 文件中:
# -*- coding: utf-8 -*- import scrapy from weather.items import WeatherItemclass WeatherSpider(scrapy.Spider): name = "myweather" allowed_domains = ["sina.com.cn"] start_urls = ['http://weather.sina.com.cn'] def parse(self, response): item = WeatherItem() #把WeatheItem()實(shí)例化成item對(duì)象 item['city'] = response.xpath('//*[@id="slider_ct_name"]/text()').extract()#//*:選取文檔中的所有元素。@:選擇屬性 /:從節(jié)點(diǎn)選取 。extract():提取 tenDay = response.xpath('//*[@id="blk_fc_c0_scroll"]'); item['date'] = tenDay.css('p.wt_fc_c0_i_date::text').extract() item['dayDesc'] = tenDay.css('img.icons0_wt::attr(title)').extract() item['dayTemp'] = tenDay.css('p.wt_fc_c0_i_temp::text').extract() return item代碼中的xpath和css后面括號(hào)的內(nèi)容為選擇器,關(guān)于xpath和css選擇器的內(nèi)容可參考官方教程:http://doc.scrapy.org/en/0.24/topics/selectors.html
3.5. 運(yùn)行爬蟲,對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證
到這里為止,我們需要驗(yàn)證一下爬蟲是否能正常工作(即能否取到我們想要的數(shù)據(jù)),驗(yàn)證的方法就是在命令行(重要:在項(xiàng)目的scrapy.cfg文件同級(jí)目錄運(yùn)行命令,下同)中運(yùn)行下面的代碼:
$ scrapy crawl myweather -o wea.json這行命令的意思是,運(yùn)行名字為 myweather 的爬蟲(我們在上一步中定義的),然后把結(jié)果以json格式保存在wea.json文件中。命令運(yùn)行結(jié)果如下:
然后,我們查看當(dāng)前目錄下的wea.json文件,正常情況下效果如下:
我們看到,wea.json中已經(jīng)有數(shù)據(jù)了,只是數(shù)據(jù)是以u(píng)nicode方式編碼的。
3.6. 保存爬取到的數(shù)據(jù)
上面只是把數(shù)據(jù)保存在json文件中了,如果我們想自己保存在文件或數(shù)據(jù)庫中,如何操作呢?
這里就要用到 Item Pipeline 了,那么 Item Pipeline 是什么呢?
當(dāng)Item在Spider中被收集之后,它將會(huì)被傳遞到Item Pipeline中,一些組件會(huì)按照一定的順序執(zhí)行對(duì)Item的處理。
每個(gè)item pipeline組件(有時(shí)稱之為“Item Pipeline”)是實(shí)現(xiàn)了簡單方法的Python類。他們接收到Item并通過它執(zhí)行一些行為,同時(shí)也決定此Item是否繼續(xù)通過pipeline,或是被丟棄而不再進(jìn)行處理。
item pipeline的典型應(yīng)用有:
- 清理HTML數(shù)據(jù)
- 驗(yàn)證爬取的數(shù)據(jù)(檢查item包含某些字段)
- 查重(并丟棄)
- 將爬取結(jié)果保存到文件或數(shù)據(jù)庫中
每個(gè)item pipeline組件都需要調(diào)用 process_item 方法,這個(gè)方法必須返回一個(gè) Item (或任何繼承類)對(duì)象, 或是拋出 DropItem異常,被丟棄的item將不會(huì)被之后的pipeline組件所處理。
我們這里把數(shù)據(jù)轉(zhuǎn)碼后保存在 wea.txt 文本中。
pipelines.py文件在創(chuàng)建項(xiàng)目時(shí)已經(jīng)自動(dòng)被創(chuàng)建好了,我們在其中加上保存到文件的代碼:
# -*- coding: utf-8 -*-# Define your item pipelines here # # Don't forget to add your pipeline to the ITEM_PIPELINES setting # See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html class WeatherPipeline(object): def __init__(self): pass def process_item(self, item, spider): with open('wea.txt', 'w+') as file: city = item['city'][0].encode('utf-8') file.write('city:' + str(city) + '\n\n') date = item['date'] desc = item['dayDesc'] dayDesc = desc[1::2] nightDesc = desc[0::2] dayTemp = item['dayTemp'] weaitem = zip(date, dayDesc, nightDesc, dayTemp) for i in range(len(weaitem)): item = weaitem[i] d = item[0] dd = item[1] nd = item[2] ta = item[3].split('/') dt = ta[0] nt = ta[1] txt = 'date:{0}\t\tday:{1}({2})\t\tnight:{3}({4})\n\n'.format( d, dd.encode('utf-8'), dt.encode('utf-8'), nd.encode('utf-8'), nt.encode('utf-8') ) file.write(txt) return item代碼比較簡單,都是python比較基礎(chǔ)的語法,如果您感覺比較吃力,建議先去學(xué)一下python基礎(chǔ)課。
3.7. 把 ITEM_PIPELINES 添加到設(shè)置中
寫好ITEM_PIPELINES后,還有很重要的一步,就是把 ITEM_PIPELINES 添加到設(shè)置文件 settings.py 中。
ITEM_PIPELINES = {'weather.pipelines.WeatherPipeline': 1 }另外,有些網(wǎng)站對(duì)網(wǎng)絡(luò)爬蟲進(jìn)行了阻止(注:本項(xiàng)目僅從技術(shù)角度處理此問題,個(gè)人強(qiáng)烈不建議您用爬蟲爬取有版權(quán)信息的數(shù)據(jù)),我們可以在設(shè)置中修改一下爬蟲的 USER_AGENT 和 Referer 信息,增加爬蟲請(qǐng)求的時(shí)間間隔。
整個(gè) settings.py 文件內(nèi)容如下:
# -*- coding: utf-8 -*-# Scrapy settings for weather project # # For simplicity, this file contains only the most important settings by # default. All the other settings are documented here: # # http://doc.scrapy.org/en/latest/topics/settings.html # BOT_NAME = 'Googlebot' SPIDER_MODULES = ['weather.spiders'] NEWSPIDER_MODULE = 'weather.spiders' # Crawl responsibly by identifying yourself (and your website) on the user-agent #USER_AGENT = 'weather (+http://www.yourdomain.com)' USER_AGENT = 'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36' DEFAULT_REQUEST_HEADERS = { 'Referer': 'http://www.weibo.com' } ITEM_PIPELINES = { 'weather.pipelines.WeatherPipeline': 1 } DOWNLOAD_DELAY = 0.5到現(xiàn)在為止,代碼主要部分已經(jīng)寫完了。
3.8. 運(yùn)行爬蟲
在項(xiàng)目的scrapy.cfg同級(jí)目錄下用下面的命令運(yùn)行爬蟲:
$ scrapy crawl myweather正常情況下,效果如下:
然后,在當(dāng)前目錄下會(huì)多一個(gè) wea.txt 文件,內(nèi)容如下:
到此我們基于scrapy的天氣數(shù)據(jù)采集就完成了。
四、FAQ
4.1. 結(jié)果只出現(xiàn)城市?
scrapy內(nèi)置的html解析是基于lxml庫的,這個(gè)庫對(duì)html的解析的容錯(cuò)性不是很好,通過檢查虛擬機(jī)中獲取到的網(wǎng)頁源碼,發(fā)現(xiàn)有部分標(biāo)簽是不匹配的(地區(qū)和瀏覽器不同取到的源碼可能不同),檢查結(jié)果如圖:
所以導(dǎo)致在spider中取到的日期數(shù)據(jù)(item['date'])為空,然后在pilepine代碼中做zip操作后,整個(gè) weaitem 為空,所以最終只有城市數(shù)據(jù)了。
既然如此,我們換個(gè)html代碼解析器就可以了,這里建議用 BeautifulSoup (官網(wǎng):http://www.crummy.com/software/BeautifulSoup/bs4/doc/index.html?),這個(gè)解析器有比較好的容錯(cuò)能力,具體用法可以參考上面的文檔。
BeautifulSoup安裝:
#下載BeautifulSoup $ wget http://labfile.oss.aliyuncs.com/beautifulsoup4-4.3.2.tar.gz #解壓 $ tar -zxvf beautifulsoup4-4.3.2.tar.gz #安裝 $ cd beautifulsoup4-4.3.2 $ sudo python setup.py install安裝成功后,優(yōu)化WeatherSpider代碼,改進(jìn)后的代碼如下:
# -*- coding: utf-8 -*- import scrapy from bs4 import BeautifulSoup from weather.items import WeatherItem class WeatherSpider(scrapy.Spider): name = "myweather" allowed_domains = ["sina.com.cn"] start_urls = ['http://weather.sina.com.cn'] def parse(self, response): html_doc = response.body #html_doc = html_doc.decode('utf-8') soup = BeautifulSoup(html_doc) itemTemp = {} itemTemp['city'] = soup.find(id='slider_ct_name') tenDay = soup.find(id='blk_fc_c0_scroll') itemTemp['date'] = tenDay.findAll("p", {"class": 'wt_fc_c0_i_date'}) itemTemp['dayDesc'] = tenDay.findAll("img", {"class": 'icons0_wt'}) itemTemp['dayTemp'] = tenDay.findAll('p', {"class": 'wt_fc_c0_i_temp'}) item = WeatherItem() for att in itemTemp: item[att] = [] if att == 'city': item[att] = itemTemp.get(att).text continue for obj in itemTemp.get(att): if att == 'dayDesc': item[att].append(obj['title']) else: item[att].append(obj.text) return item然后再次運(yùn)行爬蟲:
$ scrapy crawl myweather然后查看 wea.txt,數(shù)據(jù)如下:
4.2. 只取到了9天的數(shù)據(jù)?
如果是晚上運(yùn)行爬蟲,當(dāng)天的白天天氣是沒有的(已經(jīng)過去了),針對(duì)這部分建議自己優(yōu)化。
五、實(shí)驗(yàn)代碼
本實(shí)驗(yàn)的代碼可以通過下面這個(gè)鏈接獲取:
http://git.shiyanlou.com/shiyanlou/scrapy-weather總結(jié)
以上是生活随笔為你收集整理的基于scrapy爬虫的天气数据采集(python)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PHP学习笔记1
- 下一篇: [LeetCode] Find Larg