Android 使用Jsoup解析网页批量获取图片

一、前言

有时候浏览网站图片感觉不是很方便,不是广告太多就是要一个个翻页。我们都知道用Python做数据的爬取挺方便的,所以我想Java有没有类似的方法可以获取网页图片数据然后在Android端上显示。
答案肯定是有的,所以我就搞出来了,老规矩先看看效果:

图片来源:摄图网照片

应用主界面 >
启动应用
查看图片详情 >
查看图片详情

二、框架介绍

1、Jsoup简介

Jsoup 是Java的一个HTML解析工具,可直接解析某个URL地址、HTML文本内容,功能如下:

(1)从一个URL,文件或字符串中解析HTML

(2)使用DOM或CSS选择器来查找、取出数据使用DOM或CSS选择器来查找、取出数据

(3)可操作HTML元素、属性、文本可操作HTML元素、属性、文本

主要类:Jsoup类提供了连接,清理和解析HTML文档的方法;Document 获取HTML文档;Element 获取、操作HTML节点。详情见官网

2、EventBus简介

EventBus是由greenrobot组织开发的一个Android事件发布/订阅轻量级框架,通过解耦发布者和订阅者简化Android事件传递。EventBus可以代替Android传统的Intent、Handler、Broadcast或接口函数,在Fragment、Activity、Service线程之间传递数据以及执行方法。详情见源码

3、RecyclerView及Glide

RecyclerView就不用多说了,主要用于数据的列表展示。
Glide 是一款强大的Android图片加载和缓存库,可高效地加载网络图片、本地图片、资源ID图片等格式的图片文件,代码简洁好用。详情见郭霖大神博客

三、具体实现

1、需求确认

以摄图网照片网址为例:https://699pic.com/photo/
可以看到其首页有各个分类的照片,点击某一项可看到其详情图片页
在这里插入图片描述
图片详情页包含了图片主题、图片数量等内容在这里插入图片描述
eeeee… 这小龙虾把我都看饿了 T-T

总额言之!我们只需要实现首页的分类预览以及详情展示即可,其他信息可根据个人需求获取。。

2、引入依赖

确定好开发所需的框架后,在项目中的app / build.gradle下导入依赖:
导入Jsoup、EventBus、Glide以及SpinKit一个动画加载库,用于网络请求时显示动画效果

implementation 'org.jsoup:jsoup:1.13.1'
implementation 'org.greenrobot:eventbus:3.2.0'
implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation 'com.github.ybq:Android-SpinKit:1.4.0'

3、UI布局

项目的需求比较简单,首页分类图片的显示,在主布局文件中使用RecyclerView做首页图片预览即可。
activity_main.xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

	<!--自定义标题栏-->
    <RelativeLayout
        android:id="@+id/tool_bar"
        android:background="@color/blue"
        android:layout_width="match_parent"
        android:layout_height="50dp">

        <TextView
            android:id="@+id/tag_name"
            android:layout_centerHorizontal="true"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:textColor="@color/white"
            android:text="HappyLook"/>
    </RelativeLayout>

	<!--加载动画-->
    <com.github.ybq.android.spinkit.SpinKitView
        android:id="@+id/load_view"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_centerInParent="true"
        style="@style/SpinKitView.CubeGrid"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/main_recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/tool_bar"
        android:padding="8dp" />
</RelativeLayout>

然后是一个点击查看图片详情的界面,包含了返回按钮、图片名称(数量)
sec_layout.xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
	
	<!--自定义标题栏-->
    <RelativeLayout
        android:id="@+id/pic_bar"
        android:background="@color/blue"
        android:layout_width="match_parent"
        android:layout_height="50dp">
		
		<!--返回按钮-->
        <ImageView
            android:id="@+id/btn_back"
            android:layout_width="35dp"
            android:layout_height="35dp"
            android:layout_centerVertical="true"
            android:layout_marginStart="10dp"
            android:src="@mipmap/ic_back"/>
		
		<!--图片分类-->
        <TextView
            android:id="@+id/pic_name"
            android:layout_centerHorizontal="true"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:textColor="@color/white"
            android:text="火锅烧烤"/>
    </RelativeLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:layout_centerHorizontal="true"
        android:layout_below="@id/pic_bar"
        android:id="@+id/pic_recycler_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:padding="8dp"/>

    <com.github.ybq.android.spinkit.SpinKitView
        android:id="@+id/pic_load_view"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:layout_centerInParent="true"
        style="@style/SpinKitView.Circle"
        android:visibility="gone"/>
</RelativeLayout>

4、网页解析

解析网页获取元素,就是通过网页特有的标签属性对所需元素进行定位。这是关键的一步,使用浏览器开发者模式定位获取所需的元素,我用的是谷歌浏览器按F12就能进入,其他浏览器应该也差不多。图片分类首页进入开发者模式后就点击左上角Element一栏,开始元素定位:

