背景
抱着《python學習手冊》啃了很久,心里想着要動手寫點東西,但是一直拖延症到最近才真正開始准備。一開始不知道寫點啥好,就從生活中挖掘,發現自己每天查天氣預報查的挺頻繁的,那就爬一波天氣預報吧。
技術概覽
- selenium
- time
- re
- calendar
爬取網頁數據的過程中就用到以上4個模塊,其中calendar是可選的,最初的版本是只爬取當月的每日天氣所以不需要calendar,后續新增了可選月份的功能,才加上calendar。本次爬蟲是用類來實現的,但實際上用簡單函數即可,但抱着學以致用的想法還是用類吧,雖然類有很多很強大的功能(繼承、多態、定制等),但在我這幾十行的代碼里並不能體現出來。我的環境是win10+python27,因為工作需要所以個人PC也是python2的,后續會更新成python3。
具體內容
class Weather(object):
def __init__(self):
self.day = time.strftime('%d', time.localtime(time.time()))
self.month = time.strftime('%m', time.localtime(time.time()))
在類的開始定義了一個構造函數,里面有兩個屬性:day和month分別是今天的日期和當月月份,這里就用到time模塊中的time、localtime、strftime。詳細的介紹可以看菜鳥教程,我們現場實踐下。

這里先導入time模塊,然后time.time()是當前時間的一個時間戳,需要用localtime()格式化成time.struct_time類型的對象。(struct_time是在time模塊中定義的表示時間的對象)然后再用strftime()接收struct_time對象,返回一個字符串表示的時間值,如圖所示,我碼字的時候正是5月23號。
def get_weather_html(self):
#打開中國天氣網的40天天氣預報的網頁,爬取數據
url = 'http://www.weather.com.cn/weather40d/101020100.shtml'
driver = webdriver.Chrome()
driver.get(url)
driver.maximize_window()
driver.implicitly_wait(3)
month_input = raw_input('please input month:')
if month_input != '' and month_input != self.month:
self.select_month(month_input,driver)
如果想用selenium的話,要先下個驅動下載地址我用的是谷歌瀏覽器,所以對應的驅動是cromedriver.exe,注意驅動跟版本強相關,沒有下對版本的話就會報錯。驅動放在python.exe同目錄下即可。上面代碼主要做的事情就是:
- 創建瀏覽器的實例driver
- 打開中國天氣網
- 將瀏覽器最大化
- 等待頁面加載完成
- 等待用戶輸入期待爬蟲的月份
- 如果輸入的月份不是當前月份或者不是空字符串(不輸入月份直接回車就是空字符串),則調用select_month
這里要強調下implicitly_wait作為隱式等待可以在頁面元素找到的時間提前退出等待,但是這里設置的全局的等待時候,假設代碼里的路徑有錯或者其他什么問題,會導致調試時間變長。也可以使用time.sleep(3)這種顯式等待,設3s就是等待3s,不管元素找到與否。selenium-python里有很多selenium的詳細介紹,關於顯示等待和隱式等待的也有。
def select_month(self, month, driver):
driver.find_element_by_xpath('//*[@class="zong"]').click()
month_path = '//div[@class="w_nian"]/ul[2]/li['+month+']'
driver.find_element_by_xpath(month_path).click()
- 點擊打開網頁上選擇月份的彈框
- 創建用戶需要的月份的路徑
- 點擊該月份
xpath安裝詳見左邊鏈接,親測可用。這里需要用到xpath相關的知識,w3school、菜鳥教程上有很多很詳細的這里就不贅述。讓我們動手實踐下:

圖片文字顯示不全,1、打開開發者模式,用這個小箭頭點你要的元素。2、這里找到元素對應的位置。
for week_index in range(2,8):
for day_index in range(1,8):
week = str(week_index)
day = str(day_index)
day_path = '//*[@id="table"]/tbody/tr['+week+']/td['+day+']/h2/span[2]'
max_temp_path = '//*[@id="table"]/tbody/tr['+week+']/td['+day+']/div[1]/p/span[1]'
min_temp_path = '//*[@id="table"]/tbody/tr['+week+']/td['+day+']/div[1]/p/span[2]'
date = driver.find_element_by_xpath(day_path)
date = date.text
try:
date_int = int(date)
except(ValueError):
break
if month_input == '':
month = self.month
else:
month = month_input
if date_int == 01 and week_index != 2:
print u'19年{}月每日溫度情況如下'.format(month)
break
max_temp = driver.find_element_by_xpath(max_temp_path)
min_temp = driver.find_element_by_xpath(min_temp_path)
max_temp = re.findall('\d+', max_temp.text)
min_temp = re.findall('\d+', min_temp.text)
- 循環獲取數據
- 創建日期,最高溫,最低溫的xpath路徑
- 獲取路徑里的文本信息
- 路徑獲取不到正確的信息或者匹配到下個月的1號就跳出循環
- 用正則把溫度中的數字取出來
emm這里就是爬數據處理數據的主要邏輯了,因為天氣預報上40天的顯示是按日歷來的,跟周數,星期數相關,於是這里用了嵌套的循環,但是因為月份的不同,周數有變化,我這里就寫死range(2,8)(html數據從2開始到6或者7,range不包括8),有的月份沒有第6周會獲取不到元素的時候,強行int會報ValueError,等捕捉到這個錯就是數據已經全部獲取完可以break了。這里我用字典(key=value)存放日期和溫度的對應,因為字典的key是唯一的,所以即使會獲取到上個月30號的溫度,也會被覆蓋。這里還有個問題就是溫度顯示方式有多種,因此獲取溫度的xpath不是唯一的,於是我把這部分用try包起來,except捕捉到異常后用另一個xpath去獲取,直到取到數據。詳見下圖

if __name__ == '__main__':
weather = Weather()
dict,month = weather.get_weather_html()
count = 0
for key in dict.keys():
monthRange = calendar.monthrange(2019, int(month))
print u"{name:10d}號:{max}~{min}℃".format(name=key,max=dict[key][0],min=dict[key][1])
count += 1
if count == monthRange[-1]:
break

最后我們就可以創建類實例,運行方法,打印數據了。提醒下cmd下運行的話,中文字符前要加個u轉成Unicode,不然會報錯。下圖就是最后的運行結果啦。

其實獲取html的方法有很多種頁面獲取方法,我一開始用了urllib和request都失敗了,因為天氣預報的頁面是動態加載的,而這兩個庫只能幫助我們獲取靜態頁面,然后我就想試試自己模擬請求,實現動態加載,無奈才疏學淺,最后只能用萬能牌selenium。selenium會模擬用戶操作,真的在你電腦上打開瀏覽器然后點點點,這樣雖然萬能,但是就很慢吶。 以上就是我代碼的大部分了,源碼的話可以去我的github獲取。
