1. 程式人生 > >中國天氣網城市程式碼爬取

中國天氣網城市程式碼爬取

  想試著收集一些氣象資料,記得之前氣象資料共享平臺可以下,結果發現需要實名認證還要提交證件照之類的,想了想,臉黑,估計認證不過,還是算了。於是又網上找了找,發現在美國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());