1、div class=“img-show” 整个网页显示分类图片的预览的地方
2、div class=“pl-list” 每一项的图片分类
3、href=" " 该类图片的详情链接地址
4、data-original=" " 预览封面的图片地址
5、alt=" " 图片分类主题
在这里插入图片描述
然后点击进入到图片详情页获取元素
1、li class=“list” 每张图片项
2、 img src=" " data-original=" " 图片的地址(两个获取哪个都可以)
在这里插入图片描述

最后获取该类图片的页数
"div pager-linkPage" 图片页数列表
"a href=" " 页数链接
在这里插入图片描述
以上就是我们需要获取的所有元素。

5、功能实现

这里我介绍几个主要的类和方法,其他比较常用的东西就不多说了。项目Demo地址我会在文章末尾给大家放出来,如果有感兴趣的朋友可以去看一看。

根据网页解析所得到的元素,我们先建立一个bean文件夹并在里面新建一个Pictures类,包括图片详情链接、预览图链接以及分类名称三个变量。Pictures.java如下:

/**
 * Created by qzh.
 * Date: 2021/4/19
 */
public class Pictures {
    private String picUrl; //图片详情链接
    private String preViewImg; //预览图链接
    private String picName; //分类名称

    public String getPicUrl() {
        return picUrl;
    }

    public void setPicUrl(String picUrl) {
        this.picUrl = picUrl;
    }

    public String getPreViewImg() {
        return preViewImg;
    }

    public void setPreViewImg(String preViewImg) {
        this.preViewImg = preViewImg;
    }

    public String getPicName() {
        return picName;
    }

    public void setPicName(String picName) {
        this.picName = picName;
    }
}

然后新建一个Tools.java工具类,用于解析网页获取数据、发送事件等操作。
sendMeaasge() 封装消息发送方法,使用EventBus来发送传递数据给主线程,一个带数据,一个不带数据。

  /**
     * EventBus发送消息
     * @param what 事件标签
     */
    public static void sendMessage(int what){
        Message message = new Message();
        message.what = what;
        EventBus.getDefault().post(message);
    }

    /**
     * EventBus发送消息
     * @param what 事件标签
     * @param obj 数据对象
     */
    public static void sendMessage(int what , Object obj){
        Message message = new Message();
        message.what = what;
        message.obj = obj;
        EventBus.getDefault().post(message);
    }

InitData() 函数主要通过Jsoup来对网址链接进行解析 在子线程中进行,connect连接网址、timeout设置请求超时限制,使用get方法获取Docment实例,大家可以打印里面的数据看看,出来的是body的文本类型。
然后根据解析类型来执行不同的方法:getMainData() 获取首页数据,getPicData() 获取照片详情数据。

/**
     * 初始化网页数据
     * @param mUrl 解析链接
     * @param type 解析类型:0是首页,1是图片详情页
     */
    public static void initData( String mUrl , int type){
        new Thread(() -> {
            try {
                Document document = Jsoup.connect(mUrl).timeout(5000).get();
                if (0 == type){
                    if (list != null){
                        list.clear();
                    }
                    getMainData(document);
                }else if (1 == type){
                    if (mList != null){
                        mList.clear();
                    }
                    getPicData(document);
                }
            }catch (Exception e){
                e.printStackTrace();
                sendMessage(1008613);
            }
        }).start();
    }

getMainData() 获取首页数据:
根据我们上面所定位的标签获取相应元素,每一个elements对象对应这一个类型的照片,根据其数量生成对应的Pictures实例,并添加到列表中,最后使用自定义的sendMessage() 向主线程发送事件和数据。

    /**
     * 定位首页元素,获取分类预览封面、标题、链接
     * 生成 Pictures 实例
     * @param document Jsoup文档类解析网页body
     */
    private static void getMainData(Document document ){
        Elements elements = document.select("div.pl-list");
            for (Element element : elements){
                String preImg = element.select("img").attr("data-original");
                String name = element.select("img").attr("alt");
                String url = element.select("a").attr("href");
                Pictures pictures = new Pictures();
                pictures.setPreViewImg(preImg);
                pictures.setPicName(name);
                pictures.setPicUrl(url);
                list.add(pictures);
            }
        sendMessage(10086, list);
    }

getPicData() 获取图片详情数据:
使用getPicSrc() 方法获取每张图片的链接,getPageLink() 方法获取翻页并返回翻页链接数量,之后循环获取每页中的具体照片链接,最后使用sendMessage() 向主线程发送消息和数据

/**
     * 获取所有照片数据
     * 若当前类图片存在翻页,则循环获取其他页数照片数据
     * @param document Jsoup文档类解析网页body
     */
    private static void getPicData(Document document) {
        mList = getPicSrc(document);
        List<String> linkList = getPageLink(document);
        if (linkList.size() != 0 ){
            for (int i = 0 ; i < linkList.size() ; i++){
                try {
                    Document doc = Jsoup.connect(linkList.get(i)).timeout(5000).get();
                    mList.addAll(getPicSrc(doc));
                }catch (Exception e){
                    e.printStackTrace();
                    sendMessage(1008613);
                }
            }
        }
        Log.d(TAG, "照片数量: "+ mList.size());
        sendMessage(1008611 , mList);
    }

