java实现网页爬虫

Posted Android Graphics

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java实现网页爬虫相关的知识,希望对你有一定的参考价值。

接着上面一篇对爬虫需要的java知识,这一篇目的就是在于网络爬虫的实现,对数据的获取,以便分析。

----->

 

目录:  

1、爬虫原理

2、本地文件数据提取及分析

3、单网页数据的读取

4、运用正则表达式完成超连接的连接匹配和提取

5、广度优先遍历,多网页的数据爬取

6、多线程的网页爬取

7、总结

爬虫实现原理

网络爬虫基本技术处理

网络爬虫是数据采集的一种方法,实际项目开发中,通过爬虫做数据采集一般只有以下几种情况:

1) 搜索引擎

2) 竞品调研

3) 舆情监控

4) 市场分析

网络爬虫的整体执行流程:

1) 确定一个(多个)种子网页

2) 进行数据的内容提取

3) 将网页中的关联网页连接提取出来

4) 将尚未爬取的关联网页内容放到一个队列中

5) 从队列中取出一个待爬取的页面,判断之前是否爬过。

6) 把没有爬过的进行爬取,并进行之前的重复操作。

7) 直到队列中没有新的内容,爬虫执行结束。

 

这样完成爬虫时,会有一些概念必须知道的:

1) 深度(depth):一般来说,表示从种子页到当前页的打开连接数,一般建议不要超过5层。

2) 广度(宽度)优先和深度优先:表示爬取时的优先级。建议使用广度优先,按深度的层级来顺序爬取。

 

Ⅰ  在进行网页爬虫前,我们先针对一个飞机事故失事的文档进行数据提取的练习,主要是温习一下上一篇的java知识,也是为了下面爬虫实现作一个热身准备。

 首先分析这个文档,,关于美国历来每次飞机失事的数据,包含时间地点、驾驶员、死亡人数、总人数、事件描述,一共有12列,第一列是标题,下面一共有5268条数据。

 现在我要对这个文件进行数据提取,并实现一下分析:  

根据飞机事故的数据文档来进行简单数据统计。

1) 哪年出事故次数最多

2) 哪个时间段(上午 8  12,下午 12  18,晚上 18  24,凌晨 0  8 )事故出现次数最多。

3) 哪年死亡人数最多

4)哪条数据的幸存率最高。

 

