Jsoup在Android的使用(二)爬取简书的文章

        这篇博客延迟很久了,因为前段时间有很多杂事琐事处理。这个周末,暂时没什么事情。把Jsoup在Android的使用总结一下。自己平时喜欢写点小的应用,最近写的小应用也使用到了Jsoup。上篇文章我们简单介绍了一下Jsoup,并且知道了如何使用Jsoup与想要爬取的链接建立连接。今天,我将以一个实际的小项目总结一下如何使用Jsoup爬取简书主页的文章。学会了这个小项目后,你基本可以使用Jsoup爬取你想要的数据。

        我喜欢Android开发,因此,我会把一些PC端的技术尝试在Android上实现。最初接触爬虫,是六月份从网上找到一段python爬虫程序,简单修改了一下,可以爬取百度贴吧的照片。后来,我想,在Android上有没有类似的技术呢。于是,我找到了Java爬虫库Jsoup,并且在Android上爬取了一些数据,觉得还不错。因此,才想着把Jsoup在Android的使用分享一下,这样,我们再也不用担心我们的app没有数据了。

一.爬取前的准备

1.明确需求

        既然我们是以一个小项目来讲解和总结,那么,我们首先得明确我们的需求。我们的小项目要实现什么需求,最终的一个展示效果是怎样的,我们需要先明确。在这里,我们先看一下简书的主页。那么,我们的需求就是爬取简书主页的文章,并且实现类似下面的一个列表:

2.如何实现需求

        首先,我们需要新建个项目,例如我的项目名称是JsoupBlog。我们需要在Manifest文件中配置网络访问权限,因为我们的应用是联网去爬取数据的。做完上面的工作后,我们需要引入几个关键的库。

(1)Jsoup

        毫无疑问,我们今天的主角是Jsoup,因此,我们导入Jsoup:

implementation 'org.jsoup:jsoup:1.12.1'

(2)Rxjava+RxAndroid

        做android开发的同学都知道,我们不能在UI线程中做网络请求,应该把网络请求等耗时操作放在非UI线程。jsoup和网址建立连接的过程,实际上就是一次网络请求,因此,我们需要放在子线程。自从使用了RxJava,我变懒了,不喜欢去新开子线程或者使用handler或者asynctask了。在这里,我们导入RxJava和RxAndroid,轻松实现线程切换。

implementation "io.reactivex.rxjava2:rxjava:2.2.8"
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'

(3)Glide

        我们看文章列表,每一篇文章是有图片的,因此,我们需要一个图片加载库,在这里,我们导入Glide:

implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'

(4)RecyclerView

        要实现列表,我们当然需要一个列表控件,在这里,我使用RecyclerView,RecyclerView是material design库里的控件,所以,我们需要导入design库(注意:版本与你build.gradle自动生成的appcompat-v7版本一致即可)

implementation 'com.android.support:design:28.0.0'

二.使用Jsoup爬取

