1.問題描述
最近在做一個pyqt登錄校園網的小項目,想在窗口的狀態欄加上當天的天氣情況,用爬蟲可以很好的解決我的問題。
*最新,發現www.ip.cn采用了js動態加載,所以代碼有所變化,但整體思路不變
2.解決思路
考慮到所處位置的不同,需要先獲取本地城市地址,然后作為中國天氣網的輸入,爬取指定城市的天氣信息。
a. 先通過https://www.ip.cn/爬取本地城市名稱
b. 再通過獲取本地城市名稱作為輸入
進入城市頁面獲取所需信息即可,看起來不難,不就是爬、爬嗎
3.思路實現
a 很容易實現,直接上代碼
def download_page(url):
"""
獲取網頁源代碼
"""
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0'}
html = requests.get(url, headers=headers)
# print(html.text)
return html.text
def get_city_name(html):
"""
對網頁內容進行解析並分析得到需要的數據
"""
selector = etree.HTML(html) # 將源碼轉換為能被XPath匹配的格式
_info = selector.xpath('//script')[1].text # 由於網頁采用動態加載,“所在的地理位置”跑到了<script>中
location = re.findall(r"<code>(.*?)</code>", _info)[1]
location = location.split(" ")[0]
if location in municipality:
city = location[:-1] # 直轄市的話不取'市',不然天氣結果會不准
else:
for i, char in enumerate(location):
if char == "區" or char == "省":
index = i + 1
break
city = location[index:-1] # 取'省'后面一直到'市'中間的城市名稱用作天氣搜索
return city
我的ip會返回‘合肥’,北京,上海這些直轄市和一些自治區需要特別處理一下才能獲得所需城市名,這里必須返回城市名稱,不然直接搜索會出現這樣,得不到任何天氣data,好吧,能給我天氣情況就行,要什么自行車:
b 的話需要2步走,首先獲取城市對於編碼
在輸入合肥點擊搜索之后,通過觀察網絡請求信息,瀏覽器發送了一個GET請求,服務器響應數據中包含城市碼,如下圖
def get_city_code(city='合肥'):
"""
輸入一個城市,返回在中國天氣網上城市所對應的code
"""
try:
parameter = urlparse.parse.urlencode({'cityname': city})
conn = http.client.HTTPConnection('toy1.weather.com.cn', 80, timeout=5)
conn.request('GET', '/search?'+parameter)
r = conn.getresponse()
data = r.read().decode()[1:-1]
json_data = json.loads(data)
code = json_data[0]['ref'].split('~')[0] # 返回城市編碼
return code
except Exception as e:
raise e
模擬發送請求即可,頭信息在這,這里&callback和&_參數可以不要,然后對響應做下處理,因為得到的JSON數據字符串,需要將字符串轉為JSON數據格式,這就能得到城市碼了。
終於到了最后一步,也是最煩,不對,是最難的一步,獲取城市天氣信息,按說操作思路和上面差不多,就是分析頁面數據然后找標簽就行了,,,
可是這個網站竟然是javascript動態加載的,獲取的頁面圖上所指的內容啥都沒,這要我怎么獲取天氣情況,對於只會爬取非動態網頁的小白,只能留下沒技術的眼淚。
就這樣,又去學了一點動態爬取網站的基礎知識,通過一個一個請求分析查看,終於找到了我所要的信息,其實只要看響應就行了,服務器js返回的值都在這能看到。
接着套路和上面一樣,發送一個GET請求等服務器返回數據我們拿來用就行了
。。。。。。。。
問題又出現了,這次又是403,還有有人已經走過這條路了,在GET請求加上headers就行了,不讓服務器發現這是爬蟲,而是認為這是瀏覽器在請求,結果發現只需要在請求頭加上Referer參數就行,看來服務器並沒有真正做到反扒。。。。
def get_city_weather(city_code):
"""
通過城市編碼找到天氣信息
"""
try:
url = 'http://www.weather.com.cn/weather1dn/' + city_code + '.shtml'
# 這里選擇的體驗新版,所以是1dn,舊版是1d
headers = { "Referer": url }
conn = http.client.HTTPConnection('d1.weather.com.cn', 80, timeout=5)
conn.request('GET', '/sk_2d/' + city_code + '.html', headers=headers)
r = conn.getresponse()
data = r.read().decode()[13:]
weather_info = json.loads(data)
return weather_info
except Exception as e:
raise e
好了,大功告成!
貼一下自己的代碼供有需要的人參考:
# -*- coding:utf-8 -*-
# 天氣腳本
import json
import re
import urllib as urlparse
import http.client
import requests
from lxml import etree
target_url = 'https://www.ip.cn/'
municipality = ['北京市','上海市','重慶市','天津市']
def download_page(url):
"""
獲取網頁源代碼
"""
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0'}
html = requests.get(url, headers=headers)
# print(html.text)
return html.text
def get_city_name(html):
"""
對網頁內容進行解析並分析得到需要的數據
"""
selector = etree.HTML(html) # 將源碼轉換為能被XPath匹配的格式
_info = selector.xpath('//script')[1].text # 由於網頁采用動態加載,“所在的地理位置”跑到了<script>中
location = re.findall(r"<code>(.*?)</code>", _info)[1] #使用正則表達式獲取地理位置信息,在<code></code>標簽中
location = location.split(" ")[0]
if location in municipality:
city = location[:-1] # 直轄市的話不取'市',不然天氣結果會不准
else:
for i, char in enumerate(location):
if char == "區" or char == "省":
index = i + 1
break
city = location[index:-1] # 取'省'后面一直到'市'中間的城市名稱用作天氣搜索
return city
def get_city_code(city='合肥'):
"""
輸入一個城市,返回在中國天氣網上城市所對應的code
"""
try:
parameter = urlparse.parse.urlencode({'cityname': city})
conn = http.client.HTTPConnection('toy1.weather.com.cn', 80, timeout=5)
conn.request('GET', '/search?' + parameter)
r = conn.getresponse()
data = r.read().decode()[1:-1]
json_data = json.loads(data)
code = json_data[0]['ref'].split('~')[0] # 返回城市編碼
return code
except Exception as e:
raise e
def get_city_weather(city_code):
"""
通過城市編碼找到天氣信息
"""
try:
url = 'http://www.weather.com.cn/weather1dn/' + city_code + '.shtml'
headers = { "Referer": url }
conn = http.client.HTTPConnection('d1.weather.com.cn', 80, timeout=5)
conn.request('GET', '/sk_2d/' + city_code + '.html', headers=headers)
r = conn.getresponse()
data = r.read().decode()[13:]
weather_info = json.loads(data)
return weather_info
except Exception as e:
raise e
if __name__ == '__main__':
city = get_city_name(download_page(target_url))
city_code = get_city_code(city)
city_wether = get_city_weather(city_code)
print(city_wether)
PS:第一次寫博客,markdown剛學還不是很熟,表達可能也有點欠缺。
每次都是從別人那填好的坑走過,這次要填自己的坑,給別人開路