Java爬虫入门

刚刚看到一个贼强的大佬写的一片博客,又有干货,又浅显易懂,又是从底层开始写的源码。

赶紧记录下来,以防日后不时之需。

java爬虫的详细介绍(含大量代码):
https://www.cnblogs.com/1996swg/p/7355577.html

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

package com.exe1;
/**
 * 读取当当网下机械表的数据,并进行分析
 * sunwengang   2017-08-13  20:00
 */
import java.io.*;
import java.net.*;

public class URLDemo {
    public static void main(String args[]){
        //确定爬取的网页地址,此处为当当网搜机械表显示的网页
        //网址为        http://search.dangdang.com/?key=%BB%FA%D0%B5%B1%ED&act=input
        String strurl="http://search.dangdang.com/?key=%BB%FA%D0%B5%B1%ED&act=input";
        //建立url爬取核心对象
        try {
            URL url=new URL(strurl);
            //通过url建立与网页的连接
            URLConnection conn=url.openConnection();
            //通过链接取得网页返回的数据
            InputStream is=conn.getInputStream();
            
            System.out.println(conn.getContentEncoding());
            //一般按行读取网页数据,并进行内容分析
            //因此用BufferedReader和InputStreamReader把字节流转化为字符流的缓冲流
            //进行转换时,需要处理编码格式问题
            BufferedReader br=new BufferedReader(new InputStreamReader(is,"UTF-8"));
        
            //按行读取并打印
            String line=null;
            while((line=br.readLine())!=null){
                System.out.println(line);
            }
            
            br.close();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
}

结果为:

Java爬虫入门

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

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

public class PatternDemo {

    public static void main(String[] args) {
        Pattern p = Pattern.compile("1\\d{10}");

        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'>";

        Matcher m = p.matcher(content);
        // System.out.println(p.matcher("[email protected]").matches());
        Set<String> set = new HashSet<>();
        // 通过Matcher类的group方法和find方法来进行查找和匹配
        while (m.find()) {
            String value = m.group();
            set.add(value);
        }
        System.out.println(set);
    }
}

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

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

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

规则:

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

广度优先遍历
需要有一个队列(这里直接使用ArrayList来作为队列)保存所有等待爬取的连接。
还需要一个Set集合记录下所有已经爬取过的连接。
还需要一个深度值,记录当前爬取的网页深度,判断是否满足要求
此时对当当网首页分类里的图书进行深度为2的网页爬取,参照上述对机械表单网页的爬取,利用递归的方式进行数据获取存到E:/dangdang_book/目录下:

package com.exe1;
/**
 * 读取当当网下首页图书的数据,并进行分析
 * 爬取深度为2
 * 爬去数据存储到E:/dangdang_book/目录下,需自行创建
 * sunwengang   2017-08-13  20:00
 */
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.regex.*;

public class URLDemo {
    //提取的数据存放到该目录下
    private static String savepath="E:/dangdang_book/";
    //等待爬取的url
    private static List<String> allwaiturl=new ArrayList<>();
    //爬取过的url
    private static Set<String> alloverurl=new HashSet<>();
    //记录所有url的深度进行爬取判断
    private static Map<String,Integer> allurldepth=new HashMap<>();
    //爬取得深度
    private static int maxdepth=2;
    
    public static void main(String args[]){
        //确定爬取的网页地址,此处为当当网首页上的图书分类进去的网页
        //网址为        http://book.dangdang.com/
//        String strurl="http://search.dangdang.com/?key=%BB%FA%D0%B5%B1%ED&act=input";
        String strurl="http://book.dangdang.com/";
        
        workurl(strurl,1);
        
    }
    public static void workurl(String strurl,int depth){
        //判断当前url是否爬取过
        if(!(alloverurl.contains(strurl)||depth>maxdepth)){
        //建立url爬取核心对象
        try {
            URL url=new URL(strurl);
            //通过url建立与网页的连接
            URLConnection conn=url.openConnection();
            //通过链接取得网页返回的数据
            InputStream is=conn.getInputStream();
            
            System.out.println(conn.getContentEncoding());
            //一般按行读取网页数据,并进行内容分析
            //因此用BufferedReader和InputStreamReader把字节流转化为字符流的缓冲流
            //进行转换时,需要处理编码格式问题
            BufferedReader br=new BufferedReader(new InputStreamReader(is,"GB2312"));
        
            //按行读取并打印
            String line=null;
            //正则表达式的匹配规则提取该网页的链接
            Pattern p=Pattern.compile("<a .*href=.+</a>");
            //建立一个输出流,用于保存文件,文件名为执行时间,以防重复
            PrintWriter pw=new PrintWriter(new File(savepath+System.currentTimeMillis()+".txt"));
            
            while((line=br.readLine())!=null){
                //System.out.println(line);
                //编写正则,匹配超链接地址
                pw.println(line);
                Matcher m=p.matcher(line);
                while(m.find()){
                    String href=m.group();
                    //找到超链接地址并截取字符串
                    //有无引号
                    href=href.substring(href.indexOf("href="));
                    if(href.charAt(5)=='\"'){
                        href=href.substring(6);
                    }else{
                        href=href.substring(5);
                    }
                    //截取到引号或者空格或者到">"结束
                try{
                    href=href.substring(0,href.indexOf("\""));
                }catch(Exception e){
                    try{
                        href=href.substring(0,href.indexOf(" "));
                    }catch(Exception e1){
                        href=href.substring(0,href.indexOf(">"));
                    }
                }
                if(href.startsWith("http:")||href.startsWith("https:")){
                    //输出该网页存在的链接
                    //System.out.println(href);
                    //将url地址放到队列中
                    allwaiturl.add(href);
                    allurldepth.put(href,depth+1);
                        }
                
                    }
                
                }
            pw.close();
            br.close();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //将当前url归列到alloverurl中
        alloverurl.add(strurl);
        System.out.println(strurl+"网页爬取完成,已爬取数量:"+alloverurl.size()+",剩余爬取数量:"+allwaiturl.size());
        }
        //用递归的方法继续爬取其他链接
        String nexturl=allwaiturl.get(0);
        allwaiturl.remove(0);
        workurl(nexturl,allurldepth.get(nexturl));                
        }
}

多线程爬虫实现

需要先自定义一个线程的操作类,在这个操作类中判断不同的状态,并且根据状态来决定是进行wait()等待,还是取得一个新的url进行处理。

package com.exe1;
/**
 * 读取当当网下首页图书的数据,并进行分析
 * 爬取深度为2
 * 爬去数据存储到E:/dangdang_book/目录下,需自行创建
 * 孙文刚   2017-08-13  20:00
 */
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.regex.*;

public class URLDemo {
    //提取的数据存放到该目录下
    private static String savepath="E:/dangdang_book/";
    //等待爬取的url
    private static List<String> allwaiturl=new ArrayList<>();
    //爬取过的url
    private static Set<String> alloverurl=new HashSet<>();
    //记录所有url的深度进行爬取判断
    private static Map<String,Integer> allurldepth=new HashMap<>();
    //爬取得深度
    private static int maxdepth=2;
    //生命对象,帮助进行线程的等待操作
    private static Object obj=new Object();
    //记录总线程数5条
    private static int MAX_THREAD=5;
    //记录空闲的线程数
    private static int count=0;
    
    public static void main(String args[]){
        //确定爬取的网页地址,此处为当当网首页上的图书分类进去的网页
        //网址为        http://book.dangdang.com/
//        String strurl="http://search.dangdang.com/?key=%BB%FA%D0%B5%B1%ED&act=input";
        String strurl="http://book.dangdang.com/";
        
        //workurl(strurl,1);
        addurl(strurl,0);
        for(int i=0;i<MAX_THREAD;i++){
            new URLDemo().new MyThread().start();
        }
    }
    /**
     * 网页数据爬取
     * @param strurl
     * @param depth
     */
    public static void workurl(String strurl,int depth){
        //判断当前url是否爬取过
        if(!(alloverurl.contains(strurl)||depth>maxdepth)){
            //检测线程是否执行
            System.out.println("当前执行:"+Thread.currentThread().getName()+" 爬取线程处理爬取:"+strurl);
        //建立url爬取核心对象
        try {
            URL url=new URL(strurl);
            //通过url建立与网页的连接
            URLConnection conn=url.openConnection();
            //通过链接取得网页返回的数据
            InputStream is=conn.getInputStream();
            
            //提取text类型的数据
            if(conn.getContentType().startsWith("text")){
                
            }
            System.out.println(conn.getContentEncoding());
            //一般按行读取网页数据,并进行内容分析
            //因此用BufferedReader和InputStreamReader把字节流转化为字符流的缓冲流
            //进行转换时,需要处理编码格式问题
            BufferedReader br=new BufferedReader(new InputStreamReader(is,"GB2312"));
        
            //按行读取并打印
            String line=null;
            //正则表达式的匹配规则提取该网页的链接
            Pattern p=Pattern.compile("<a .*href=.+</a>");
            //建立一个输出流,用于保存文件,文件名为执行时间,以防重复
            PrintWriter pw=new PrintWriter(new File(savepath+System.currentTimeMillis()+".txt"));
            
            while((line=br.readLine())!=null){
                //System.out.println(line);
                //编写正则,匹配超链接地址
                pw.println(line);
                Matcher m=p.matcher(line);
                while(m.find()){
                    String href=m.group();
                    //找到超链接地址并截取字符串
                    //有无引号
                    href=href.substring(href.indexOf("href="));
                    if(href.charAt(5)=='\"'){
                        href=href.substring(6);
                    }else{
                        href=href.substring(5);
                    }
                    //截取到引号或者空格或者到">"结束
                try{
                    href=href.substring(0,href.indexOf("\""));
                }catch(Exception e){
                    try{
                        href=href.substring(0,href.indexOf(" "));
                    }catch(Exception e1){
                        href=href.substring(0,href.indexOf(">"));
                    }
                }
                if(href.startsWith("http:")||href.startsWith("https:")){
                    /*
                    //输出该网页存在的链接
                    //System.out.println(href);
                    //将url地址放到队列中
                    allwaiturl.add(href);
                    allurldepth.put(href,depth+1);
                    */
                    //调用addurl方法
                    addurl(href,depth);
                        }
                
                    }
                
                }
            pw.close();
            br.close();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            //e.printStackTrace();
        }
        //将当前url归列到alloverurl中        
        alloverurl.add(strurl);        
        System.out.println(strurl+"网页爬取完成,已爬取数量:"+alloverurl.size()+",剩余爬取数量:"+allwaiturl.size());
        }
        /*
        //用递归的方法继续爬取其他链接
        String nexturl=allwaiturl.get(0);
        allwaiturl.remove(0);
        workurl(nexturl,allurldepth.get(nexturl));
        */
        if(allwaiturl.size()>0){
            synchronized(obj){
                obj.notify();
            }
        }else{
            System.out.println("爬取结束.......");
        }
                
        }
    /**
     * 将获取的url放入等待队列中,同时判断是否已经放过
     * @param href
     * @param depth
     */
    public static synchronized void addurl(String href,int depth){
        //将url放到队列中
        allwaiturl.add(href);
        //判断url是否放过
        if(!allurldepth.containsKey(href)){
            allurldepth.put(href, depth+1);
        }
    }
    /**
     * 移除爬取完成的url,获取下一个未爬取得url
     * @return
     */
    public static synchronized String geturl(){
        String nexturl=allwaiturl.get(0);
        allwaiturl.remove(0);
        return nexturl;
    }
    /**
     * 线程分配任务
     */
    public class MyThread extends Thread{
        @Override
        public void run(){
            //设定一个死循环,让线程一直存在
            while(true){
                //判断是否新链接,有则获取
                if(allwaiturl.size()>0){
                    //获取url进行处理
                    String url=geturl();
                    //调用workurl方法爬取
                    workurl(url,allurldepth.get(url));
                }else{
                    System.out.println("当前线程准备就绪,等待连接爬取:"+this.getName());
                    count++;
                    //建立一个对象,让线程进入等待状态,即wait()
                    synchronized(obj){
                        try{
                            obj.wait();
                        }catch(Exception e){
                            
                        }
                    }
                    count--;
                }
            }
        }
        
    }
}