您的位置:首页 > 移动开发 > Android开发

爬网易首页新闻头条

2015-10-20 19:27 471 查看
对于新闻APP,2012年之前采用的一般都是RSS订阅,这种模式,可以实现这样的功能,但是因为网易和新浪的RSS订阅基本都停止更新了,你根本获取不到最新的新闻消息,所以只有采用如下方法来提取新闻信息。

爬虫程序:可以抓取网页有用的任何信息,包括代码,甚至可以篡改网页信息,窃取用户资料。

下面我们来首先了解一下网易APP新闻的实体类,如下图:



1.分析网易新闻实体类

看上图可以知道,首页头条有四条滚动的图片新闻,滚动新闻有一张图片,一个标题,下面的新闻为一个图片,一个标题,一个描述。

综上所述,提取新闻实体类共同点,为一个标题,一张新闻图片,一个新闻简短描述,一个新闻具体的详细文章链接。

MessageItem代码如下:

public class MessageItem {
private byte[] imageRes;//新闻图片
private String title;//新闻标题
private String content;//新闻描述,当为四张首页图片时,这个用于保存四张首页图片网址
private String hrefSrc;//新闻链接

public String getHrefSrc() {
return hrefSrc;
}

public void setHrefSrc(String hrefSrc) {
this.hrefSrc = hrefSrc;
}

public byte[] getImageRes() {
return imageRes;
}

public void setImageRes(byte[] imageRes) {
this.imageRes = imageRes;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}
}

需要解释的一下是,图片用字节是因为SQLite存储的时候转换成BLOB类型很方便。

2.导入解析HTML工具包

目前国内用的最多的,性能最高,解析最好的HTML解析工具包为Jsoup,目前最新版本为1.8.3。下面详解导入AndroidStudio过程:

Ⅰ点击Project,如下图所示



Ⅱ在app目录下的src目录下面新建libs文件夹



Ⅲ点击你刚在创建文件夹下面复制的包,右键会出现Add as library...,这样就成功的导入的解析HTML网页的开发包。

3.获取网易首页信息

Ⅰ新建工具类ConnectNetwork

获取网页信息有两种方法,一种是HttpURLConnection,一种HTTPClient,具体用那种方式,自己可以自由选择,我们这里采用的是HTTPURLConnection方式。

代码如下:

public class ConnectNetwork {
/**
* 连接网络解析网址
* @param path 网址链接
* @return 网址byte[]数据
* @throws Exception
*/
public static byte[] getImageDat(String path) throws Exception {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//必须大写 设置响应头
conn.setRequestMethod("GET");
//设置延时
conn.setConnectTimeout(5000);
InputStream inStream = conn.getInputStream();
//字节数组输出流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
//封装
byte[] data = bos.toByteArray();
return data;
}
}

返回数据的方式为字节流,这里的代码可以说不是Android基础,而是java基础,如连这都不懂,我只能说孩子你还是把java学习好的了在来玩转Android把。

4.测试界面

为了达到测试我们获取到网易首页新闻的信息,我们需要一个Android界面进行测试,下面是主Activity的布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<ImageView
android:id="@+id/newsImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"/>

</LinearLayout>


5.解析网易首页新闻

开始解析网易新闻的时候,我们需要注意以下几个点:

Ⅰ网易编码,如下图所示,查看源码:



当你查看首页编码的时候你会发现如下所示的编码格式:



新闻列表的网页编码格式为GBK

当你点击某个新闻标题进去查看详细的新闻的内容的时候,会得到如下的编码格式:



新闻详细内容的编码格式为gb2312。之所以要注意这个,是因为解析的时候如果你不转换成相应的编码格式就会出现中文乱码。

Ⅱ注意手机获取的网易新闻,与电脑端看到的网易布局是不一样的。你也知道,只要是大型的网站,当不同的设备访问的时候,显示的是不同的格式,比如手机访问会自动适应手机的大小,以方便用户查看,每次你访问网站,手机下端是不是有个电脑版和手机版可以切换?所以,你在电脑开发时候解析的代码,有时候并不适用于手机的代码,所以对于爬虫程序,模仿网易APP,以手机网页显示为准。

