Java爬取中國天氣網實況天氣資料
因實驗室需求,需要找一個實況天氣API。
百度雲、阿里雲、騰訊雲上邊我都去找了,很多平臺要麼沒有,要麼要收費(免費的可呼叫次數太少了)。而我在高德開放平臺上找到了一個,但是不符合要求,被老師pass掉了。
百度搜一下,基本上都是用Python自動化測試Selenium寫的,那也太沒意思了吧。找不到,那我只好自己寫一個爬蟲去爬取了。
分析
如果想在中國天氣網上爬取實況天氣還是很簡單的,但是由於思路一直受限制,所以道路很曲折。
原來是考慮著爬取這個圖,每天爬一次就夠了。
我費勁腦汁,我還想用x、y的比值來計算出每個點所在的位置然後得出每個點對應的資料。但是經過幾次測試之後,誤差較大,所以這個方案就放棄了。
然後考慮追溯每個點資料的來源,但是這個網站的js檔案用了混淆加密,變數全是a、b、c、dxxxx看不出來意思的英文字母。後來又想想。網頁上的資料無非來自兩個地方:
- 靜態的(在原始碼中可以直接找到的)
- 非同步請求(在開發者工具欄中network中可以查詢)
- 其他的都是玄學
那麼根據這個網站,不是靜態的。但是network中有那麼多的請求,不好找?。
我們就在網站原始碼中找js
檔案,看看哪個js
檔案中有請求。看到一個請求像是在請求資料(一般資料都放在一個伺服器上),所以我們根據該請求的Domain
,縮小network中的我們尋找的部分。
在Domain是d1.weather.com.cn
成功了一半,但是請求連結後面加的那一串數字是什麼?
http://d1.weather.com.cn/sk_2d/101180101.html?_=1546572266854
越看越像時間戳,然後再eclipse
中測試一下,果然是時間戳。
那麼問題就簡單了。直接寫程式碼就行了。
總結
遇到爬取任務時,要先分析:
找資料
- 看資料是否就在網站原始碼中
- 看資料是否是非同步載入的
- 若network中請求不多,直接遍歷檢視請求即可。
- 反之,通過請求連結尋找
Domain
,然後縮小範圍尋找請求。
- 如果上兩個都不容易,那就在開發者工具中
Break on subtree modifications
除錯js檔案- 如果js檔案使用了混淆加密,你不想掉頭髮的話,就直接找非同步請求吧。
檢查資料
要知道很多網站都有反爬蟲機制,也就是說 你獲取的資料中可能被"投了毒"。好好檢查檢查資料是否正確。
寫程式碼爬取資料
要注意以下幾部分
- 請求頭偽裝的像瀏覽器一點
- 讓爬蟲隨機獲取
user-agent
- 設定
referer
(這個還是比較有用的)
- 讓爬蟲隨機獲取
- 請求之間有一些延遲最好
- 如果需要的話,就上下邊的終極武器。
- 構造
cookies
池 - 構造
IP
池
- 構造
Java程式碼
我用了quartz
定時任務框架來實現定時爬取。
public void execute(JobExecutionContext arg0) {
String[] city = new String[124];
// 讀取城市ID
int i = 0;
String str = "";
try {
URL resource = this.getClass().getResource("/cityId.txt");
String path = resource.getPath();// 獲取檔案的絕對路徑
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(path)));
while ((str = br.readLine()) != null) {
city[i] = str;
i++;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setSocketTimeout(60000).setConnectTimeout(60000)
.setConnectionRequestTimeout(60000)
.setStaleConnectionCheckEnabled(true).build();
// 建立httpClient物件
CloseableHttpClient h = HttpClients.custom()
.setDefaultRequestConfig(defaultRequestConfig).build();
// 建立並設定URI
URIBuilder uri = null;
// 建立Get請求
HttpGet hg = null;
String url = "";
// 建立響應物件
CloseableHttpResponse response = null;
InputStream inputstream = null;
for (int j = 0; j < city.length; j++) {
try {
url = "http://d1.weather.com.cn/sk_2d/" + city[j] + ".html?_="
+ System.currentTimeMillis();
uri = new URIBuilder(url);
hg = new HttpGet(uri.build());
} catch (Exception e2) {
e2.printStackTrace();
}
// 設定請求頭
hg.setHeader("Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
hg.setHeader("Accept-Encoding", "gzip, deflate");
hg.setHeader("Accept-Language",
"zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
hg.setHeader("Cache-Control", "no-cache");
hg.setHeader("Connection", "keep-alive");
hg.setHeader("Host", "d1.weather.com.cn");
hg.setHeader("Upgrade-Insecure-Requests", "1");
hg.setHeader("Cookie",
"f_city=%E9%83%91%E5%B7%9E%7C101180101%7C; Hm_"
+ "lvt_080dabacb001ad3dc8b9b9049b36d"
+ "43b=1546482322; Hm_lpvt_080dabacb001a");
hg.setHeader(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36");
hg.setHeader("Referer",
"http://www.weather.com.cn/weather1dn/101180101.shtml");
// 傳送請求
HttpEntity entity = null;
String line = "";
String Sline = "";
try {
response = h.execute(hg);
// 獲取請求結果
entity = response.getEntity();
inputstream = entity.getContent();
BufferedReader bufferedreader = new BufferedReader(
new InputStreamReader(inputstream, "UTF-8"));
while ((line = bufferedreader.readLine()) != null) {
Sline += line + "\n";
}
Sline = Sline.substring(Sline.indexOf('{'));
JSONObject jsonObject = JSONObject.fromObject(Sline);
show(jsonObject);
} catch (ClientProtocolException e1) {
// TODO Auto-generated catch block
System.out.println("請求超時等問題");
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
System.out.println("I/O問題");
e1.printStackTrace();
} finally {
try {
inputstream.close();
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("I/O、response流關閉");
e.printStackTrace();
}
}
}
}
/**
* 樣例對照表
*
* var dataSK = { "nameen":"zhengzhou", "cityname":"鄭州", "city":"101180101",
* "temp":"1", 攝氏度 "tempf":"33", 華氏度 "WD":"西南風", 風向 "wde":"SW", 風向英文
* "WS":"1級", 風力等級 "wse":"<12km/h", 風速 "SD":"52%", 溼度 "time":"11:25",
* "weather":"晴", "weathere":"Sunny", "weathercode":"d00", "qy":"1019", 氣壓
* "njd":"4.94km", 能見度 "sd":"52%", 溼度 "rain":"0.0", 降雨量 "rain24h":"0",
* "aqi":"214", "limitnumber":"", "aqi_pm25":"214", pm2.5
* "date":"01月03日(星期四)" }
*/
public void show(JSONObject jsonObject) {
// 獲取城市編號
String cityId = jsonObject.getString("city");
System.out.println(cityId);
String cityName = jsonObject.getString("cityname");
System.out.println(cityName);
// 獲取當前氣溫
String temperature = jsonObject.getString("temp");
System.out.println("當前氣溫" + ":" + temperature);
// 獲取當前風向
String windDirection = jsonObject.getString("WD");
System.out.println("風向:" + windDirection);
// 獲取當前風向
String windDirectionEn = jsonObject.getString("wde");
System.out.println("風向符號:" + windDirectionEn);
// 獲取當前風速等級
String windPower = jsonObject.getString("WS");
System.out.println("風力:" + windPower);
// 獲取當前風速
String windSpeed = jsonObject.getString("wse");
System.out.println("風力:" + windSpeed);
// 獲取當前溼度
String humidity = jsonObject.getString("SD");
System.out.println("溼度:" + humidity);
String time = jsonObject.getString("time");
System.out.println("時間:" + time);
String weather = jsonObject.getString("weather");
System.out.println("天氣中文:" + weather);
String weatherEn = jsonObject.getString("weathere");
System.out.println("天氣英文:" + weatherEn);
String weatherCode = jsonObject.getString("weathercode");
System.out.println("天氣代號:" + weatherCode);
String airPressure = jsonObject.getString("qy");
System.out.println("氣壓:" + airPressure);
String visibility = jsonObject.getString("njd");
System.out.println("能見度:" + visibility);
String rain = jsonObject.getString("rain");
System.out.println("降雨量:" + rain);
String rain24h = jsonObject.getString("rain24h");
System.out.println("日降雨量" + rain24h);
String aqi_pm25 = jsonObject.getString("aqi");
System.out.println("PM2.5:" + aqi_pm25);
String date = jsonObject.getString("date");
System.out.println("時間" + date);
Connection con = JButil.getConnection();
String sql = "INSERT INTO weather_sk "
+ "(cityId, cityName, lastUpdate, temperature, windSpeed, windPower,"
+ " windDirectionEn, windDirection, humidity, weather, weatherEn, weatherCode,"
+ " visibility, airPressure, rain24h, rain, aqi_pm25, time, date) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
QueryRunner qr = new QueryRunner();
int m = 0;
try {
m = qr.update(con, sql, cityId, cityName,
Timestamp.valueOf(new Date().toLocaleString()),
temperature, windSpeed, windPower, windDirectionEn,
windDirection, humidity, weather, weatherEn, weatherCode,
visibility, airPressure, rain24h, rain, aqi_pm25, time,
date);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
DbUtils.closeQuietly(con);
}
}