制作网页的Android客户端(一)
当发现一个有趣的新闻网站(AnimeNews)没有android客户端时,打算做一个学习和自用。
0.简单的需求分析
1.能看新闻
2.能查单词
3.单词表
1.建立新闻的抽象类
需要的属性有:
新闻标题
新闻内容链接
新闻描述
新闻发布时间
新闻分类
新闻编辑
新闻封面
新闻正文
package com.saltwater.animenews; /**新闻元素类 * Created by xin on 2016/10/22. * @version 1.1 */ public class NewsItem { private String mTitle=null; private String mLink=null; private String mDescription=null; private String mPubData=null; private String mCategory=null; private String mEditor=null; private String mCover=null; private String mContent=null; public String getTitle() { return mTitle; } public String getEditor() { return mEditor; } public String getDescription() { return mDescription; } public String getCover() { return mCover; } public String getContent() { return mContent; } public String getPubData() { return mPubData; } public String getLink() { return mLink; } public String getCategory() { return mCategory; } public void setTitle(String title) { mTitle = title; } public void setEditor(String author) { mEditor = author; } public void setDescription(String description) { mDescription = description; } public void setCover(String picture) { mCover = picture; } public void setContent(String content) { mContent = content; } public void setPubData(String pubData) { mPubData = pubData; } public void setLink(String link) { mLink = link; } public void setCategory(String Category) { this.mCategory = Category; } }
2.获取新闻内容
一开始的选择是爬虫获取所有的内容,然后建立自己的服务器。学习Python2天后,失败于爬虫,放弃。
第二选择使用RSS(AnimeNewsRSS)。其他属性都有了,就缺少一个封面图的链接。为了美观还是选择放弃。如果打算使用RSS的话有个Rome的开源包很好用。
最后选择jsoup解析HTML。优点是网页上展示的所有想要的东西都可以抓下来,还可以自动更新内容。缺点是新闻条目少,只有200多条,但是自用还是够了。然后如果网站的Html格式换了要重新去解析,RSS基本很少会变格式。
Jsoup的使用
首先添加jsoup的jar包(如果使用Android Studio直接在Library Dependency搜jsoup添加就好了)
然后添加一个工具类使用jsoup解析Html,参考http://www.open-open.com/jsoup/学习jsoup解析。
1.与目标URL建立链接,获取到Html文档
Document doc = Jsoup.connect(mURL).get();
2.从Html文档提取需要的属性
这里需要的属性需要从Html中去找,如果使用Chrome浏览器,可以很方便的用右键的检查功能来定位想要的属性在Html文档中的位置。
/*这是一个完整的新闻div*/
<div class="herald box news" data-topics="article108636 news people"> <div class="category-line news"></div> <div class="thumbnail" style="background-image: url(/thumbnails/cover400x200/cms/news/108636/bg_contents01.jpg); background-position: 51.873% 0.000%;"> <div class="overlay"> <div class="category news"> news </div> <div class="comments"><a href="/cms/discuss/108636">1 comments</a></div> </div> <a href="/news/2016-11-09/kamen-rider-blade-star-takayuki-tsubaki-seriously-injured-in-traffic-dispute/.108636" data-track="id=35655&from=HP.MF"></a> </div> <div class="wrap"> <div> <h3> <a href="/news/2016-11-09/kamen-rider-blade-star-takayuki-tsubaki-seriously-injured-in-traffic-dispute/.108636" data-track="id=35655&from=HP.MF"><i>Kamen Rider Blade</i> Star Takayuki Tsubaki Seriously Injured in Traffic Dispute</a> </h3> <div class="byline"> <time datetime="2016-11-09T09:37:48Z"> Nov 9, 04:37 </time> <div class="comments"><a href="/cms/discuss/108636">1 comments</a></div> <span class="topics"> <span class="people">people</span> </span> </div> <div class="preview"> <span class="intro">Actor allegedly struck in face with golf club Tuesday evening</span> <span class="full">― 34-year-old Kamen Rider Blade lead actor Takayuki Tsubaki sustained serious injuries when he was beaten in the face with a golf club at the end of a traffic dispute in Tokyo's Nakano Ward on Tuesday evening. Tsubaki had stepped from the sidewalk onto the roadway after 9:00 p.m. on Tuesday in Nakano's Chuo area, and he engaged in an argum...</span> </div> </div> </div> </div>
我们可以发现每一条新闻都是在class="herald box news"这个div内,所以通过
Elements elements = doc.select("div[class=herald box news]");
获取到Html所有的class="herald box news"的div,放了elements这个集合中。然后通过foreach循环提取每个div中的内容
需要提一下的是因为提起到的PubData属性是UTC时间字符串(<time datetime="2016-11-09T09:37:48Z">),所以需要转换一下。
还有就是对class属性值含有空格的标签(div class="herald box news")进行提取时,利用doc.select("dherald box news")是提取不到结果的,因为这里有三个class。应该使用doc.select("div[class=herald box news]"),所以程序中尽可能还是用[attr=val]模式进行查询。
for(Element element : elements) { mItem=new NewsItem(); mItem.setLink(element.getElementsByClass("wrap").select("div").select("h3").select("a").attr("href")); mItem.setCover(element.getElementsByClass("thumbnail").attr("style")); mItem.setTitle(element.getElementsByClass("wrap").select("div").select("h3").select("a").text()); mItem.setDescription(element.getElementsByClass("wrap").select("div").select("div[class=preview]").text()); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); df.setTimeZone(TimeZone.getTimeZone("UTC")); String time =new SimpleDateFormat("yyyy/MM/dd HH:mm").format(df.parse(element.getElementsByClass("wrap").select("div").select("div[class=byline]").select("time").attr("datetime"))); mItem.setPubData(time); mItem.setCategory(element.getElementsByClass("wrap").select("div").select("div[class=byline]").select("span[class=topics]").text()); newsList.add(mItem); }
完整的工具类
package com.saltwater.animenews; import android.content.Context; import android.os.AsyncTask; import android.util.Log; import android.widget.ListView; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.net.URL; import java.net.URLConnection; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import java.util.TimeZone; /**解析Html * Created by xin on 2016/10/28. * @version 2.0 */ public class DataLoadTask extends AsyncTask<Void,Integer,List<NewsItem>> { private String mURL ="http://www.animenewsnetwork.com/news"; private NewsItem mItem=null; public static List<NewsItem> newsList=null; private ListView mListView=null; private Context mContext=null; public DataLoadTask(Context context, ListView lv) { this.mContext=context; this.mListView=lv; } /*预处理方法*/ @Override protected void onPreExecute() { super.onPreExecute(); } /*异步线程*/ @Override protected List<NewsItem> doInBackground(Void... params) { try { Log.d("out","start"); newsList=new ArrayList<NewsItem>(); Document doc = Jsoup.connect(mURL).get(); /*// 修改http包中的header,伪装成浏览器进行抓取 conn.header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:32.0) Gecko/ 20100101 Firefox/32.0");*/ /*解析Html,获取数据*/ Elements elements = doc.select("div[class=herald box news]"); for(Element element : elements) { mItem=new NewsItem(); mItem.setLink(element.getElementsByClass("wrap").select("div").select("h3").select("a").attr("href")); mItem.setCover(element.getElementsByClass("thumbnail").attr("style")); mItem.setTitle(element.getElementsByClass("wrap").select("div").select("h3").select("a").text()); mItem.setDescription(element.getElementsByClass("wrap").select("div").select("div[class=preview]").text()); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); df.setTimeZone(TimeZone.getTimeZone("UTC")); String time =new SimpleDateFormat("yyyy/MM/dd HH:mm").format(df.parse(element.getElementsByClass("wrap").select("div").select("div[class=byline]").select("time").attr("datetime"))); mItem.setPubData(time); mItem.setCategory(element.getElementsByClass("wrap").select("div").select("div[class=byline]").select("span[class=topics]").text()); newsList.add(mItem); } } catch (Exception e) { e.printStackTrace(); } finally { Log.d("out",newsList.size()+"item"); } return newsList; } /*绑定adapter*/ @Override protected void onPostExecute(final List<NewsItem> newsList) { super.onPostExecute(newsList); NewsListAdapter adapter=new NewsListAdapter(newsList,mContext); mListView.setAdapter(adapter); } }