不过经过我的测试,除了新闻的详细内容网易新闻在手机端获取的样式和电脑端获取的样式不同外,其他的其实都是一样的。所以,获取新闻条目手机端的代码与电脑端的代码是可以通用的。

Ⅲ获取的新闻文章就一定会有图片吗?答案是有的新闻文章并没有图片。你获取后,如果不加以判断,当设置图片的时候会报空指针异常。

下面我们来着手开始解析网易的前四个滚动新闻。

①查看滚动新闻网页源码



②创建我们的解析类LYJJsoupWangYiTUtils:

我们看到上面图片有一个习主席的新闻,前面有一个<div class="ns-gallery-mask"></div>,我们又发现这个div和习主席后面的img属于同意级别的标签。

在Jsoup中查找A元素之后的同级X元素,比如:div.ns-gallery-mask ~ img,这个意思是div的class是ns-gallery-mask的同级元素img

这样我们就获取了我们的图片,那么前面的链接我们怎么获取呢?

其实前面的链接就是ul的class是ns-gallery-con UI_GALLERY_CON下的li下的子a标签。这个好像貌似没有好的办法,只有一级一级的下去查找。代码如下:

Element ul= doc.select("ul.ns-gallery-con").select("ul.UI_GALLERY_CON").first();//class只要有空格就不能直接写在一个select里面,必须写在两个里面Jsoup才能识别。

全部的代码如下:

/**
* 加载头条四个循环图片
* @param doc HTML文档
* @return
* @throws Exception
*/
public static MessageItem[] jsoupFourImages(Document doc) throws Exception {
MessageItem[] messageItems = new MessageItem[4];//滚动4条新闻
Elements links= doc.select("div.ns-gallery-mask ~ img");//获取上面提到的div的同级元素img
Element ul= doc.select("ul.ns-gallery-con").select("ul.UI_GALLERY_CON").first();//获取上面的ul
Elements lis=ul.getElementsByTag("li");//获取所有ul里面li
int i=0;
for (Element li : lis) {//遍历li得到新闻链接
messageItems[i]=new MessageItem();
Element a=li.getElementsByTag("a").first();//获取li里面的a标签
String linkHref = a.attr("href");
messageItems[i].setHrefSrc(linkHref);
i++;
}
i=0;
for (Element link : links) {//遍历img
if(i<4){
String linkHref = link.attr("title");//获取新闻标题
String linkText = link.attr("src");//获取新闻图片网址
byte[] srcByte=ConnectNetwork.getImageDat(linkText);//得到图片字节流
messageItems[i].setTitle(linkHref);
messageItems[i].setImageRes(srcByte);
}else{
break;
}
i++;
}
return messageItems;//返回滚动四条新闻
}

上面需要解释的是,为什么循环四次就要退出,因为网易首页有两个一样div的标签8个,避免获取多余的,只有循环四次跳出。

③测试获取的头条新闻

对于几秒内的网络任务,我们使用AsyncTask异步加载就可以满足,如果超过10秒,是不建议使用AsyncTask异步加载的。特别注意。

网易首页滚动图片:









测试截图:









测试代码如下,代码可能不是很漂亮,甚至可能重复,但只为测试,简单掠过:

