带有翻新功能的Android Web抓取

时间:2020-02-23 14:29:24  来源:igfitidea点击:

在本教程中,我们将在Android应用程序中实施网络搜集。
我们将抓取theitroad.local以获取主页上列出的所有单词。
我们将使用翻新库来阅读网页。

Android改装转换器

在以下教程中,我们对翻新进行了很多介绍:

  • 改造基础
  • 改造和RxJava
  • 改造离线缓存
  • 间隔调用
  • 改造下载文件
  • 改造MVP Dagger RxJava
  • 改造下载并显示通知进度

大多数时候,我们使用Gson对JSON响应进行序列化/反序列化。
为此,我们在Retrofit Builder中使用了GsonConverters。

在某些情况下,您只需要纯文本作为网络调用的响应正文即可。
在这种情况下,我们需要使用"标量转换器"来代替GsonConverters。

为了使用标量转换器,您需要在build.gradle中添加以下依赖以及Retrofit和OkHttp依赖。

implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'

要将标量转换器添加到Retrofit Builder,请执行以下操作:

Retrofit retrofit = new Retrofit.Builder()
              .addConverterFactory(ScalarsConverterFactory.create())
              .baseUrl("BASE URL")
              .client(okHttpClient).build();

我们也可以向构建器添加多个转换器。
但是顺序很重要,因为改造会选择第一个兼容的转换器。

如果我们不想使用"标量转换器",可以使用OkHttp中的RequestBody和ResponseBody类作为类型。
" RequestBody"和" ResponseBody"允许使用enqueue方法中的" request.body()"接收任何类型的响应数据。

唯一的缺点:您需要自己处理RequestBody对象的创建。

网页使用HTML格式,因此为了解析它们,我们将使用Jsoup库。

在以下部分中,我们将使用ScalarConverter来解析在Retrofit请求中传递的。
我们将获取所有文本单词,并将每个单词的数量保留在RecyclerView中。

另外,我们将添加过滤器功能,该功能可按计数过滤单词。
我们将使用哈希表存储单词/计数对,并按值对其进行排序。

项目结构

在build.gradle中的依赖是:

implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
implementation 'com.android.support:design:28.0.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'
implementation 'org.jsoup:jsoup:1.10.1'

代码

" activity_main.xml"的代码定义如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
  xmlns:app="https://schemas.android.com/apk/res-auto"
  xmlns:tools="https://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <android.support.v7.widget.RecyclerView
      android:id="@+id/wordList"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:orientation="vertical"
      app:layoutManager="android.support.v7.widget.LinearLayoutManager"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      app:layout_constraintTop_toTopOf="parent" 

  <android.support.design.widget.FloatingActionButton
      android:id="@+id/fab"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_margin="16dp"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintLeft_toLeftOf="parent"
      android:src="@drawable/ic_filter_list"
      app:layout_constraintRight_toRightOf="parent" 

</android.support.constraint.ConstraintLayout>

ApiService.java类的代码如下:

package com.theitroad.androidwebscrapingretrofit;

import retrofit2.Call;
import retrofit2.http.GET;

public interface ApiService {

  @GET(".")
  Call<String> getStringResponse();
}

"。
"用于指定没有路径。
因此,仅将使用基本URL。

MainActivity.java的代码如下:

package com.theitroad.androidwebscrapingretrofit;

import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import org.jsoup.Jsoup;
import org.jsoup.helper.StringUtil;
import org.jsoup.nodes.Document;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.scalars.ScalarsConverterFactory;

public class MainActivity extends AppCompatActivity {

  RecyclerView recyclerView;
  FloatingActionButton fab;
  HashMap<String, Integer> occurrences = new HashMap<>();
  WordsAdapter wordsAdapter;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      recyclerView = findViewById(R.id.wordList);

      fab = findViewById(R.id.fab);

      OkHttpClient okHttpClient = new OkHttpClient().newBuilder().addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
              .build();

      Retrofit retrofit = new Retrofit.Builder()
              .addConverterFactory(ScalarsConverterFactory.create())
              .baseUrl("https://www.theitroad.local/")
              .client(okHttpClient).build();

