jsoup_解析任意网站,做任意网站客户端

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: jsoup是一个解析网页源码的开源库,他能按照给定的规则提取出一个网页中的任意元素,和其他网页解析库不同的是,他提取网页内容的方式和css、jquery的选择器非常相似。因此如果你懂得前端的知识,只需根据以下的代码样例就可以在3分钟之内学会jsoup的用法: 1 2 3 4 5             Document doc = Jsoup.

jsoup是一个解析网页源码的开源库,他能按照给定的规则提取出一个网页中的任意元素,和其他网页解析库不同的是,他提取网页内容的方式和css、jquery的选择器非常相似。因此如果你懂得前端的知识,只需根据以下的代码样例就可以在3分钟之内学会jsoup的用法:

1
2
3
4
5
             Document doc = Jsoup.connect(href).timeout(10000).get(); 
             Element masthead = doc.select( "div.archive-list" ).first();
             Elements titleElements = masthead.select( "div.archive-list-item h4 a" );    
             Elements summaryElements = masthead.select( "div.archive-list-item div.post-intro p" );
             Elements imgElements = masthead.select( "div.archive-list-item img" );

没错就是3分钟,我第一次接触jsoup完全没有看官方文档,直接在网上下载了jsoup的jar包,然后找了一段jsoup的例子程序就开始用他来解析网页了,最开始我想做一个cnbeta的客户端,非常顺利,从cnbeta网页上解析得到的数据就跟是cnbeta专门为我提供的一样。

不过需要明白的是使用jsoup开发客户端并不是一个客户端开发的首选,一般是针对那些没有为你提供客户端接口的网站,一个标准的客户端接口应该解析的数据形式是json或者xml,有些网站都提供了rss的功能,rss其实就是xml格式的,所以开发一个网站的客户端可以基于一个网站的rss数据。

那么为什么还要用jsoup呢,原因有两点:1、不是所有网站都有rss;2、有的网站rss功能比较全,能够覆盖网站的大部分内容,但有的网站rss很简单,基本就是摆设。

而使用jsoup,你在网站上能看到的任何东西都可以解析出来。

但是jsoup开发网站客户端其实有个弊端,因此给自己的网站做客户端绝对不会用jsoup,而是专门写接口。

那就是一旦网站改版,原来的解析规则就失效了,客户端上可能显示不出任何数据,而rss一般很难得改一次。

因此使用jsoup开发网站客户端最好针对那些版面比较固定的网站。