代码实现:(一切知识从源码获取!)

  1 package com.plane;
  2 
  3 import java.io.*;
  4 import java.text.ParseException;
  5 import java.text.SimpleDateFormat;
  6 import java.util.*;
  7 /**
  8  * 飞机事故统计
  9  * @author k04
 10  *sunwengang    
 11  *2017-08-11
 12  */
 13 public class planeaccident {
 14         //数据获取存取链表
 15         private static List<String>  alldata=new ArrayList<>();
 16         
 17         public static void main(String args[]){            
 18             getData("飞行事故数据统计_Since_1908.csv");
 19             alldata.remove(0);
 20             //System.out.println(alldata.size());
 21             //死亡人数最多的年份
 22             MaxDeadYear();
 23             //事故发生次数最多的年份
 24             MaxAccidentsYear();
 25             //事故各个时间段发生的次数
 26             FrequencyPeriod();
 27             //幸村率最高的一条数据
 28              MaximumSurvival();        
 29         }
 30         
 31         /**
 32          * 从源文件爬取数据
 33          * getData(String filepath)
 34          * @param filepath
 35          */
 36         public static void getData(String filepath){
 37             File f=new File(filepath);
 38             //行读取数据
 39             try{
 40                 BufferedReader br=new BufferedReader(new FileReader(f));
 41                 String line=null;
 42                 while((line=(br.readLine()))!=null){
 43                     alldata.add(line);
 44                 }
 45                 br.close();
 46             }catch(Exception e){
 47                 e.printStackTrace();
 48             }
 49         }
 50         /**
 51          * 记录每年对应的死亡人数
 52          * @throws  
 53          * 并输出死亡人数最多的年份,及该年死亡人数
 54          */
 55         public static void MaxDeadYear(){
 56             //记录年份对应死亡人数
 57             Map<Integer,Integer> map=new HashMap<>();
 58             //时间用date显示
 59             SimpleDateFormat sdf=new SimpleDateFormat("MM/dd/YYYY");
 60             //循环所有数据
 61             for(String data:alldata){
 62                 //用逗号将数据分离,第一个是年份,第11个是死亡人数
 63                 String[] strs=data.split(",");
 64                 if(strs[0]!=null){
 65                     //获取年份
 66                     try {
 67                         Date date=sdf.parse(strs[0]);
 68                         int year=date.getYear();
 69                         //判断map中是否记录过这个数据
 70                         if(map.containsKey(year)){
 71                             //已存在,则记录数+该年死亡人数
 72                             map.put(year, map.get(year)+Integer.parseInt(strs[10]));
 73                         }else{
 74                             map.put(year, Integer.parseInt(strs[10]));
 75                         }
 76                         
 77                     } catch (Exception e) {
 78                         // TODO Auto-generated catch block
 79                         
 80                     }
 81                     
 82                 }
 83             }
 84             //System.out.println(map);
 85             
 86             //记录死亡人数最多的年份
 87             int max_year=-1;
 88             //记录死亡人数
 89             int dead_count=0;
 90             //用set无序获取map中的key值,即年份
 91             Set<Integer> keyset=map.keySet();
 92             //
 93             for(int year:keyset){
 94                 //当前年事故死亡最多的年份,记录年和次数
 95                 if(map.get(year)>dead_count&&map.get(year)<10000){
 96                     max_year=year;
 97                     dead_count=map.get(year);
 98                 }
 99             }
100             
101             System.out.println("死亡人数最多的年份:"+(max_year+1901)+"   死亡人数:"+dead_count);
102         }
103         /**
104          * 记录事故次数最多的年份
105          * 输出该年及事故次数
106          */
107         public static void MaxAccidentsYear(){
108             //存放年份,该年的事故次数
109             Map<Integer,Integer> map=new HashMap<>();
110             SimpleDateFormat sdf =new SimpleDateFormat("MM/dd/YYYY");
111             //循环所有数据
112             for(String data:alldata){
113                 String[] strs=data.split(",");
114                 if(strs[0]!=null){
115                     try {
116                         Date date=sdf.parse(strs[0]);
117                         //获取年份
118                         int year=date.getYear();
119                         //判断是否存在记录
120                         if(map.containsKey(year)){
121                             //已存在记录,+1
122                             map.put(year, map.get(year)+1);
123                         }else{
124                             map.put(year, 1);
125                         }
126                     } catch (Exception e) {
127                         // TODO Auto-generated catch block                        
128                     }                                                
129                 }
130             }
131             //记录事故次数最多的年份
132             int max_year=0;
133             //该年事故发生次数
134             int acc_count=0;
135             //循环所有数据,获取事故次数最多的年份
136             Set<Integer> keyset=map.keySet();
137             for(int year:keyset){
138                 if(map.get(year)>acc_count){
139                     max_year=year;
140                     acc_count=map.get(year);
141                 }
142             }
143             //输出结果
144             System.out.println("事故次数最多的年份"+(max_year+1901)+"  该年事故发生次数:"+acc_count);
145         }
146         /**
147          * FrequencyPeriod()
148          * 各个时间段发生事故的次数
149          */
150         public static void FrequencyPeriod(){
151             //key为时间段,value为发生事故次数
152             Map<String,Integer>  map=new HashMap<>();
153             //String数组存放时间段
154             String[] strsTime={"上午(6:00~12:00)","下午(12:00~18:00)","晚上(18:00~24:00)","凌晨(0:00~6:00)"};
155             //小时:分钟
156             SimpleDateFormat sdf=new SimpleDateFormat("HH:mm");
157             
158             for(String data:alldata){
159                 String[] strs=data.split(",");
160                 //判断时间是否记录,未记录则忽略
161                 if(strs[1]!=null){
162                     try {
163                         Date date=sdf.parse(strs[1]);
164                         //取得小时数
165                         int hour=date.getHours();
166                         //判断小时数在哪个范围中
167                         int index=0;
168                         if(hour>=12&&hour<18){
169                             index=1;
170                         }else if(hour>=18){
171                             index=2;
172                         }else if(hour<6){
173                             index=3;
174                         }
175                         //记录到map中
176                         if(map.containsKey(strsTime[index])){
177                             map.put(strsTime[index], map.get(strsTime[index])+1);
178                         }else{
179                             map.put(strsTime[index], 1);
180                         }                                                            
181                     } catch (ParseException e) {                        
182                     }                
183                 }
184                 
185             }
186             /*
187             System.out.println("各时间段发生事故次数:");
188             for(int i=0;i<strsTime.length;i++){        
189             System.out.println(strsTime[i]+" : "+map.get(strsTime[i]));
190             }        
191             */
192             // 记录出事故最多的时间范围
193             String maxTime = null;
194             // 记录出事故最多的次数
195             int maxCount = 0;
196 
197             Set<String> keySet = map.keySet();
198             for (String timeScope : keySet) {
199                 if (map.get(timeScope) > maxCount) {
200                     // 当前年就是出事故最多的年份,记录下年和次数
201                     maxTime = timeScope;
202                     maxCount = map.get(timeScope);
203                 }
204             }
205             System.out.println("发生事故次数最多的时间段:");
206             System.out.println(maxTime+" : "+maxCount);                            
207         }
208         /**
209          * 获取幸村率最高的一条数据的内容
210          * 返回该内容及幸存率
211          */
212         public static void MaximumSurvival(){
213             //存放事故信息以及该事故的幸村率
214             Map<String,Float> map=new HashMap<>();
215             //SimpleDateFormat sdf =new SimpleDateFormat("MM/dd/YYYY");
216             //事故幸存率=1-死亡率,第十一个是死亡人数,第十个是总人数
217             float survial=0;        
218             //循环所有数据
219             for(String data:alldata){
220                 try{
221                 String[] strs=data.split(",");
222                 //计算幸存率
223                 float m=Float.parseFloat(strs[10]);
224                 float n=Float.parseFloat(strs[9]);
225                 survial=1-m/n;
226                 map.put(data, survial);
227                 }catch(Exception e){
228                     
229                 }
230             }
231             //记录事故次数最多的年份
232             float max_survial=0;    
233             //幸存率最高的数据信息
234             String this_data="null";
235             //循环所有数据,获取事故次数最多的年份
236             Set<String> keyset=map.keySet();
237             for(String data:keyset){
238                 if(map.get(data)>max_survial){
239                     this_data=data;
240                     max_survial=map.get(data);
241                 }
242             }
243             System.out.println("幸存率最高的事故是:"+this_data);
244             System.out.println("幸存率为:"+survial);
245         }    
246 }

 

