Java实现爬虫给App提供数据(Jsoup 网络爬虫)

需求 ##

近期基于 Material Design 重构了自己的新闻 App,数据来源是个问题。

有前人分析了知乎日报、凤凰新闻等 API,依据相应的 URL 能够获取新闻的 JSON 数据。为了锻炼写代码能力,笔者打算爬虫新闻页面,自己获取数据构建 API。

本文链接 http://blog.csdn.net/never_cxb/article/details/50524571 转载请注明出处

效果图

下图是原站点的页面

blog.csdn.net/never_cxb

爬虫获取了数据,展示到 APP 手机端

blog.csdn.net/never_cxb

爬虫思路

Created with Raphaël 2.1.0開始基于Get请求获取URL对于的网页Html利用Jsoup把Html解析为Document利用Dom的getElementsById等方法获取标题、公布时间、内容等依据标题、公布时间、内容构建javabean给APP使用结束

关于 App 的实现过程能够參看这几篇文章,本文主要解说一下怎样爬虫数据。

Jsoup 简单介绍

Jsoup 是一个 Java 的开源HTML解析器,可直接解析某个URL地址、HTML文本内容。

Jsoup主要有以下功能:

- 从一个URL,文件或字符串中解析HTML。

- 使用DOM或CSS选择器来查找、取出数据;

- 对HTML元素、属性、文本进行操作;

- 清除不受信任的HTML (来防止XSS攻击)

到官网下载相应的Jsoup依赖包 http://jsoup.org/download

爬虫过程

Get 请求获取网页 HTML

新闻网页Html的DOM树例如以下所看到的:

http://blog.csdn.net/never_cxb

以下这段代码依据指定的 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。能够查看这篇文章 《抓取前端渲染的页面》 http://webmagic.io/docs/zh/posts/chx-cases/js-render-page.html

  • 所以分析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;">&nbsp;(通讯员</span><strong><span style="font-size:16px;line-height:1.5;"> 丁彤 王朱丹</span></strong><span style="font-size:16px;line-height:1.5;">...)

展望

本文解说了怎样实现Jsoup 网络爬虫。假设文章对您有帮助,感谢捐赠。

微信

近期用 Material Design 重构了自己的新闻 App,新闻数据是利用 Jsoup 实现的。

第1版爬虫是在手机端实现的(我承认这设计非常不好,既费流量又添加client负担),后来在新浪云上实现了一个简单的 JSP 。过滤了原网页的图片、一级栏目等,仅仅返回新闻标题、阅读次数、新闻内容等等。

本文链接 http://blog.csdn.net/never_cxb/article/details/50524571 转载请注明出处

后期的打算是把爬虫这步移到新浪云上,返回格式化的 JSON 数据给client使用。

可能的话,图片使用七牛CDN(Content Delivery Network 内容分发网络)。在云上利用 Mysql 数据库缓存新闻信息。

參考文章

posted on 2017-07-27 15:03  ljbguanli  阅读(1323)  评论(0编辑  收藏  举报