回到我们的话题,我们将针对jcodecraeer的《综合资讯》栏目的文章列表 (http://jcodecraeer.com/plus/list.php?tid=4)  做解析来得到文章列表并显示在一个ListView中。如果你学会了这点,解析一个网站就不成问题了,当然有些网站的解析要复杂一些,你必须先对这个网站做一些数据分析,提炼出一些数据模型。

提取数据模型

我们假设 http://jcodecraeer.com/plus/list.php?tid=4 就是一个网站(事实上它只是一个栏目),来看看我们能够提炼出什么数据模型。

 

这个网页上有导航栏,还有右边的一些相关文章之类的。但是我只关心文章列表,仔细观察文章列表,其实每一项都是固定的:标题、缩略图、摘要、发表时间、标签、作者、阅览数,这就是我们的数据模型,是一篇文章的模型。为了在ListView中显示文章列表,我们新建一个用于表示一篇文章的Article类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.jcodecraeer.newsapp;
 
public class Article {
     private String title;
     private String summary;
     private String url;
     private String imageUrl;
     private String postTime;
  
     public void setTitle(String title) {
         this .title = title;
     }
     
     public String getTitle() {
         return  title;
     }
     
     public void setSummary(String summary) {
         this .summary = summary;
     }
     
     public String getSummary() {
         return  summary;
     }
     
     public void setUrl(String url){
         this .url=url;
     }
     
     public String getUrl(){
         return  url;
     }    
     
     public void setImageUrl(String imageUrl){
         this .imageUrl = imageUrl;
     }
     
     public String getImageUrl(){
         return  imageUrl;
     }        
     
     public void setPostTime(String postTime){
         this .postTime = postTime;
     }
     
     public String getPostTime(){
         return  postTime;
     }    
     
}

接下来的任务就是解析网页

一个网页是由html标签组成的,要查看这些代码可以直接在网页的任何位置右键,在弹出的菜单中点击查看网页源代码。

网页的源码很多,有很多是我们不关心的,你的任务是迅速找到目标代码块,这篇文章的目的是要提取文章列表,所以我找到了文章列表相关的代码块(要熟练的找到需要一点点前端的知识):

QQ图片20141226204637.png

从上面的代码我可以肯定,文章列表位于

<div class="archive-list">

所包含的div中,而每一篇文章又是包含在

<div class="archive-list-item">

div中。因此我们先找出class="archive-list"的节点,确保后续的解析是文章相关的代码,然后再在这个节点之下解析每篇文章以及文章的数据项。

找出archive-list节点:

Jsoup连接网页:

1
Document doc = Jsoup.connect( "http://jcodecraeer.com/plus/list.php?tid=4" ).timeout(10000).get();

找出archive-list节点:

1
Element masthead = doc.select( "div.archive-list" ).first();

注意最后必须加上first()方法,不然得到的不是单个数据而是一组数据。select("div.archive-list")中select顾名思义是选择的意思,其中“div.archive-list”是css 选择器的写法,意思是class=“archive-list”的div,所以

doc.select("div.archive-list")的意思就是找出class=“archive-list”的所有div,doc.select("div.archive-list").first()的意思就是找出第一个class=“archive-list”的div。

获取每篇文章直接相关的html元素,比如与文章标题相关的直接元素为class="archive-list-item"的div 下面的h4 标签下面的超链接中,用Jsoup表示就是:

1
  Elements titleElements = masthead.select( "div.archive-list-item h4 a" );

上面获取了所有文章的标题相关的html元素(这里是个超链接),返回的结果是Elements类型,他是一个List类型,按照同样的道理我们再获取所有缩略图,所有摘要,所有发表时间,他们的顺序应该是一一对应的,然后根据其中任意一个的Elements数量来遍历,将这些数据一条一条的赋予上面我们定义的Article模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
             Document doc = Jsoup.connect(href).timeout(10000).get(); 
             Element masthead = doc.select( "div.archive-list" ).first();
             Elements titleElements = masthead.select( "div.archive-list-item h4 a" );    
             Elements summaryElements = masthead.select( "div.archive-list-item div.post-intro p" );
             Elements imgElements = masthead.select( "div.archive-list-item img" );   
             Elements postTimeElements = masthead.select( "div.archive-list-item div.post-intro span.date" );
             int count=titleElements.size();
             Log.i( "count" , "count = "  + count);        
             for (int i = 0;i < count;i++) {
                 Article article =  new  Article();
                 Element titleElement = titleElements.get(i);
                 Element summaryElement = summaryElements.get(i);
                 Element imgElement = imgElements.get(i);
                 String url = titleElement.attr( "href" ); 
                 url= "http://www.jcodecraeer.com/"  + url;
                        
                 String title = titleElement.text();
                 String summary = summaryElement.text();
                 String imgsrc = "http://www.jcodecraeer.com/"  + imgElement.attr( "src" );
                 article.setTitle(title);
                 article.setSummary(summary);
                 article.setImageUrl(imgsrc);
                 articleList.add(article);
             }

以标题为例,遍历的时候我们取出一篇文章的title节点:

1
  Element titleElement = titleElements.get(i);

然后用 String title = titleElement.text();获取标题的文字,以

1
2
  String url = titleElement.attr( "href" ); 
  url= "http://www.jcodecraeer.com/"  + url;

获取文章的超链接,注意html源码中这个超链接是相对路径,因此我们增加了网站的网址来组成完整路径。之所以可以使用

1
titleElement.attr( "href" );

是因为这是一个a标签,a标签是有href属性的。

好了,上面的这种方式在后来我发现是有问题的,因为文章标题的列表和缩略图的列表并不是一一对应的,有些文章可能没有缩略图,所以我们换一种方式,获得包含一篇文章所有信息的节点,然后针对每一个节点在for循环内部再解析出文章的每一个数据项。如果没有缩略图,该数据项为空就是了,这就不会有任何问题。

下面是经过改进后的代码,这是一个解析的过程因此我将方法命名为parseArticleList:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
     public ArrayList<Article>  parseArticleList(String href, final int page){
         ArrayList<Article> articleList =  new  ArrayList<Article>();
         try  {
             href = _MakeURL(href,  new  HashMap<String, Object>(){{
                 put( "PageNo" , page);
             }});
             Log.i( "url" , "url = "  + href);
             Document doc = Jsoup.connect(href).timeout(10000).get(); 
             Element masthead = doc.select( "div.archive-list" ).first();
             Elements articleElements =  masthead.select( "div.archive-list-item" );        
             for (int i = 0; i < articleElements.size(); i++) {
                 Article article =  new  Article();
                 Element articleElement = articleElements.get(i);
                 Element titleElement = articleElement.select( "h4 a" ).first();
                 Element summaryElement = articleElement.select( "div.post-intro p" ).first();
                 Element imgElement =  null ;
                 if (articleElement.select( "img" ).size() != 0){
                    imgElement = articleElement.select( "img" ).first();
                 }
                 Element timeElement = articleElement.select( ".date" ).first();
                 String url =  "http://www.jcodecraeer.com"  + titleElement.attr( "href" ); 
                 String title = titleElement.text();
                 String summary = summaryElement.text();
                 String imgsrc =  "" ;
                 if (imgElement !=  null ){
                     imgsrc  = "http://www.jcodecraeer.com"  + imgElement.attr( "src" );
                 }
               
                 String postTime = timeElement.text();
                 article.setTitle(title);
                 article.setSummary(summary);
                 article.setImageUrl(imgsrc);
                 article.setPostTime(postTime);
                 article.setUrl(url);
                 articleList.add(article);
             }
         catch  (Exception e) {
              e.printStackTrace();
         }
         
         return  articleList;
     }

parseArticleList()方法返回了 ArrayList<Article>的集合,有了它你应该知道如何在ListView中使用了吧。

 

分页问题

上面所讨论的仅仅是单个网页,在这个栏目下有很多页数据,因此我们还需要考虑页码的问题,如果还有更多的页,当ListView滑动到最底下自动加载下一页的数据。

实现自动加载下一页ListView我已经放在了文末给出的完整源码中,这里就不讨论了,这里要继续讨论的是如何处理好分页的问题。

首先我们要先分析这个网站文章列表的url地址,第一页的url地址为:http://jcodecraeer.com/plus/list.php?tid=4 第二页为http://jcodecraeer.com/plus/list.php?tid=4&TotalResult=454&PageNo=2,第三页为:

http://jcodecraeer.com/plus/list.php?tid=4&TotalResult=454&PageNo=3,显然,不同页之间url上的区别是仅仅PageNo参数。那么当我们加载更多页面的时候只需把请求的url的PageNo换一下就行了,然而问题是如何确定是第几页呢?

我们再看,发现每一页文章数为10,所以我们判断加载页数的依据是:

1
  int pageIndex = mArticleList.size() / 10 + 1;

还有一个问题,如何判断是否能够加载更多页,我们怎么确定第九页之后还有第10页呢?

1
2
3
4
5
if  (articleList.size() < 10) {
          //已经加载完了  
else  if  (articleList.size() == 10) {
       //还有更多页 
}

当然这个判断方法有缺陷,如果第9页刚刚有10条数据,而么有第10页,这种情况是可能的,但是这不是什么大问题。

  1. 这种几率很小  

  2. 即便这种情况存在,当加载10页的数据什么也没有,articleList = 0 小于10 ,进入第一个判断条件。也达到了目的。

 

图片的异步加载

图片的缩略图加载是需要异步的,你可以使用 universal image loader ,但在我们给出的demo中使用的是自己实现的一个ImageLoader。

 

运行界面

1419606579627661.png

 

在这篇文章中我们只是简单的完成了文章列表的展示,你还可以根据我所提供的方法实现更多的功能,除了Jsoup的使用之外,更重要的是分析一个网站,我已经采用这种方法把一个专门介绍日本爱情动作片的网站全部解析做成了客户端。事实证明这种方式是完全可行的。其实即便是网站改版,也可以很快写出新的解析规则,我们甚至可以将解析规则放到网络上,这样到网站改版的时候,就不需要变更客户端的代码使客户端照常正常运行。

 

Jsoup的应用远远不止于此,我们可以用Jsoup解析出整个网站,将网站的所有数据提炼出来从未到达采集网站的目的。

 

读完这篇文章你可能会产生一个疑问,为什么不直接把整个列表取出来放到webview中呢,其实这个问题就好比是使用html5还是原生app的问题。webview的效率和体验在短时间内难以超越原生应用。

 

代码下载:http://pan.baidu.com/s/1c0s5MiG 

 

转:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1226/2218.html

目录
相关文章
|
4月前
|
域名解析 网络协议 安全
在Linux中,想在命令行下访问某个网站,并且该网站域名还没有解析,如何做?
在Linux中,想在命令行下访问某个网站,并且该网站域名还没有解析,如何做?
|
18天前
|
监控 前端开发 安全
如何开发一个网站:全面解析与实战指南
在数字化时代,网站是企业和个人展示形象、传播信息的关键平台。本文提供从规划、设计、开发、上线到后期维护的全方位网站开发指南,涵盖明确目标、分析用户、设定功能需求、设计风格、技术选型、测试部署及优化升级等内容,帮助你打造既美观又实用的网站。
27 4
|
2月前
|
监控 网络协议 应用服务中间件
深入解析:如何确定网站使用的端口号
【10月更文挑战第21天】 在网络通信中,端口号是识别特定服务的关键。一个IP地址可以有多个端口,每个端口可能运行着不同的服务。当我们在浏览器地址栏输入一个网址时,实际上是通过特定的端口与服务器进行通信。本文将探讨如何确定一个网站使用的端口号,以及端口号在网络通信中的作用。
102 4
|
2月前
|
数据采集 XML 前端开发
Jsoup在Java中:解析京东网站数据
Jsoup在Java中:解析京东网站数据
|
2月前
|
域名解析 缓存 网络协议
Windows系统云服务器自定义域名解析导致网站无法访问怎么解决?
Windows系统云服务器自定义域名解析导致网站无法访问怎么解决?
|
2月前
|
网络安全 Docker 容器
【Bug修复】秒杀服务器异常,轻松恢复网站访问--从防火墙到Docker服务的全面解析
【Bug修复】秒杀服务器异常,轻松恢复网站访问--从防火墙到Docker服务的全面解析
32 0
|
4月前
|
开发者 Python
深入解析Python `httpx`源码,探索现代HTTP客户端的秘密!
深入解析Python `httpx`源码,探索现代HTTP客户端的秘密!
100 1
|
4月前
|
API C# 开发框架
WPF与Web服务集成大揭秘:手把手教你调用RESTful API,客户端与服务器端优劣对比全解析!
【8月更文挑战第31天】在现代软件开发中,WPF 和 Web 服务各具特色。WPF 以其出色的界面展示能力受到欢迎,而 Web 服务则凭借跨平台和易维护性在互联网应用中占有一席之地。本文探讨了 WPF 如何通过 HttpClient 类调用 RESTful API,并展示了基于 ASP.NET Core 的 Web 服务如何实现同样的功能。通过对比分析,揭示了两者各自的优缺点:WPF 客户端直接处理数据,减轻服务器负担,但需处理网络异常;Web 服务则能利用服务器端功能如缓存和权限验证,但可能增加服务器负载。希望本文能帮助开发者根据具体需求选择合适的技术方案。
191 0
|
4月前
|
缓存 网络协议 Linux
在Linux中,当用户在浏览器当中输入⼀个网站,计算机对dns解释经过那些流程?
在Linux中,当用户在浏览器当中输入⼀个网站,计算机对dns解释经过那些流程?
|
5月前
|
编解码 JSON 文字识别
印刷文字识别使用问题之进行表格解析时年份和灰色部分没解析出来,网站体验能检测到,该如何优化
印刷文字识别产品,通常称为OCR(Optical Character Recognition)技术,是一种将图像中的印刷或手写文字转换为机器编码文本的过程。这项技术广泛应用于多个行业和场景中,显著提升文档处理、信息提取和数据录入的效率。以下是印刷文字识别产品的一些典型使用合集。

推荐镜像

更多