Java实现爬虫给App提供数据(Jsoup 网络爬虫)
一、需求
最近基于 material design 重构了自己的新闻 app,数据来源是个问题。
有前人分析了知乎日报、凤凰新闻等 api,根据相应的 url 可以获取新闻的 json 数据。为了锻炼写代码能力,笔者打算爬虫新闻页面,自己获取数据构建 api。
二、效果图
下图是原网站的页面
爬虫获取了数据,展示到 app 手机端
三、爬虫思路
关于app 的实现过程可以参看这几篇文章,本文主要讲解一下如何爬虫数据。
android下录制app操作生成gif动态图的全过程 :
学习android material design(recyclerview代替listview):
android项目实战之仿网易新闻的页面(recyclerview ):
jsoup 简介
jsoup 是一个 java 的开源html解析器,可直接解析某个url地址、html文本内容。
jsoup主要有以下功能:
- - 从一个url,文件或字符串中解析html;
- - 使用dom或css选择器来查找、取出数据;
- - 对html元素、属性、文本进行操作;
- - 清除不受信任的html (来防止xss攻击)
四、爬虫过程
get 请求获取网页 html
新闻网页html的dom树如下所示:
下面这段代码根据指定的 url,用代码获取get 请求返回的 html 源代码。
public static string doget(string urlstr) throws commonexception { url url; string html = ""; try { url = new url(urlstr); httpurlconnection connection = (httpurlconnection) url.openconnection(); connection.setrequestmethod("get"); connection.setconnecttimeout(5000); connection.setdoinput(true); connection.setdooutput(true); if (connection.getresponsecode() == 200) { inputstream in = connection.getinputstream(); html = streamtool.intostringbybyte(in); } else { throw new commonexception("新闻服务器返回值不为200"); } } catch (exception e) { e.printstacktrace(); throw new commonexception("get请求失败"); } return html; }
inputstream in = connection.getinputstream();将得到输入流转化为字符串是个普遍需求,我们将其抽象出来,写一个工具方法。
public class streamtool { public static string intostringbybyte(inputstream in) throws exception { bytearrayoutputstream outstr = new bytearrayoutputstream(); byte[] buffer = new byte[1024]; int len = 0; stringbuilder content = new stringbuilder(); while ((len = in.read(buffer)) != -1) { content.append(new string(buffer, 0, len, "utf-8")); } outstr.close(); return content.tostring(); } }
五、解析 html 获取标题
利用 google 浏览器的审查元素,找出新闻标题对于的html 代码:
<div id="article_title"> <h1> <a href="http://see.xidian.edu.cn/html/news/7428.html"> 关于举办《经典音乐作品欣赏与人文审美》讲座的通知 </a> </h1> </div>
我们需要从上面的 html 中找出id="article_title"的部分,使用 getelementbyid(string id) 方法
string htmlstr = httptool.doget(urlstr); // 将获取的网页 html 源代码转化为 document document doc = jsoup.parse(htmlstr); element articleele = doc.getelementbyid("article"); // 标题 element titleele = articleele.getelementbyid("article_title"); string titlestr = titleele.text();
六、获取发布日期、信息来源
同样找出对于的 html 代码
<html> <head></head> <body> <div id="article_detail"> <span> 2015-05-28 </span> <span> 来源: </span> <span> 浏览次数: <script language="javascript" src="http://see.xidian.edu.cn/index.php/news/click/id/7428"> </script> 477 </span> </div> </body> </html>
思路也和上面类似,使用 getelementbyid(string id) 方法找出id="article_detail"为element,再利用getelementsbytag获取span 部分。因为一共有3个<span> ... </span>,所以返回的是elements而不是element。
// article_detail包括了 2016-01-15 来源: 浏览次数:177 element detailele = articleele.getelementbyid("article_detail"); elements details = detailele.getelementsbytag("span"); // 发布时间 string datestr = details.get(0).text(); // 新闻来源 string sourcestr = details.get(1).text();
七、解析浏览次数
如果打印出上面的details.get(2).text(),只会得到
浏览次数:
没有浏览次数?为什么呢?
因为浏览次数是javascript 渲染出来的, jsoup爬虫可能仅仅提取html内容,得不到动态渲染出的数据。
解决方法有两种
- 在爬虫的时候,内置一个浏览器内核,执行js渲染页面后,再抓取。这方面对应的工具有selenium、htmlunit或者phantomjs。
- 所以分析js请求,找到对应数据的请求url
如果你访问上面的 urlhttp://see.xidian.edu.cn/index.php/news/click/id/7428,会得到下面的结果
document.write(478)
这个478就是我们需要的浏览次数,我们对上面的url做get 请求,得到返回的字符串,利用正则找出其中的数字。
// 访问这个新闻页面,浏览次数会+1,次数是 js 渲染的 string jsstr = httptool.doget(count_base_url + currentpage); int readtimes = integer.parseint(jsstr.replaceall("\\d+", "")); // 或者使用下面这个正则方法 // string readtimesstr = jsstr.replaceall("[^0-9]", "");
八、解析新闻内容
本来是获取新闻内容纯文字的形式,但后来发现 android 端也可以显示 css 格式,所以后来内容保留了 html 格式。
element contentele = articleele.getelementbyid("article_content"); // 新闻主体内容 string contentstr = contentele.tostring(); // 如果用 text()方法,新闻主体内容的 html 标签会丢失 // 为了在 android 上用 webview 显示 html,用tostring() // string contentstr = contentele.text();
九、解析图片 url
注意一个网页上大大小小的图片很多,为了只获取新闻正文中的内容,我们最好首先定位到新闻内容的element,然后再利用getelementsbytag(“img”)筛选出图片。
element contentele = articleele.getelementbyid("article_content"); // 新闻主体内容 string contentstr = contentele.tostring(); // 如果用 text()方法,新闻主体内容的 html 标签会丢失 // 为了在 android 上用 webview 显示 html,用tostring() // string contentstr = contentele.text(); elements images = contentele.getelementsbytag("img"); string[] imageurls = new string[images.size()]; for (int i = 0; i < imageurls.length; i++) { imageurls[i] = images.get(i).attr("src"); }
十、新闻实体类 javabean
上面获取了新闻的标题、发布日期、阅读次数、新闻内容等等,我们自然需要构造一个 javabean,把获取的内容封装进实体类中。
public class articleitem { private int index; private string[] imageurls; private string title; private string publishdate; private string source; private int readtimes; private string body; public articleitem(int index, string[] imageurls, string title, string publishdate, string source, int readtimes, string body) { this.index = index; this.imageurls = imageurls; this.title = title; this.publishdate = publishdate; this.source = source; this.readtimes = readtimes; this.body = body; } @override public string tostring() { return "articleitem [index=" + index + ",\n imageurls=" + arrays.tostring(imageurls) + ",\n title=" + title + ",\n publishdate=" + publishdate + ",\n source=" + source + ",\n readtimes=" + readtimes + ",\n body=" + body + "]"; } }
测试
public static articleitem getnewsitem(int currentpage) throws commonexception { // 根据后缀的数字,拼接新闻 url string urlstr = article_base_url + currentpage + ".html"; string htmlstr = httptool.doget(urlstr); document doc = jsoup.parse(htmlstr); element articleele = doc.getelementbyid("article"); // 标题 element titleele = articleele.getelementbyid("article_title"); string titlestr = titleele.text(); // article_detail包括了 2016-01-15 来源: 浏览次数:177 element detailele = articleele.getelementbyid("article_detail"); elements details = detailele.getelementsbytag("span"); // 发布时间 string datestr = details.get(0).text(); // 新闻来源 string sourcestr = details.get(1).text(); // 访问这个新闻页面,浏览次数会+1,次数是 js 渲染的 string jsstr = httptool.doget(count_base_url + currentpage); int readtimes = integer.parseint(jsstr.replaceall("\\d+", "")); // 或者使用下面这个正则方法 // string readtimesstr = jsstr.replaceall("[^0-9]", ""); element contentele = articleele.getelementbyid("article_content"); // 新闻主体内容 string contentstr = contentele.tostring(); // 如果用 text()方法,新闻主体内容的 html 标签会丢失 // 为了在 android 上用 webview 显示 html,用tostring() // string contentstr = contentele.text(); elements images = contentele.getelementsbytag("img"); string[] imageurls = new string[images.size()]; for (int i = 0; i < imageurls.length; i++) { imageurls[i] = images.get(i).attr("src"); } return new articleitem(currentpage, imageurls, titlestr, datestr, sourcestr, readtimes, contentstr); } public static void main(string[] args) throws commonexception { system.out.println(getnewsitem(7928)); }
输出信息
articleitem [index=7928, imageurls=[/uploads/image/20160114/20160114225911_34428.png], title=电院2014级开展“让诚信之花开遍冬日校园”教育活动, publishdate=2016-01-14, source=来源: 电影新闻网, readtimes=200, body=<div id="article_content"> <p style="text-indent:2em;" align="justify"> <strong><span style="font-size:16px;line-height:1.5;">西电新闻网讯</span></strong><span style="font-size:16px;line-height:1.5;"> (通讯员</span><strong><span style="font-size:16px;line-height:1.5;"> 丁彤 王朱丹</span></strong><span style="font-size:16px;line-height:1.5;">...)
本文讲解了如何实现jsoup 网络爬虫,如果文章对您有帮助,那就给个赞吧。
上一篇: Yii2表单事件之Ajax提交实现方法