      final ApiService apiService = retrofit.create(ApiService.class);

      Call<String> stringCall = apiService.getStringResponse();
      stringCall.enqueue(new Callback<String>() {
          @Override
          public void onResponse(Call<String> call, Response<String> response) {
              if (response.isSuccessful()) {

                  String responseString = response.body();
                  Document doc = Jsoup.parse(responseString);
                  responseString = doc.text();
                  createHashMap(responseString);
              }

          }

          @Override
          public void onFailure(Call<String> call, Throwable t) {

          }
      });

      fab.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {

              occurrences = sortByValueDesc(occurrences);

              wordsAdapter = new WordsAdapter(MainActivity.this, occurrences);
              recyclerView.setAdapter(wordsAdapter);

          }
      });

  }

  private void createHashMap(String responseString) {

      responseString = responseString.replaceAll("[^a-zA-Z0-9]", " ");

      String[] splitWords = responseString.split(" +");

      for (String word : splitWords) {

          if (StringUtil.isNumeric(word)) {
              continue;
          }

          Integer oldCount = occurrences.get(word);
          if (oldCount == null) {
              oldCount = 0;
          }
          occurrences.put(word, oldCount + 1);
      }

      wordsAdapter = new WordsAdapter(this, occurrences);
      recyclerView.setAdapter(wordsAdapter);
  }

  public static HashMap<String, Integer> sortByValueDesc(Map<String, Integer> map) {
      List<Map.Entry<String, Integer>> list = new LinkedList(map.entrySet());
      Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
          @Override
          public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
              return o2.getValue().compareTo(o1.getValue());
          }
      });

      HashMap<String, Integer> result = new LinkedHashMap<>();
      for (Map.Entry<String, Integer> entry : list) {
          result.put(entry.getKey(), entry.getValue());
      }
      return result;
  }

}

以下代码从HTML格式解析字符串;

Document doc = Jsoup.parse(responseString);
                  responseString = doc.text();

在" createHashMap"内部,我们删除了所有特殊字符,并从哈希图中省略了所有数字。
sortByValueDesc使用Comparator比较值并按降序对HashMap进行排序。

下面给出了list_item_words.xml的代码,其中包含RecyclerView行的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:background="?attr/selectableItemBackground"
  android:padding="24dp">

  <TextView
      android:id="@+id/txtWord"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentStart="true"
      android:layout_centerVertical="true" 

  <TextView
      android:id="@+id/txtCount"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentEnd="true"
      android:layout_centerVertical="true" 

</RelativeLayout>

下面给出了WordsAdapter.java类的代码:

package com.theitroad.androidwebscrapingretrofit;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.HashMap;

public class WordsAdapter extends RecyclerView.Adapter<WordsAdapter.WordsHolder> {

  HashMap<String, Integer> modelList;
  Context mContext;
  private String[] mKeys;

  class WordsHolder extends RecyclerView.ViewHolder {

      TextView txtWord;
      TextView txtCount;

      public WordsHolder(View itemView) {
          super(itemView);

          txtWord = itemView.findViewById(R.id.txtWord);
          txtCount = itemView.findViewById(R.id.txtCount);
      }
  }

  public WordsAdapter(Context context, HashMap<String, Integer> modelList) {
      this.modelList = modelList;
      mContext = context;
      mKeys = modelList.keySet().toArray(new String[modelList.size()]);
  }

  @NonNull
  @Override
  public WordsHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
      View view = LayoutInflater.from(mContext).inflate(R.layout.list_item_words, parent, false);
      return new WordsHolder(view);
  }

  @Override
  public void onBindViewHolder(@NonNull WordsHolder holder, int position) {
      holder.txtWord.setText(mKeys[position]);
      holder.txtCount.setText(String.valueOf(modelList.get(mKeys[position])));
  }

  @Override
  public int getItemCount() {
      return modelList.size();
  }

}

上述应用程序的输出如下:

因此,以上输出显示了在撰写本教程时theitroad主页上出现的所有单词及其频率。