爬虫怎么实现

言归正传,java实现网络爬虫一般有五种方法(据我所知,要是有其他方法的同学欢迎分享)

1.基于socket通信编写爬虫:最底层的方式,同时也是执行最高效的,不过开发效率最低。

2.基于HttpURLConnection类编写爬虫:java se的net包的核心类,主要用于http的相关操作。

3.基于apache的HttpClient包编写爬虫:由net包拓展而来,专为java网络通信编程而服务。

4.基于phantomjs之类的无头(无界面)浏览器:

    (1)它是浏览器的核心,并非浏览器。换言之,它是没有UI的浏览器。

    (2)它提供的js api,故它可以方便直接的被各种程序语言调用。换言之,似乎是js写的。

5.基于Selenium或者是WebDriver之类的有头(有界面)浏览器

    (1)它是浏览器核心,并非浏览器。换言之,它是没有界面UI的浏览器。无头,即无界面。

    (2)它提供的js api,故它可以方便直接的被各种程序语言调用。

其实第五个呢,我也不是很熟悉,到时候写第五篇的时候呢,会一边学一边写,可能会有比较幼稚的错误,欢迎大家指点哈。

这部分呢。借鉴了周光亮老师的视频上的部分讲解。

首先,这篇介绍的是socket编程编写爬虫,当然,一般在程序开发的时候我们一般不会用这种方式,毕竟httpclient几行代码的事情,但是基于这种方式使其他的方式更易于理解,了解一下还是比较必要的。

    socket并不是什么通信协义,只是为了方便tcp/ip层的上层访问tcp/ip层而做一层封装。即应用层以下负责ip地址间的传输,而应用层应用socket实现端对端的传输,即精确到    IP:端口。

首先呢,因为之后要写出很多的类,所以提前规划一下包结构是比较好的,如下:

com.lzx.simple.control这个包是用来给用户控制行为的包

com.lzx.simple.enumeration 这个包集合了一些枚举类型,如TaskLevel用来控制优先级。

package com.lzx.simple.enumeration;
 
/**
 * 抓取任务的优先级等级
 * @author Administrator
 *
 */
public enum TaskLevel {
    HIGH,MIDDLES,LOW
}
com.lzx.simple.iface.crawl这个包集合了一些接口,毕竟我们需要面向接口来编程,什么是面向接口编程?去拿本设计模式的书出去罚站

package com.lzx.simple.iface.crawl;
 
import com.lzx.simple.pojos.CrawlResultPojo;
import com.lzx.simple.pojos.UrlPojo;
 
public interface ICrawler {
    public CrawlResultPojo crawl(UrlPojo urlPojo);
}
com.lzx.simple.imple.crawl这个包集合了一些接口的实现类,比如这次的SocktCrawlerImpl类实现了ICrawler接口,这个类的代码不放了,待会儿单独解释。

com.lzx.simple.manager这个包集合了一些管理方法

com.lzx.simple.pojos这个包集合了一些简单对象,例如UrlPojo类

package com.lzx.simple.pojos;
 
import java.net.MalformedURLException;
import java.net.URL;
 
import com.lzx.simple.enumeration.TaskLevel;
 
/**
 * 简单的Java对象(Plain Ordinary Java Objects)
 * @author Administrator
 *
 */
public class UrlPojo {
    private String url;
    private TaskLevel taskLevel;
    
    public String getHost(){
        try {
            URL url = new URL(this.url);
            return url.getHost();
        } catch (MalformedURLException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        }
        return null;
    }
    
    
    public UrlPojo(String url) {
        this.url = url;
    }
    public UrlPojo(String url, TaskLevel taskLevel) {
        this.url = url;
        this.taskLevel = taskLevel;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public TaskLevel getTaskLevel() {
        return taskLevel;
    }
    public void setTaskLevel(TaskLevel taskLevel) {
        this.taskLevel = taskLevel;
    }
    
}
我们可以看到UrlPojo中有url属性(网址)和taskLevel(优先级),除了getset还有gethost函数,这个类封装 了一个需抓取的网页,还有另一个类如下:

package com.lzx.simple.pojos;
 
public class CrawlResultPojo {
    private boolean isSuccess;
    private String pageContent;
    private int httpStatuCode;
    public boolean isSuccess() {
        return isSuccess;
    }
    public void setSuccess(boolean isSuccess) {
        this.isSuccess = isSuccess;
    }
    public String getPageContent() {
        return pageContent;
    }
    public void setPageContent(String pageContent) {
        this.pageContent = pageContent;
    }
    public int getHttpStatuCode() {
        return httpStatuCode;
    }
    public void setHttpStatuCode(int httpStatuCode) {
        this.httpStatuCode = httpStatuCode;
    }
}
我们用CrawlResultPojo这个类来表示抓取结果,三个属性都很显而易见,抓取是否成功,网页内容,状态码。还有相应getset。
有些package还没有文件,之后用到会添加并说明。

然后开始我们的socket通信的类:

package com.lzx.simple.imple.crawl;
 
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
 
import javax.swing.text.AbstractDocument.BranchElement;
 
import com.lzx.simple.iface.crawl.ICrawler;
import com.lzx.simple.pojos.CrawlResultPojo;
import com.lzx.simple.pojos.UrlPojo;
 
public class SocketCrawlerImpl implements ICrawler{
 
