Java爬虫(二)

Java爬虫(二)

前言:在上一篇博客中我们采用了基于HttpURLConnection的方式进行数据爬取
Java爬虫(一),但是里面没有用任何框架,通过原生http进行爬取,那么问题来了,有没有什么轻便的框架供我们使用呢?

Jsoup 是一款 Java的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据,本篇博客将主要针对jsoup技术进行数据爬取,而且为了提高效率我们将采用多线程的方式进行数据的读取


爬取入口

我们就随便找个网站进行爬取吧,原理都一样,比如我们爬取一个国家统计局的数据,首先我们找到所爬取网站的页面如下:

在这里插入图片描述

比如我就爬天津市的数据,找到要爬取的网站页面入口为:

http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2018/12.html

爬取思路

其实对于爬虫的那些代码并不需要过于计较,先把思路来理清楚,先来一张图:
在这里插入图片描述

从上图可以看出我们的基本大概思路和流程,但是由于网站的多变性我们不能冒然爬取,造成数据冗余,此网页最后一层网页只有文本信息以外,其余的网页都是一条文本信息对应一个文本信息对应一个URL地址,并且出最后一层之外,每一层的数字和文本信息对应的URL是相同的,所以我们只爬取名称就行了,如下:

在这里插入图片描述


开始爬取

注意我们下面Document导包路径都为:

import org.jsoup.nodes.Document;

最后的项目目录结构如下:
在这里插入图片描述

1.简单的创建一个maven项目

在这里插入图片描述

2.在pom.xml文件中添加如下依赖

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.11.3</version>
</dependency>

3.创建主类

public class Main {
        //待抓取的Url队列,全局共享
        public static final LinkedBlockingQueue<String> UrlQueue = new LinkedBlockingQueue<>();
        public static final WormCore wormCore = new WormCore();
        public static void main(String[] args) {
            //要抓取的根URL
            String rootUrl = "http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2018/12/1201.html";
            //先把根URL加入URL队列
            UrlQueue.offer(rootUrl);
            Runnable runnable = new MyRunnable();
            //开启固定大小的线程池,线程数为10
            ExecutorService Fixed = Executors.newFixedThreadPool(10);
            //开始爬取
            for (int i = 0;i < 10;i++){
                Fixed.submit(runnable);
            }
            //关闭线程池
            Fixed.shutdown();
        }
}

主类中定义全局共享的URL队列,同时定义Runnable实现类所要使用的核心控制模块的对象,使用固定线程池进行线程调度管理,以及根URL的定义以及入队列

4.创建Runnable实现类

public class MyRunnable implements Runnable{
    @Override
    public void run() {

        while (true) {
            try {
                Thread.sleep(200);
                //把主方法中的URL队列传给核心控制类,开始该线程的爬取
                Main.wormCore.Wormcore(Main.UrlQueue);
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

通过此实现类不断调用核心控制类WormCore的方法,进行数据的爬取

5.核心控制类

public class WormCore {
    //Document获取层对象
    private volatile Catch Catch = new Catch();
    //Document解析层对象
    private volatile Analysis analysis = new Analysis();
    //数据处理层对象
    public volatile Access access = new Access();

    public void Wormcore(LinkedBlockingQueue<String> UrlQueue) throws IOException, InterruptedException {
        synchronized (this) {
            if (!UrlQueue.isEmpty()) {
                String Url = UrlQueue.take();
                //通过Url队列中的Url抓取Document,进行Url和文本信息的抓取
                Document document = Catch.CatchDocument(Url);
                //数据解析模块返回的数据(含有文本信息以及URL)
                HashMap<String, ArrayList<String>> DataMap = analysis.AnalysisDocument(document, Url);
                //数据处理模块分离出的、只含有URL的集合
                ArrayList<String> UrlList = access.DataAccess(DataMap);
                //定义迭代器,把抓取到的Url添加到Url队列中
                Iterator<String> iterator = UrlList.iterator();

                while (iterator.hasNext()) {
                    UrlQueue.put(iterator.next());
                }
                //打印URL队列中的URL条数以及队列是否为空
                System.out.println(UrlQueue.size());
                System.out.println(UrlQueue.isEmpty());
                //为空说明爬取完毕
                if (UrlQueue.isEmpty()) {
                    System.out.println("抓取完毕!");
                    System.exit(1);
                }
            }
        }
    }
}

此类就是我们上面说的核心控制类,首先将URI队列队弹出发送给网页抓取模块从而获取Document,其次将获取的Document发送给网页解析模块提取URL和文本信息,最后将信息的HashMap交给数据处理模块获取网页中所有的URL并再加入URL队列

6.网页抓取模块

public class Catch {
    //根据网页的Url获取网页Document
    public Document CatchDocument(String Url) throws IOException {
        try {
            return Jsoup.connect(Url)
                    .userAgent("Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)").timeout(5000).get();
            //如出现超时问题就继续抓取
        }catch (SocketTimeoutException s){
            return Jsoup.connect(Url)
                    .userAgent("Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)").timeout(5000).get();
        }
    }
}

根据核心控制类传来的URL获取Document并返回,自行设置超时时间以及超时之后的操作

7.网页解析模块

public class Analysis {

        //根据Document解析网页
        public HashMap<String, ArrayList<String>> AnalysisDocument(Document document, String Url){
            //因为网页上的URL为相对地址,所以在这里进行URL的拼接,这是前半部分
            String Before_Url = Url.substring(0, Url.lastIndexOf("/") + 1);
            //储存文本信息的List
            ArrayList<String> Text = new ArrayList<>();
            //储存Url的List
            ArrayList<String> Urls = new ArrayList<>();
            HashMap<String,ArrayList<String>> Message = new HashMap<>();
            //最后一个页面的前三个文本不是我们想要的
            int Flag = 1;
            Elements elements = document.select("tr[class]").select("a[href]");
            //最后一个页面的处理
            if(elements.isEmpty()){
                elements = document.select("tr[class]").select("td");
                for (Element element : elements) {

                    if (!Number.IsNumber(element.text()) && Flag > 3) {
                        System.out.println(element.text());
                    }
                    Flag++;
                }
                //普通页面的处理
            }else {
                for (Element element : elements) {
                    if (!Number.IsNumber(element.text())) {
                        Text.add(element.text());
                        System.out.println(element.text());
                        Urls.add(Before_Url + element.attr("href"));
                    }
                }
            }
            //把文本集合和URL集合装到Map中返回
            Message.put("text",Text);
            Message.put("Url",Urls);
            return Message;
    }
}

由于最后的页面和前面的页面不同,所以需要因地制宜,我们抛出了之前说的统计用区划代码数字,只要名称,所以博主通过下面的Number类里面的方法进行了判断

8.判断是否为数字

public class Number {

    public static boolean IsNumber(String str){
        for(int i=0;i<str.length();i++){
            char c = str.charAt(i);
            if(c>=48 && c<=57){

            }else {
                return false;
            }
        }
        return true;
    }
}

9.数据处理模块

public class Access {
    //数据处理,把信息中的Url返回给核心,文本信息储存
    public ArrayList<String> DataAccess(HashMap<String, ArrayList<String>> Message){
        return Message.get("Url");
    }
}

将HashMap中保存URL的集合提取出来进行保存

10.最后运行结果如下:

在这里插入图片描述

结语:对于爬虫有多种方式,不同的方式存在着代码简明、爬取效率等各种差异,所以对于爬取数据应该要因地制宜

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值