Ⅱ  接下来我们就可以在网页的数据上下手了。

下面先实现一个单网页数据提取的功能。

使用的技术可以有以下几类

1) 原生代码实现:

  a) URL

2) 使用第三方的URL

  a) HttpClient

3) 开源爬虫框架

  a) Heritrix

  b) Nutch

 【一】

先使用URL,来将当当网下搜索机械表的内容提取出来。

 1 package com.exe1;
 2 /**
 3  * 读取当当网下机械表的数据,并进行分析
 4  * sunwengang   2017-08-13  20:00
 5  */
 6 import java.io.*;
 7 import java.net.*;
 8 
 9 public class URLDemo {
10     public static void main(String args[]){
11         //确定爬取的网页地址,此处为当当网搜机械表显示的网页
12         //网址为        http://search.dangdang.com/?key=%BB%FA%D0%B5%B1%ED&act=input
13         String strurl="http://search.dangdang.com/?key=%BB%FA%D0%B5%B1%ED&act=input";
14         //建立url爬取核心对象
15         try {
16             URL url=new URL(strurl);
17             //通过url建立与网页的连接
18             URLConnection conn=url.openConnection();
19             //通过链接取得网页返回的数据
20             InputStream is=conn.getInputStream();
21             
22             System.out.println(conn.getContentEncoding());
23             //一般按行读取网页数据,并进行内容分析
24             //因此用BufferedReader和InputStreamReader把字节流转化为字符流的缓冲流
25             //进行转换时,需要处理编码格式问题
26             BufferedReader br=new BufferedReader(new InputStreamReader(is,"UTF-8"));
27         
28             //按行读取并打印
29             String line=null;
30             while((line=br.readLine())!=null){
31                 System.out.println(line);
32             }
33             
34             br.close();
35         } catch (Exception e) {
36             // TODO Auto-generated catch block
37             e.printStackTrace();
38         }
39         
40     }
41 }

 

 结果显示:

【二】