    public CrawlResultPojo crawl(UrlPojo urlPojo) {
        CrawlResultPojo crawlResultPojo=new CrawlResultPojo();
        if (urlPojo==null||urlPojo.getUrl()==null ){
            crawlResultPojo.setSuccess(false);
            crawlResultPojo.setPageContent(null);
            
            return crawlResultPojo;
        }
        String host=urlPojo.getHost();
        if (host==null) {
            crawlResultPojo.setSuccess(false);
            crawlResultPojo.setPageContent(null);
            
            return crawlResultPojo;
        }
        BufferedWriter bufferedWriter=null;
        BufferedReader bufferedReader=null;
        try {
            Socket socket=new Socket(host, 80);
            //socket.setKeepAlive(false);
            bufferedWriter=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bufferedWriter.write("GET "+urlPojo.getUrl()+" HTTP/1.1\r\n");
            bufferedWriter.write("HOST:"+host+"\r\n");
            bufferedWriter.write("Connection:close"+"\r\n");
            bufferedWriter.write("\r\n");//提示http header结束
            bufferedWriter.flush();//flush()表示强制将缓冲区中的数据发送出去,不必等到缓冲区满.
            
            bufferedReader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line;
            StringBuilder stringBuilder=new StringBuilder();
            while((line=bufferedReader.readLine())!=null){
                stringBuilder.append(line+"\n");
            }
            crawlResultPojo.setSuccess(true);
            crawlResultPojo.setPageContent(stringBuilder.toString());
            
            return crawlResultPojo;
        } catch (UnknownHostException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        } catch (IOException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        }finally {
            try {
                if (bufferedReader!=null) {
                    bufferedReader.close();                    
                }
                if (bufferedWriter!=null) {
                    bufferedWriter.close();                                    
                }
            } catch (Exception e2) {
                // TODO: handle exception
                e2.printStackTrace();
            }
        }
        return null;
    }
    public static void main(String[] args) {
        SocketCrawlerImpl socketCrawlerImpl=new SocketCrawlerImpl();
        UrlPojo urlPojo=new UrlPojo("http://www.baidu.com");
        CrawlResultPojo crawlResultPojo=socketCrawlerImpl.crawl(urlPojo);
        System.out.println(crawlResultPojo.getPageContent());
    }
 
}

这个是整个类的源码

主要的抓取工作就在crawl这个函数中,因为我们要返回CrawlResultPojo对象,所以一开始new了一个CrawlResultPojo;

然后判断待抓取的网页对象urlPojo是不是空,以及它的属性url是不是空。如果是就构造个返回失败的CrawlResultPojo返回回去。

然后gethost获得待抓取对象的主机名,用于待会儿构造http header。当然也要判断一下首部是否为空了。

然后构造缓冲输入流和输出流bufferedWriter和bufferedReader;

然后构造Socket,构造函数的参数这里是主机名+端口,端口默认是80,如果有特殊需要再改动;当然构造函数的参数也可以是IP地址+端口,端对端通信的本质。

然后在bufferedWriter构造http首部以便发送。

这里就有一个比较有趣事情了,周光亮老师在视频的讲解中发现了一个bug,他发现使用http/1.1的协议会导致抓取有一段真空期,即程序输出完这个页面的字符串后,会停顿一段时间然后再结束程序,而改用http/1.0的协议就不会,抓取完直接结束程序,周老师当时调试了很久都没有解决。

其实主要原因呢,是他在构造http首部的时候没有加

bufferedWriter.write("Connection:close"+"\r\n");
这段代码,为什么加了这段代码后就可以去除真空期呢?

因为,

http 1.0中默认是关闭的,需要在http头加入"Connection: Keep-Alive",才能启用Keep-Alive;

http 1.1中默认启用Keep-Alive,如果加入"Connection: close ",才关闭。
嗯,1.1版本更持久一点- -,

构造完就调用flush扔出去了

然后输入流就不多说了,基本套路。

这样就实现了socket抓取网页= =。
--------------------- 
作者:AaronLin_ 
来源:CSDN 
原文:https://blog.csdn.net/qq_35159818/article/details/80753701 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值