带有翻新功能的Android Web抓取
在本教程中,我们将在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主页上出现的所有单词及其频率。