下面尝试将这个网页的源代码保存成为本地的一个文本文件,以便后续做离线分析。

如果想根据条件提取网页中的内容信息,那么就需要使用Java的正则表达式

 

正则表达式

Java.util包下提供了PatternMatcher这两个类,可以根据我们给定的条件来进行数据的匹配和提取。

通过Pattern类中提供的规则字符或字符串,我们需要自己拼凑出我们的匹配规则。

正则表达式最常用的地方是用来做表单提交的数据格式验证的

常用的正则表达式规则一般分为两类:

1) 内容匹配

  a) \\d:是否是数字

  b) \\w:匹配 字母、数字或下划线

  c) .:任意字符

  d) [a-z]:字符是否在给定范围内。

2) 数量匹配

  a) +1个或以上

  b) *0个或以上

  c) ?01

  d) {n,m}n-m

 

匹配手机电话号码:

规则:1\\\\d{10}

匹配邮件地址:

规则:\\\\w+@\\\\w+.\\\\w+(\\\\.\\\\w+)?

 

通过PatternMatcher的配合,我们可以把一段内容中匹配我们要求的文字提取出来,方便我们来处理。

例如:将一段内容中的电话号码提取出来。

 1 public class PatternDemo {
 2 
 3     public static void main(String[] args) {
 4         Pattern p = Pattern.compile("1\\\\d{10}");
 5 
 6         String content = "<div><div class=\'jg666\'>[转让]<a href=\'/17610866588\' title=\'手机号码17610866588估价评估_值多少钱_归属地查询_测吉凶_数字含义_求购转让信息\' class=\'lj44\'>17610866588</a>由 张云龙 300元转让,联系电话:17610866588</div><div class=\'jg666\'>[转让]<a href=\'/17777351513\' title=\'手机号码17777351513估价评估_值多少钱_归属地查询_测吉凶_数字含义_求购转让信息\' class=\'lj44\'>17777351513</a>由 胡俊宏 888元转让,QQ:762670775,联系电话:17777351513,可以小砍价..</div><div class=\'jg666\'>[求购]<a href=\'/15019890606\' title=\'手机号码15019890606估价评估_值多少钱_归属地查询_测吉凶_数字含义_求购转让信息\' class=\'lj44\'>15019890606</a>由 张宝红 600元求购,联系电话:15026815169</div><div class=\'jg666\'>";
 7 
 8         Matcher m = p.matcher(content);
 9         // System.out.println(p.matcher("sf@sina").matches());
10         Set<String> set = new HashSet<>();
11         // 通过Matcher类的group方法和find方法来进行查找和匹配
12         while (m.find()) {
13             String value = m.group();
14             set.add(value);
15         }
16         System.out.println(set);
17     }
18 }

通过正则表达式完成超连接的连接匹配和提取

对爬取的html页面来说,如果想提取连接地址,就必须找到所有超连接的标签和对应的属性。

超连接标签是<a></a>,保存连接的属性是:href

<a href=”…”>…</a>

规则:

<a .*href=.+</a>

广度优先遍历

需要有一个队列(这里直接使用ArrayList来作为队列)保存所有等待爬取的连接。

还需要一个Set集合记录下所有已经爬取过的连接。

还需要一个深度值,记录当前爬取的网页深度,判断是否满足要求

此时对当当网首页分类里的图书进行深度为2的网页爬取,参照上述对机械表单网页的爬取,利用递归的方式进行数据获取存到E:/dangdang_book/目录下:

  1 package com.exe1;
  2 /**
  3  * 读取当当网下首页图书的数据,并进行分析
  4  * 爬取深度为2
  5  * 爬去数据存储到E:/dangdang_book/目录下,需自行创建
  6  * sunwengang   2017-08-13  20:00
  7  */
  8 import java.io.*;
  9 import java.net.*;
 10 import java.util.*;
 11 import java.util.regex.*;
 12 
 13 public class URLDemo {

以上是关于java实现网页爬虫的主要内容,如果未能解决你的问题,请参考以下文章

java实现网页爬虫

Java实现网络爬虫-Java入门|Java基础课程

使用Java实现网络爬虫

网络爬虫Java实现抓取网页内容

java爬虫抓取指定数据

请大家帮帮忙. 使用java爬虫得到网页以后怎么提取里面自己需要的内容呢?如果会代码请您写一下.谢谢您