本文介绍了如何简单地在知乎上爬取自己想要的信息,并将爬取的信息显示在Android中。记录下自己在实际写代码中遇到的问题和解决办法。整个项目具体工作有:
- 分析待爬取的知乎网页
- Android上展示数据
一、知乎网页的分析
以 https://www.zhihu.com/topic/19550874/hot 为例分析:
对于想要爬取的内容,使用浏览器自带的审查元素功能就可以查看到具体的位置,利用Jsoup分析静态网页,提取出相关标签及内容即可。
//引用的包有Jsoup1.6和Gson 2.7
public void connectZhihuAnswer() throws IOException
{
Connection conn=Jsoup.connect("https://www.zhihu.com/topic/19550874/hot");
Document doc = conn.get();
Elements answerList = doc.select("div[class=ContentItem AnswerItem]");
System.out.println(answerList.size());
for (Element element : answerList)
{
//作者 标题
System.out.println(element.attr("data-zop"));
JsonParser parser = new JsonParser();
JsonObject data = parser.parse(element.attr("data-zop")).getAsJsonObject();
String authorName = data.get("authorName").getAsString();
String title = data.get("title").getAsString();
System.out.println(authorName+":"+title);
//简略内容
Elements spanContent = element.select("span[class=RichText CopyrightRichText-richText]");
System.out.println(spanContent.text());
//赞数
Elements likeCount = element.select("button[class=Button VoteButton VoteButton--up]");
System.out.println(likeCount.text());
//回答URL
System.out.println(element.getElementsByAttributeValue("itemprop", "url").get(2).attr("content"));
}
}
到这里还算是比较简单的,但是这里获取出来的answerList
一般只有四五条,原因是这个页面知乎采用动态加载的方式。如果想获取下一页的内容可以采用模拟鼠标下滑或者在报头添加相关信息的方式(https://www.zhihu.com/question/46528604/answer/101954171)。在这里留下一个坑,等以后来填。采用模拟鼠标的方式貌似要加载.exe的文件,适合在电脑操作,而本身应用是在Android之上,所以并没有实际测试过;在使用HtmlUnit请求页面报头中添加'Content-Length'
的值时,无法正常接收数据,而且直接使用HtmlUnit接收返回数据时数据获取较慢,故两种方法都没有使用。
二、将数据展示在Android上
首先是数据的获取,因为Android的网络操作需要在不能在主线程运行,可以使用Thread+Handler或者AsyncTask获取数据并在主界面刷新UI。
其次是数据的展示,将数据放在listView中即可,如果要加入下拉刷新或者上滑加载则需要额外的工作。
在这里数据的获取我使用的是AsyncTask,贴上代码
private class SearchTask extends AsyncTask<Void, Void, Boolean>
{
Context context;
Map.Entry<String,String> topicURL;
public SearchTask(Context context,Map.Entry<String,String> topicURL)
{
this.context = context;
this.topicURL = topicURL;
}
@Override
protected Boolean doInBackground(Void... voids)
{
Connection conn= Jsoup.connect("https://www.zhihu.com/topic/" + topicURL.getValue()+ "/hot");
Document doc;
try
{
doc = conn.get();
Elements answerList = doc.select("div[class=ContentItem AnswerItem]");
for (Element element : answerList)
{
Map<String, Object> map = new HashMap<>();
//作者 标题
JsonParser parser = new JsonParser();
JsonObject data = parser.parse(element.attr("data-zop")).getAsJsonObject();
String authorName = data.get("authorName").getAsString();
String title = data.get("title").getAsString();
map.put("title", title);
//简略内容
Elements spanContent = element.select("span[class=RichText CopyrightRichText-richText]");
String minContent = spanContent.text();
map.put("content", authorName+":"+minContent);
//赞数
Elements likeCountButton = element.select("button[class=Button VoteButton VoteButton--up]");
String likeCount = likeCountButton.text();
map.put("likeCount", likeCount+"点赞");
//回答URL
String url = element.getElementsByAttributeValue("itemprop", "url").get(2).attr("content");
map.put("url", url);
map.put("topic",topicURL.getKey());
answersList.add(map);
}
} catch (IOException e)
{
e.printStackTrace();
}
return true;
}
@Override
protected void onPostExecute(final Boolean success) {
if (success) {
listView_answer.onLoadComplete();
adapter.notifyDataSetChanged();
if(getUserVisibleHint())
Toast.makeText(getActivity(), "加载完成", Toast.LENGTH_SHORT).show();
} else {
if(getUserVisibleHint())
Toast.makeText(getActivity(),"连接服务器出错",Toast.LENGTH_SHORT).show();
}
mAuthTask = null;
}
@Override
protected void onCancelled() {
mAuthTask = null;
}
}
private void setListView(View rootView)
{
listView_answer = (LoadMoreListView) rootView.findViewById(R.id.listView_answer);
listView_answer.setLoadMoreListen(this);
swip = (SwipeRefreshLayout) rootView.findViewById(R.id.swip_index);
swip.setOnRefreshListener(this);
swip.setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_red_light, android.R.color.holo_orange_light,
android.R.color.holo_green_light);
adapter = new SimpleAdapter(getContext(),answersList,R.layout.answer_layout,
new String[]{"title","content","likeCount","topic"},
new int[]{R.id.textView_title,R.id.textView_content,R.id.textView_likeCount,R.id.textView_topic});
listView_answer.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l)
{
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri content_url = Uri.parse(answersList.get(i).get("url").toString());
intent.setData(content_url);
startActivity(intent);
}
});
listView_answer.setAdapter(adapter);
}
AsyncTask中的doInBackground执行耗时操作(网络连接+爬虫),onPostExecute用于刷新UI,其中
private LoadMoreListView listView_answer;
private SimpleAdapter adapter;
adapter = new SimpleAdapter(getContext(),answersList,R.layout.answer_layout,
new String[]{"title","content","likeCount","topic"},
new int[]{R.id.textView_title,R.id.textView_content,R.id.textView_likeCount,R.id.textView_topic});
listView_answer.setAdapter(adapter);
LoadMoreListView是继承ListView,用于实现上滑加载功能,不需要这个功能可以直接使用ListView替换;SimpleAdapter将Layout元素与map元素一一对应,将map里的值加载到对应的Layout元素中。
如果需要在SimpleAdapter中插入图片,可以参照http://blog.csdn.net/u011609853/article/details/25513035 这篇文章。
这里用几个需要注意的地方。
首先是使用LoadMoreListView中(参照http://blog.csdn.net/u012782626/article/details/45701203),上滑加载时会出现一个问题:如果数据长度长于屏幕时,会无法显示listView中的最后一个Item。我们可以简单的在LoadMoreListView最后加一个空的Item,让实际上所有的Item都显示出来。
还有一个是Spinner的问题,在初始化Spinner时会自动调用一次OnItemSelectedListener事件,如果我们在OnItemSelectedListener中每一个选项都调用一次AsyncTask话会耗时很长,经测试即使注册事件之前调用 spinner.setSelection(0, false),选择其他的选项时OnItemSelectedListener依然被调用一次。
整个项目写的比较赶,很多细节都没优化,希望能对大家Android学习能有帮助。
源码地址