最近工作中需要用到全國區劃代碼,感覺國家統計局提供的數據比較權威,而且也算比較新(截止到2014年10月31日),所以打算把這些數據抓下來。
這是國家統計局提供的查詢頁面:http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2014/index.html
首先分析下頁面
<table class='provincetable' width=775> <tr> <td colspan=8 height=1 style='FONT-SIZE: 5px'> </td> </tr> <tr class='provincehead'> <td colspan=8 align='center' style='FONT-SIZE: 16px' height=39 vAlign='center' background='images/tiao.jpg'> <strong>2014年統計用區劃代碼和城鄉劃分代碼(截止2014年10月31日)</strong></td> </tr> <tr> <td colspan=8 height=50 style='FONT-SIZE: 12px'> 統計用區劃代碼和城鄉劃分代碼說明:統計用區劃代碼和城鄉劃分代碼所涉及的數據,是國家統計局開展統計調查所涉及的區劃范圍,未包括我國台灣省、香港特別行政區、澳門特別行政區。 <br> </td> </tr> <tr class='provincetr'> <td><a href='11.html'>北京市<br/></a></td> <td><a href='12.html'>天津市<br/></a></td> <td><a href='13.html'>河北省<br/></a></td> <td><a href='14.html'>山西省<br/></a></td> <td><a href='15.html'>內蒙古自治區<br/></a></td> <td><a href='21.html'>遼寧省<br/></a></td> <td><a href='22.html'>吉林省<br/></a></td> <td><a href='23.html'>黑龍江省<br/></a></td> </tr>
。。。 </table>
頁面比較干淨,用jsoup解析沒有什麼困難,所有的地區信息都是放在一個table中的,但是會有幾點不同,第一,一級地區信息是從第四個tr開始的,其它等級的地區信息是從第二個tr開始的;第二,每一級地區信息的tr的class不一致;第三,最後一級地區以及市轄區td中的子元素不一致。
由於大部分地區信息的樣式都是相同,所以可以有一個大致的思路,可以將全國的地區信息看做成一棵樹,比如我們可以將中國看做是這棵樹的根節點,然後遍歷這棵樹,將節點依次插入。
所以節點要定義name也就是地區名,id地區代碼,childNodes該地區的下一級地區節點,還有一個就是url,我們利用url訪問頁面。
有了這個思路我們便可以進行遍歷了。
PageFetcher根據url抓取得到頁面,ContentParser頁面解析器解析頁面。
要將節點插入樹中,我們首先要構造一個帶有子節點的根節點,然後遞歸遍歷所有節點。
由於從頁面中取到的鏈接是一個相對路徑,所以我們需要將該鏈接拼接成一個完整的url,並通過遞歸傳遞,當該節點的url為空並且該節點代碼的第7位到第10位的值為000時,說明該節點為葉子節點,就會跳出循環不再往下遞歸。
public void loadRegionNode(RegionNode regionNode, String superUrl) { String url = subUrl(superUrl) + regionNode.getUrl(); FetchedPage fetchedPage = pageFetcher.getContentFromUrl(url); List<RegionNode> list; //首頁數據已經預先加載 if (regionNode.getChildNode() == null) { list = contentParser.parseHTML(fetchedPage); } else { list = regionNode.getChildNode(); } //當list為null時則可能遍歷到葉子節點 if (list != null && list.size() > 0) { //將子節點插入 regionNode.setChildNode(list); for (RegionNode node : list) { //該節點為葉子節點且為最後一級 if(node.getUrl() == null && !node.getId().substring(6,12).equals("000000") ){ break; } loadRegionNode(node, url); } } }
根據各級地區頁面樣式的不同,可以利用jsoup定義一個頁面解析器,不同等級的地區使用不同的解析策略,所以可以在ContentParser中進行判斷,分別解析
public static List<RegionNode> parseHTML(FetchedPage fetchedPage) { //當抓取的頁面為空或者返回狀態碼不是200返回空 if (fetchedPage == null || fetchedPage.getStatusCode() != 200) { return null; } List<RegionNode> regionNodeList = new ArrayList<RegionNode>(); Document doc = Jsoup.parse(fetchedPage.getContent()); //獲取當前頁面中的地區節點 Elements tableSet = doc.getElementsByTag("TABLE"); Element regionTable = tableSet.get(4); Elements trSet = regionTable.getElementsByTag("tr"); for (int i = 1; i < trSet.size(); i++) { Element tr = trSet.get(i); Elements elements = tr.getAllElements(); //當子元素數量等於3時,說明為沒有子節點的市轄區 if (elements.size() == 3) { Element codeTdTag = elements.get(1); Element nameTdTag = elements.get(2); String name = nameTdTag.text(); String code = codeTdTag.text(); RegionNode regionNode = new RegionNode(); regionNode.setRegionName(name); regionNode.setId(code); regionNodeList.add(regionNode); System.out.println("code:"+code+";name:"+name); continue; } //當子元素數量等於4時,說明為葉子節點 if (elements.size() == 4) { Element codeTdTag = elements.get(1); Element nameTdTag = elements.get(3); String name = nameTdTag.text(); String code = codeTdTag.text(); RegionNode regionNode = new RegionNode(); regionNode.setRegionName(name); regionNode.setId(code); regionNodeList.add(regionNode); System.out.println("code:"+code+";name:"+name); continue; } Element codeATag = elements.get(2); Element nameATag = elements.get(4); String href = codeATag.attr("href"); String code = codeATag.text(); String name = nameATag.text(); RegionNode regionNode = new RegionNode(); regionNode.setId(code); regionNode.setRegionName(name); regionNode.setUrl(href); regionNodeList.add(regionNode); System.out.println("code:"+code+";name:"+name+";href:"+href); } return regionNodeList; }
大概需要等待一個小時,一個完整的樹形結構的地區信息就抓取完成了,然後我們可以根據需要保存成json或者xml文件,或者直接保存到數據庫中。
源碼已經上傳到我的github:https://github.com/gordonFm/regionSpider,編譯即可運行,數據是以json格式保存的。