1.建立连接

        我们今天要爬的内容是简书的文章,因此,我们先打开简书主页(https://www.jianshu.com)。接下来,我们按照上篇文章说过的方法,使用Jsoup与简书建立连接:

Document document = null;
String url = "https://www.jianshu.com";
document = Jsoup.connect(url).get();

2.分析html

        通过上面的步骤,我们已经拿到了简书首页的html代码。为了更方便的查看,我们可以用chrome等可以查看源码的浏览器查看源码。我们大体浏览一下源码,对比着网站的内容,找到我们要爬取的部分。看下面的两段源码,我们忽略置顶的官方文章,只去看用户的博客,来,看一下源码:

<li id="note-19243324" data-note-id="19243324" class="have-img">
    <a class="wrap-img" href="/p/7031752823e7" target="_blank">
      <img data-echo="//upload-images.jianshu.io/upload_images/3301720-db890fabf626e0ac.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/360/h/240" class="img-blur" src="//upload-images.jianshu.io/upload_images/3301720-db890fabf626e0ac.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/180/h/120" alt="120" />
    </a>
  <div class="content">
    <a class="title" target="_blank" href="/p/7031752823e7">简书一年,我找到了一份好工作</a>
    <p class="abstract">
      这是我的亲身经历,没有虚构,虽然它听起来不像真的。 2017年11月7日 星期二 晴 说起来很惭愧,在简书写了一年的文,至今,我还是原来的我,既...
    </p>
    <div class="meta">
        <span class="jsd-meta">
          <i class="iconfont ic-paid1"></i> 2139.3
        </span>
      <a class="nickname" target="_blank" href="/u/0aba71a90d02">陌上红裙</a>
      <span><i class="iconfont ic-list-like"></i> 11163</span>
        <span><i class="iconfont ic-list-money"></i> 36</span>
    </div>
  </div>
</li>

        有html语言基础的,很容易就能看懂。html是使用标签一层层嵌套的,可以联想一下Android的Manifest.xml文件,也是一层层标签嵌套。因此,即使没有html基础,对照着网页渲染后的内容,我们也大体可以解析出如下内容:

(1)最外层有个叫li的标签,它有个id属性,id的内容是note-53366789,我们猜测这是这篇文章的id。

(2)li里面是一个a标签,a标签里面是img的标签,src是图片的地址。当然,这个地址是不完整的,需要我们拼接一下。

(3)往里一层是一个div标签,它有个class属性,class的名字是content,毫无疑问,它指的是博客的内容。

(4)再往里有个a标签,有个名字为title的class。它还包含着一个href属性,内容是/p/7031752823e7。看到href,我们应该很容易考虑到它是一个地址,也就是这篇博客的地址,当然,他也是不完整的,需要我们去拼接。然后,a标签的内容是”简书一年,我找到了一份好工作“。

(5)div还有个p标签,是名为abstract的class,abstract顾名思义,是摘要的意思。它的内容是“这是我的亲身经历,xxx”。

(6)再往下是另一个div标签,它的名字是meta,里面有两个span标签,其中一个的内容是2139.3,它代表的是这篇文章的钻石数量。另一个span标签内容是11163,代表的是这篇文章被喜欢的数量。还有a标签,一个名字为nickname的class,我们很容易联想到,这是博客作者的暱称。

3.建立Model类

        经过上面我们自己的人工初步解析,我们确认,我们可以拿到如下的有用的内容:

(1)博客的标号id

(2)博客的图片地址img

(3)博客的标题title

(4)博客的链接href

(5)博客的摘要abstract

(6)博客的价值paid

(7)博客的作者nickname

(8)博客的喜欢数量like

        既然我们上面已经确认了我们可以获取的数据,那么,接下来,我们可以创建一个model类,我们取名为JianShuBlog,代码如下(由于abstract是java的关键字,所以我们就使用abs吧):

package com.example.tuduzhao.jsoupblog;

/**
 * Created by Tudu.Zhao
 * on 2019/9/6
 **/
public class JianShuBlog {

    private String id;
    private String title;
    private String href;
    private String abs;
    private String paid;
    private String name;
    private String like;
    private String img;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getHref() {
        return href;
    }

    public void setHref(String href) {
        this.href = href;
    }

    public String getAbs() {
        return abs;
    }

    public void setAbs(String abs) {
        this.abs = abs;
    }

    public String getPaid() {
        return paid;
    }

    public void setPaid(String paid) {
        this.paid = paid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLike() {
        return like;
    }

    public void setLike(String like) {
        this.like = like;
    }

    public String getImg() {
        return img;
    }

    public void setImg(String img) {
        this.img = img;
    }
}

4.解析html

        建立好model类后,接下来我们可以解析html了,解析出来的结果我们存到JianShuBlog,供我们接下来使用。解析的方法说难有点难,说简单也简单,难是因为很多同学没用过,不知道如何下手,大家可以去百度或者谷歌一下Jsoup的常用方法。后面我也会大体整理一下常用的一些方法。接下来,按照上面的model,看一下我们的解析过程。

        在解析之前,稍微说一下html元素的概念,html的每一个元素(elements),就是一对标签之间的内容。html的标签都是成对出现的,有开始有结尾,就好比我们Android的xml布局文件,一对标签中的内容就是一个控件。好,接下来,我们正式开始解析工作。

        通过观察上面的博客源码,我们可以看到,我们所需要的博客的内容,都包裹在一个li标签中。可以看到,我们的li标签中,有一个名为have-img的class。接下来,我们通过如下的方法获取所有的博客元素:

Elements input = document.getElementsByClass("have-img");

        这段代码理解起来不难,就是获取所有class为“have-img”的元素。Elements继承自ArrayList,那么,ArrayList的常用的方法,Elements也都具有。我们可以打印log,打印出我们获取到的blog的数量。可以看到,数量是40:

        接下来,我们遍历所有的博客元素,依次获取博客的标题、链接、摘要、价值、暱称、喜欢数量以及图片地址。不熟悉的同学可以跟着我后面的完整代码和注释来看。

(1)获取每篇博客的id

        获取博客的id,直接遍历元素列表,获取属性名为“data-note-id”的内容,代码如下:

String id = input.get(i).attr("data-note-id");

(2)获取博客的图片

        获取博客图片稍微麻烦一点点。因为,很明显,src的内容不是标准的图片地址,需要我们简单处理一下。这是src的内容://upload-images.jianshu.io/upload_images/5303650-16bd31b30bff0a71.png?imageMogr2/auto-orient/strip|imageView2/1/w/180/h/120

        我们在源码中点击这个链接过去,查看完整的url如下:https://upload-images.jianshu.io/upload_images/5303650-16bd31b30bff0a71.png?imageMogr2/auto-orient/strip|imageView2/1/w/180/h/120

        对比一下,完整的url需要在前面加上https:,但是,这仍然不是一个标准的图片链接地址,标准的图片地址是以.png或者.jpg等结尾的。我们发现,链接中确实有.png,我们把后面的内容去掉,如下:https://upload-images.jianshu.io/upload_images/5303650-16bd31b30bff0a71.png

        好了,我们的图片地址终于像样了,访问一下,可以看到,这个地址是原始图片,这也是我们需要的内容。实现起来不难,三步走。第一步,获取到img标签的src属性的内容;第二步,截掉?以后的字符串;第三步,字符串拼接,具体代码如下:

String src = input.get(i).select("a").select("img").attr("src");
String s = src.substring(0, src.lastIndexOf("?"));
String img = "https:" + s;

(3)获取博客的标题

        获取每篇博客的标题,这里分为两个步骤。首先遍历元素列表(input),获取class名为“content”的元素。然后获取名字为a的标签的内容,由于content的div下有两个a标签,一个是标题,一个是暱称。因此获取标题的代码如下:

Elements content = input.get(i).getElementsByClass("content");
String title = content.select("a").get(0).text();

(4)获取博客的暱称

        接着上面获取博客的标题,第二个a标签的内容就是暱称:

String name = content.select("a").get(1).text();

(5) 获取博客的链接

        上面我们提起过,博客的链接不完整,我们需要拼接一下。以第一篇博客的href为例,它是”/p/7031752823e7”,从源码中跳转到这个地址,我们查看一下浏览器的地址栏,发现它的完整地址是:https://www.jianshu.com/p/7031752823e7

        我们先获取到href属性的内容,获取后,我们在前面拼接上“https://www.jianshu.com/”,代码如下:

String href = "https://www.jianshu.com/" + content.select("a").get(0).attr("href");

(6)获取博客的摘要 

        使用的方法和获取标题的一样,只不过获取摘要的标签是p,p标签只有一个,代码如下:

String abs = content.select("p").text();

 (7)获取博客的价值

          博客价值在div标签里的第一个span标签,因此,获取博客价值的方法如下:

String paid = content.select("div").select("span").get(0).text();

(8)获取博客的喜欢数量

        博客喜欢数量在div标签里的第二个span标签,因此,获取博客喜欢数量的代码如下:

String like = content.select("div").select("span").get(1).text();

        通过上面的代码,我们获取到了首页所有博客的所有的信息,最终,我们得到了一个博客的List。完整的代码如下: 

private void connectJianShu() {
        blogList = new ArrayList<>();

        Observable.create(new ObservableOnSubscribe<List<JianShuBlog>>() {

            @Override
            public void subscribe(ObservableEmitter<List<JianShuBlog>> emitter) throws Exception {

                Document document = null;
                String url = "https://www.jianshu.com";
                document = Jsoup.connect(url).get();

                Elements input = document.getElementsByClass("have-img");
                Log.d("TTTT", "size:" + input.size());

                for (int i = 0; i < input.size(); i++) {

                    JianShuBlog blog = new JianShuBlog();

                    //获取id:.attr()方法通过key获取内容
                    String id = input.get(i).attr("data-note-id");

                    //获取图片链接:获取src属性的内容,只保留.png之前的内容,最前面拼接"https:"
                    String src = input.get(i).select("a").select("img").attr("src");
                    String s = src.substring(0, src.lastIndexOf("?"));
                    String img = "https:" + s;

                    //获取content元素
                    Elements content = input.get(i).getElementsByClass("content");

                    //获取标题和暱称:.text()直接获取标签的内容,由于div下有两个a标签,第一个是title,第二个是暱称
                    String title = content.select("a").get(0).text();
                    String name = content.select("a").get(1).text();

                    //获取链接:.attr()通过key获取内容
                    String href = "https://www.jianshu.com/" + content.select("a").get(0).attr("href");
                    //获取摘要:与获取标题一样的方法
                    String abs = content.select("p").text();
                    //获取价值和喜欢:价值和喜欢数量在div标签的两个span标签,因此,第0个是价值,第1个是喜欢数量
                    String paid = content.select("div").select("span").get(0).text();
                    String like = content.select("div").select("span").get(1).text();

                    blog.setId(id);
                    blog.setHref(href);
                    blog.setImg(img);
                    blog.setAbs(abs);
                    blog.setLike(like);
                    blog.setName(name);
                    blog.setPaid(paid);
                    blog.setTitle(title);

                    blogList.add(blog);
                    emitter.onNext(blogList);
                }
            }
        }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<List<JianShuBlog>>() {

                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(List<JianShuBlog> blogs) {
                        showBlog(blogs);
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

 三.展示爬取的内容

        我们使用RecyclerView来展示解析到的博客列表,RecyclerView的使用在此不详细展开。展示效果如下图:

        最后,总结一下。本篇文章,以Android使用Jsoup爬取简书主页博客为例,总结了Jsoup的一些常用方法,通过这个例子,如果有html的基础,可以轻松爬取想爬取的数据。即使没有html基础,也可以根据示例代码和注释爬取自己想要的数据。Jsoup的功能还是比较强大的,这是源码地址,需要的可以参考:https://github.com/hearttudu/JsoupBlog

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章