最近工作中需要用到全国区划代码,感觉国家统计局提供的数据比较权威,而且也算比较新(截止到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格式保存的。