public class MainActivity extends Activity {
private TextView title;
private TextView content;
private ImageView newsImage;
private Button mybut;
private int count=0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.title=(TextView)findViewById(R.id.titlesss);
this.content=(TextView)findViewById(R.id.content);
this.newsImage=(ImageView)findViewById(R.id.newsImage);
this.mybut=(Button)findViewById(R.id.mybut);
this.mybut.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new NewsAsyncTask().execute();
}
});
}
private class NewsAsyncTask extends AsyncTask<Void,Integer,MessageItem[]>{
@Override
protected MessageItem[] doInBackground(Void... params) {
MessageItem[] messageItems=new MessageItem[4];
try {
byte[] htmlbyte= ConnectNetwork.getImageDat("http://news.163.com/");
String html = new String(htmlbyte,"GBK");
Document doc = Jsoup.parse(html);
messageItems= LYJJsoupWangYiTUtils.jsoupFourImages(doc);
} catch (Exception e) {
e.printStackTrace();
}
return messageItems;
}
@Override
protected void onPostExecute(MessageItem[] messageItems) {
if(count<4){
title.setText(messageItems[count].getTitle());
content.setText(messageItems[count].getHrefSrc());
ByteArrayInputStream bais=new ByteArrayInputStream(messageItems[count].getImageRes());
Bitmap bitmap=BitmapFactory.decodeStream(bais);
newsImage.setImageBitmap(bitmap);
count++;
}else{
count=0;
}
}
}
}

④获取网易四张循环新闻及头条八条新闻

如下图所示我们需要获取的是如下列表区1的八条新闻:



我们这里将列表区新闻当作头条新闻,是因为列表区新闻比头条多,容易扩展。你也可以获取头条区的新闻。这都无伤大雅。

代码如下:

/**
* 加载头条列表区的新闻,i每加1增加八条新闻,将括号8改成8*循环多少次i,当然你也可以直接增加参数,由你决定要多少新闻。
* @param doc
* @return
* @throws Exception
*/
public static MessageItem[] jsouplistTopLine(Document doc) throws Exception {
MessageItem[] messageItems = new MessageItem[8];//定义保存八条新闻的数组
Elements as=doc.select("div.ns-wnews").select("div.mb30");//同理获取div的class为ns-wnews mb30的div
for(int i=0;i<1;i++){//循环后逐层获取标签,具体代码不需要过多解释,于上相同。
Element ul=as.get(i).getElementsByTag("ul").get(0);
Elements li=ul.getElementsByTag("li");
for(int j=0;j<li.size();j++){
messageItems[j]=new MessageItem();
Element a=li.get(j).getElementsByTag("a").get(0);
String aHref=a.attr("href");
String aText=a.text();
messageItems[j].setTitle(aText);
messageItems[j].setHrefSrc(aHref);
jsoupNewsContentAndImage(messageItems[j],aHref);
}
}
return messageItems;
}


大家可能看到,我们截图中只有新闻的标题和网址链接,并没有图片和新闻文章的内容描述,下面我们还要获取内容简介和图片,代码如下:

/**
* 获取文章描述和文章图片
* @param messageItem
* @param aHref
* @throws Exception
*/
public static void jsoupNewsContentAndImage(MessageItem messageItem,String aHref) throws Exception {
String html = new String(ConnectNetwork.getImageDat(aHref),"UTF-8");
Document doc = Jsoup.parse(html);
Element p=doc.select("div.article-body > p").get(2);
String pText=p.text();
messageItem.setContent(pText);
int i=0;
Elements imgs=doc.select("p.f_center > img");
for (Element img : imgs) {
if(i<1){
String imgSrc=img.attr("src");
messageItem.setImageRes(ConnectNetwork.getImageDat(imgSrc));
}else{
break;
}
}
}


解释如下:

parent > child: 查找某个父元素下的直接子元素,比如:可以用
div.content > p
 查找 
p
 元素,也可以用
body > *
 查找body标签下所有直接子元素



看到上面的标签,有的人可能会问,文章是写在div.class为endText里面的,你的代码为什么是article-body呢?这是就是手机端的样式,于电脑端显示网页的样式的不同了,前面已经让大家注意的几点就有这个。一定要记住,不然得到的会是空,报空指针异常的。

⑤现在可以测试所有新闻条目了

当我们把新闻的条目获取变成一个静态方法,这样就可以直接得到我们所需要的所有新闻包括头四条新闻:

/**
* 获取网易四张循环新闻及头条八条新闻
* @param url 网易网址
* @return
* @throws Exception
*/
public static List<MessageItem> jsoupWangYiMessageItem(String url) throws Exception {
List<MessageItem> messageItemsList=new ArrayList<>();
byte[] htmlbyte=ConnectNetwork.getImageDat(url);
String html=new String(htmlbyte,"GBK");
Document doc = Jsoup.parse(html);
MessageItem[] fourImageItem=jsoupFourImages(doc);
MessageItem[] listNewsItem=jsouplistTopLine(doc);
for(int i=0;i<fourImageItem.length;i++){
messageItemsList.add(fourImageItem[i]);
}
for (int i=0;i<listNewsItem.length;i++){
messageItemsList.add(listNewsItem[i]);
}
return messageItemsList;
}


现在测试来看看我们的效果:





头四条新闻的详细内容网址在content里面,后面新闻的详细内容在hrefSrc里面,所以前面四张图片显示的是网址,这里显示的是新闻文章第一段内容。

测试代码不变,唯一变的就是异步加载类的内容:

private class NewsAsyncTask extends AsyncTask<Void,Integer,List<MessageItem>>{

@Override
protected List<MessageItem> doInBackground(Void... params) {
List<MessageItem> itemList=new ArrayList<>();
try {
itemList=LYJJsoupWangYiTUtils.jsoupWangYiMessageItem("http://news.163.com/");
} catch (Exception e) {
e.printStackTrace();
}
return itemList;
}

@Override
protected void onPostExecute(List<MessageItem> messageItems) {
if(count<12){
title.setText(messageItems.get(count).getTitle());
if(count>=4){
content.setText(messageItems.get(count).getContent());
}else{
content.setText(messageItems.get(count).getHrefSrc());
}
if(messageItems.get(count).getImageRes()!=null){//此处设置应该判断文章有不有图片
ByteArrayInputStream bais=new ByteArrayInputStream(messageItems.get(count).getImageRes());
Bitmap bitmap=BitmapFactory.decodeStream(bais);
newsImage.setImageBitmap(bitmap);
}else{
newsImage.setImageBitmap(null);
}
count++;
}else{
count=0;
}
}
}


⑥最后一步获取新闻的详细内容

我前面已经说过了,新闻的详细内容网址在手段端显示就是适合手机观看的内容,这样我们只需要获取刚才那个新闻详细内容div里面的所有代码,包括HTML代码,把他装进WebView中,也就可以显示详细的内容了。

代码如下:

/***
* 获取新闻详细文章
* @param url
* @return
* @throws Exception
*/
public static String jsoupNewsPage(String url) throws Exception {
String page=null;
byte[] htmlbyte=ConnectNetwork.getImageDat(url);
String html=new String(htmlbyte,"UTF-8");
Document doc = Jsoup.parse(html);
Element div=doc.select("div.article-body").get(0);
page = div.html().toString();
return page;
}


测试代码如下:

private class NewsAsyncTask extends AsyncTask<Void,Integer,String[]>{
@Override
protected String[] doInBackground(Void... params) {
List<MessageItem> itemList=new ArrayList<>();
String[] strWebs=new String[8];
try {
itemList=LYJJsoupWangYiTUtils.jsoupWangYiMessageItem("http://news.163.com/");
for (int i=4;i<itemList.size();i++){
strWebs[i]=LYJJsoupWangYiTUtils.jsoupNewsPage(itemList.get(i).getHrefSrc());
}
} catch (Exception e) {
e.printStackTrace();
}
return strWebs;
}

@Override
protected void onPostExecute(String[] strings) {
if(count<12){
myWebView.loadDataWithBaseURL(null,strings[count] , "text/html", "UTF-8", null);
count++;
}else{
count=4;
}
}
}


我加入了一个webView控件,在代码中没有给出,还请模仿的时候注意一下。

本文章的源代码在如下网址中,想爬网页的同学可以参考一下:
http://download.csdn.net/detail/liyuanjinglyj/9197619

jsoup用法参考网址:

http://blog.csdn.net/huangxy10/article/details/8185108
测试结果如下:



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息