1. 程式人生 > >Java爬取中國天氣網實況天氣資料

Java爬取中國天氣網實況天氣資料

因實驗室需求,需要找一個實況天氣API。

百度雲、阿里雲、騰訊雲上邊我都去找了,很多平臺要麼沒有,要麼要收費(免費的可呼叫次數太少了)。而我在高德開放平臺上找到了一個,但是不符合要求,被老師pass掉了。

百度搜一下,基本上都是用Python自動化測試Selenium寫的,那也太沒意思了吧。找不到,那我只好自己寫一個爬蟲去爬取了。

分析

如果想在中國天氣網上爬取實況天氣還是很簡單的,但是由於思路一直受限制,所以道路很曲折。

在這裡插入圖片描述

原來是考慮著爬取這個圖,每天爬一次就夠了。

我費勁腦汁,我還想用x、y的比值來計算出每個點所在的位置然後得出每個點對應的資料。但是經過幾次測試之後,誤差較大,所以這個方案就放棄了。

然後考慮追溯每個點資料的來源,但是這個網站的js檔案用了混淆加密,變數全是a、b、c、dxxxx看不出來意思的英文字母。後來又想想。網頁上的資料無非來自兩個地方:

  • 靜態的(在原始碼中可以直接找到的)
  • 非同步請求(在開發者工具欄中network中可以查詢)
  • 其他的都是玄學

那麼根據這個網站,不是靜態的。但是network中有那麼多的請求,不好找?。

我們就在網站原始碼中找js檔案,看看哪個js檔案中有請求。看到一個請求像是在請求資料(一般資料都放在一個伺服器上),所以我們根據該請求的Domain,縮小network中的我們尋找的部分。

img

在Domain是d1.weather.com.cn

中進行尋找,果然資料基本上都出於這個Domain。

1546572380110

成功了一半,但是請求連結後面加的那一串數字是什麼?

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);
		}

	}