中國天氣網城市程式碼爬取
想試著收集一些氣象資料,記得之前氣象資料共享平臺可以下,結果發現需要實名認證還要提交證件照之類的,想了想,臉黑,估計認證不過,還是算了。於是又網上找了找,發現在美國noaa網站可以下載,包含中國大概700多個站點的資料,時間從50年代一直到最近,但是細看了幾個站點的資料後,發現有些站點資料只到2000年初或者更早。繼續搜尋後想到可以用爬蟲,從天氣網等網站爬取一些資料,考慮到權威性,選擇了中國天氣網。
目標網頁
目標網頁主要是各個城市的天氣頁面,主要是當前天氣,以及過去24小時天氣,每個城市的網址為:
www.weather.com.cn/weather1d/+{城市程式碼}.shtml
因此,首先需要找到各個城市的程式碼,從網上搜了一些後,發現這些可能時間較早,與現在網站上資料不能一一對應,所以考慮從網頁中重新爬取到城市程式碼,順便可以練習一下相關爬蟲技術。
網上搜到的爬蟲案例大多基於python編寫,但是個人最近剛看到一個用java爬取京東商品資訊及圖片的案例,因此想先用java試著爬取,個人感覺用python會顯得更簡潔,python程式碼可以很快的看到核心程式碼,而java,尤其是比較成熟的java程式碼,即使核心程式碼很短,卻經常被包含在各種異常處理,各種執行緒,各種配置檔案,各種分門別類裡。
話不多說,說城市程式碼的獲取,在研究每個城市天氣頁面的原始碼時,發現在一個select的div下,有對應城市的各個區縣的程式碼,比如在上海閔行的頁面裡,可以看到上海市的各個區縣(下圖)的城市程式碼及名稱。
再說城市程式碼的組成規則,前三位101代表中國,接下來的六位依次是省(自治區、直轄市、特區),地市以及區縣,分別各佔兩位,直轄市區縣最後兩位都是0,真不明白為什麼不直接用行政程式碼。
具體方法
用到的技術主要是傳送請求的httpClient以及解析網頁的Jsoup。
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version >${version}</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${version}</version>
</dependency>
因為不清楚具體有多少個地級市,最先想到的是迴圈101010100-101350000,遍歷所有頁面,但這樣迴圈次數過多,因此加了一層迴圈,內層迴圈連續出現無效頁面次數大於100時,跳出內層迴圈,外層迴圈加10000,也就是調到下一個省。需要注意的是,該網站無效頁面返回並不是404,而是空頁面,因此可以通過判斷html頁面字元長度來確認無效頁面。
for (long j = 101010100; j< 101360000; j+=10000) {
//標記無效頁面
int countInvalid = 0;
for(long i = j;i<101350000;i++) {
String url = "http://www.weather.com.cn/weather1d/" + i + ".shtml";
String html = getHtml(url);
StringBuilder sb = new StringBuilder();
if (html!=null && html.length()>20) {
//計數歸零
countInvalid=0;
File file = new File("D:\\temp\\" + i +".txt");
//解析Html
Document document = Jsoup.parse(html);
Elements lis = document.select("#select ul").select("li");
//System.out.println(lis.size());
for (Element li : lis) {
String cityCode = li.attr("tip");
String cityName = li.text();
sb.append(cityCode + "\t" + cityName + "\r\n");
}
FileUtils.writeStringToFile(file, sb.toString() , "UTF-8");
}else {
//連續100次出現無效頁面,退出內迴圈
countInvalid++;
if(countInvalid > 100) {
break;
}
}
}
}
用httpClient傳送頁面請求方法如下,用的是最基礎的方法:
public static String getHtml(String url) throws Exception {
// 建立Httpclient物件
CloseableHttpClient httpclient = HttpClients.createDefault();
// 建立http GET請求
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse response = null;
try {
// 執行請求
response = httpclient.execute(httpGet);
// 判斷返回狀態是否為200
if (response.getStatusLine().getStatusCode() == 200) {
String content = EntityUtils.toString(response.getEntity(), "UTF-8");
return content;
}
} finally {
if (response != null) {
response.close();
}
httpclient.close();
}
return null;
}
這樣就得到每個區縣級別對應的txt檔案,接下來就是處理重複的txt檔案,原本的想法是,每個地市級別(前7位程式碼)僅保留一個,最後將txt檔案合併成一個。在實際操作中發現,遍歷每個檔案的每一行資料,刪除掉重複項,寫入一個列表裡,最後將列表輸出到一個txt檔案,這樣會快很多。最終得到3231條記錄。
File[] files = new File("D:\\temp\\weatherCitycode").listFiles();
File outFile = new File("D:\\temp\\weatherCitycode.txt");
List<String> cityList = new ArrayList<String>();
for(int idx = 0; idx<files.length;idx++) {
File curFile = files[idx];
List<String> lines = FileUtils.readLines(curFile, "UTF-8");
for(String line:lines) {
if (!cityList.contains(line)){
cityList.add(line);
}
}
}
FileUtils.writeLines(outFile, cityList);
System.out.println(cityList.size());