接下来就是实现MainActivity里的方法了:
onCreate() 方法里调用initView() 声明控件,并调用Tools.initData() 获取网页数据,要注意的是在使用EventBus之前需要对订阅者(当前上下文)进行注册,在活动销毁的时候取消注册。

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        Tools.initData(Tools.photoUrl,0);
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!EventBus.getDefault().isRegistered(this)){ 
            EventBus.getDefault().register(this); //注册当前上下文
        }
    }

    @SuppressLint("WrongViewCast")
    public void initView(){
        recyclerView = findViewById(R.id.main_recycler);
        tagName = findViewById(R.id.tag_name);
        loadView = findViewById(R.id.load_view);
        GridLayoutManager layoutManager = new GridLayoutManager(this,2);
        recyclerView.setLayoutManager(layoutManager);
        adapter = new MainAdapter(this ,this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this); //活动销毁注销
    }

在应用加载完首页分类的图片后,需要点击进入到图片详情页面中,这时候需要在适配器中写一个监听接口,然后再MainActivity中实现:(这里适配器贴个接口代码,具体实现可参考Demo)

MainAdapter.java

/**
 * Created by qzh.
 * Date: 2021/4/17
 */
public class MainAdapter extends RecyclerView.Adapter<MainAdapter.ViewHolder>  {

    private final ItemClickListener mListener;

    public interface ItemClickListener{
        void clickListener(View v ,String categoryName/*图片分类名称*/, String moreUrl/*该类图片链接*/);
    }
 }

MainActivity.java 中实现接口,获取类名以及链接,再次调用Tools.initData() 获取图片详情页,最后结果通过sendMessage() 返回

   @Override
    public void clickListener(View v, String categoryName, String moreUrl) {
        loadView.setVisibility(View.VISIBLE);
        recyclerView.setVisibility(View.INVISIBLE);
        picName = categoryName;
        Tools.initData(moreUrl , 1);
    }

然后是消息接收处理,使用EventBus发送过来的消息在MainActivty中的处理如下:
将该处理函数声明为主线程接收模式 @Subscribe(threadMode = ThreadMode.MAIN) 这样就会接收来自Tools里的信息,然后根据msg.what执行相应的操作:

10086: 获取首页图片数据,这里需要把msg.obj对象转化一下变成 Pictures 的列表对象 然后更新到适配器中显示出来。

1008611: 获取图片详情页数据,将图片类型名称以及数据列表传到 SecActivity中显示出来。

后面两个方法就是对UI进行简单的控制

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void handleMessage(@NonNull Message msg) {
        switch (msg.what){
            case 10086:
                loadView.setVisibility(View.INVISIBLE);
                adapter.setList((List<Pictures>)msg.obj);
                recyclerView.setAdapter(adapter);
                break;
            case 1008611:
                loadView.setVisibility(View.INVISIBLE);
                ArrayList<String> mList = (ArrayList<String>)msg.obj;
                Intent intent = new Intent(MainActivity.this,SecActivity.class);
                intent.putExtra("picName",picName);
                intent.putStringArrayListExtra("picUrl", mList);
                startActivity(intent);
                break;
            case 1008612:
                recyclerView.setVisibility(View.VISIBLE);
                break;
            case 1008613:
                Toast.makeText(this, "出现错误!!", Toast.LENGTH_SHORT).show();
                break;
        }
    }

点击某个分类图片执行1008611下的方法,启动SecActivity 显示图片详情。
SecActivity.java 如下:
获取图片类型名称、图片数据以及图片数量,最后显示出来

  @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sec_layout);
        picName = getIntent().getStringExtra("picName");
        mList = getIntent().getStringArrayListExtra("picUrl");
        initView();
    }

    private void initView() {
        picRecyclerView = findViewById(R.id.pic_recycler_view);
        btn_Back = findViewById(R.id.btn_back);
        pic_Name = findViewById(R.id.pic_name);
        picLoadView = findViewById(R.id.pic_load_view);
        btn_Back.setOnClickListener(view -> {
            Tools.sendMessage(1008612);
            finish();
        });
        String title = picName +" ("+mList.size()+"p) ";
        pic_Name.setText(title);
        adapter = new SecAdapter(SecActivity.this , mList);
        manager = new LinearLayoutManager(this);
        picRecyclerView.setLayoutManager(manager);
        picRecyclerView.setAdapter(adapter);
    }

    @Override
    public void onBackPressed() {
        Tools.sendMessage(1008612);
        super.onBackPressed();
    }

6、总结

总的来说项目实现起来不难,功能比较简单,主要是使用Jsoup作为网页解析获取数据,EventBus作为通信工具在不同线程中传递信息和数据,再用RecyclerView 和Glide显示出来。后面有时间的话就继续添加完善功能,比如 网址选择,图片下载等,感兴趣的朋友可以尝试搞一下。
最后附上 Demo下载地址《《《

ok,打完收工~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值