要求:抓取博客的粉丝数、关注数、圆龄、文章数、阅读数、评论数、积分、排名、推荐数、反对数等数据。
首先,来看下标准的博客界面,博客首页含有昵称、圆龄、粉丝、关注以及随笔、文章、评论等数据。
右键页面,审查元素,可以看到以下HTML代码。
1 <div id="blog-news">
2 <div id="profile_block">
3 昵称:<a href="http://home.cnblogs.com/u/giiku/">Giiku</a><br>
4 园龄:<a href="http://home.cnblogs.com/u/giiku/" title="入园时间:2015-07-18">1天</a><br>
5 粉丝:<a href="http://home.cnblogs.com/u/giiku/followers/">0</a><br>
6 关注:<a href="http://home.cnblogs.com/u/giiku/followees/">0</a>
7 <div id="p_b_follow"></div>
8 </div>
9 </div>
那么既然可以从HTML里面看到页面的数据,那么是否我们只要获取该页面的HTML文本,就可以抓取这些数据呢?
通过URL,先读取一下http://www.cnblogs.com/giiku/的html文本。
//打开URL
URL myURL = new URL(url);
URLConnection conn = myURL.openConnection();
//获取输入流
InputStream in = (InputStream) conn.getContent();
BufferedReader buf = new BufferedReader(new InputStreamReader(in,
"UTF-8"));
String line = "";
StringBuilder html = new StringBuilder();
//获取html文本
while ((line = buf.readLine()) != null) {
html.append(line);
}
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8"/>
<title>Giiku - 博客园</title>
<link type="text/css" rel="stylesheet" href="/bundles/blog-common.css?v=VDh8zSH1vx51MDqRT7hK220akQ58FjlaaeGuWBPhfOA1"/>
<link id="MainCss" type="text/css" rel="stylesheet" href="/skins/Custom/bundle-Custom.css?v=Z683z9azGWin6jOfOyAHK6FgPVxm_nmCnl3EFztc2eE1"/>
<link title="RSS" type="application/rss+xml" rel="alternate" href="http://www.cnblogs.com/giiku/rss"/>
<link title="RSD" type="application/rsd+xml" rel="EditURI" href="http://www.cnblogs.com/giiku/rsd.xml"/>
<link type="application/wlwmanifest+xml" rel="wlwmanifest" href="http://www.cnblogs.com/giiku/wlwmanifest.xml"/>
<script src="http://common.cnblogs.com/script/jquery.js" type="text/javascript"></script>
<script type="text/javascript">var currentBlogApp = 'giiku', cb_enable_mathjax=false;</script>
<script src="/bundles/blog-common.js?v=FPlxjK7DBkhdjUge-xvpcctYZfiyO32cepQZO-j3WJk1" type="text/javascript"></script>
</head>
<body>
<a name="top"></a>
<!--done-->
<div id="home">
<div id="header">
<div id="blogTitle">
<a id="lnkBlogLogo" href="http://www.cnblogs.com/giiku/"><img id="blogLogo" src="/Skins/custom/images/logo.gif" alt="返回主页" /></a>
<!--done-->
<h1><a id="Header1_HeaderTitle" class="headermaintitle" href="http://www.cnblogs.com/giiku/">Giiku</a></h1>
<h2></h2>
</div><!--end: blogTitle 博客的标题和副标题 -->
<div id="navigator">
<ul id="navList">
<li><a id="MyLinks1_HomeLink" class="menu" href="http://www.cnblogs.com/">博客园</a></li>
<li><a id="MyLinks1_MyHomeLink" class="menu" href="http://www.cnblogs.com/giiku/">首页</a></li>
<li><a id="MyLinks1_NewPostLink" class="menu" rel="nofollow" href="http://i.cnblogs.com/EditPosts.aspx?opt=1">新随笔</a></li>
<li><a id="MyLinks1_ContactLink" class="menu" rel="nofollow" href="http://msg.cnblogs.com/send/Giiku">联系</a></li>
<li><a id="MyLinks1_Syndication" class="menu" href="http://www.cnblogs.com/giiku/rss">订阅</a>
<!--<a id="MyLinks1_XMLLink" class="aHeaderXML" href="http://www.cnblogs.com/giiku/rss"><img src="http://www.cnblogs.com/images/xml.gif" alt="订阅" /></a>--></li>
<li><a id="MyLinks1_Admin" class="menu" rel="nofollow" href="http://i.cnblogs.com/">管理</a></li>
</ul>
<div class="blogStats">
<!--done-->
随笔- 0
文章- 0
评论- 0
</div><!--end: blogStats -->
</div><!--end: navigator 博客导航栏 -->
</div><!--end: header 头部 -->
<div id="main">
<div id="mainContent">
<div class="forFlow">
<!--done-->
</div><!--end: forFlow -->
</div><!--end: mainContent 主体内容容器-->
<div id="sideBar">
<div id="sideBarMain">
<!--done-->
<div class="newsItem">
<h3 class="catListTitle">公告</h3>
<div id="blog-news"></div><script type="text/javascript">loadBlogNews();</script>
</div>
<div id="blog-calendar" style="display:none"></div><script type="text/javascript">loadBlogDefaultCalendar();</script>
<div id="leftcontentcontainer">
<div id="blog-sidecolumn"></div><script type="text/javascript">loadBlogSideColumn();</script>
</div>
</div><!--end: sideBarMain -->
</div><!--end: sideBar 侧边栏容器 -->
<div class="clear"></div>
</div><!--end: main -->
<div class="clear"></div>
<div id="footer">
<!--done-->
Copyright ©2015 Giiku
</div><!--end: footer -->
</div><!--end: home 自定义的最大容器 -->
</body>
</html>
通过读取的html文本发现,访问该URL,服务器反馈给我们的信息,只有一部分数据,根本不存在圆龄、粉丝、关注等数据。原因在于这些数据是通过异步加载的方式读取的,所以只需要知道对应加载的URL链接,就可以获取这些数据。使用谷歌浏览器,右键审查元素,点击Network,就可以看到访问博客首页时,对资源的请求情况。
从上图,可以发现访问http://www.cnblogs.com/mvc/blog/news.aspx?blogApp=giiku该地址,服务器将会提供园龄、粉丝、关注等数据。现在分析下该URL,该URL是由"http://www.cnblogs.com/mvc/blog/news.aspx?blogApp="以及用户账号组合而成的。那么相当于只要我们知道用户的账号,我们就可以获取这部分数据。
假设已经获取到该页面的文本了,那么现在开始对文本数据进行处理,提取出需要的数据。
1 <div id="blog-news">
2 <div id="profile_block">
3 昵称:<a href="http://home.cnblogs.com/u/giiku/">Giiku</a><br>
4 园龄:<a href="http://home.cnblogs.com/u/giiku/" title="入园时间:2015-07-18">1天</a><br>
5 粉丝:<a href="http://home.cnblogs.com/u/giiku/followers/">0</a><br>
6 关注:<a href="http://home.cnblogs.com/u/giiku/followees/">0</a>
7 <div id="p_b_follow"></div>
8 </div>
9 </div>
先需要设置几个关键字,来定位下数据的大概位置以及判断是否存在该数据。这里选取“入园时间”、“followers”、“followees”作为关键字。
通过关键字定位到大概位置,再找到开口<a>的结束位置以及闭合</a>的位置,这样就可以获取到标签a的文本内容了。
public String getAContent(String html, String keyword){
int index = html.indexOf(keyword);
index = html.indexOf(">", index);
html = html.substring(index+1, html.indexOf("</a>", index));
return html;
}
现在我们来获取文章数、阅读数、评论数、积分、排名、推荐数、反对数等数据。
关于文章数,刚才上文在也看到了,博客首页是有显示文章数的,是可以直接抓取下来的。但是存在一个问题,由于大家的BLOG页面布局都不同,在你的BLOG里可能显示文章-0,而在他的BLOG里可能就Article-0。对于“文章”这一名词有很多的表示方法,所以如何确定关键字就成了一个问题。同时阅读数、评论数、推荐数及反对数需要我们到具体的文章里面去查看,然后进行总计。那么我们就必须要准备一个List来存放该博客所有的文章的链接,然后通过访问每一个链接,获取数据。这里我们新建一个实体类Article,存放文章的URL、文章的阅读数、评论数、推荐数、反对数。
现在来考虑下如何获取所有文章的链接?这里获取的方式有很多种,我来介绍一下,我发现的一种。
随便打开一个博客,进入首页,你会发现首页会陈列最新的几篇博文,如果博主博文比较多的话,提供了一个分页的机制。点击“下一页”,然后在第二页里就会显示该博主共有几页博文。现在看下该URL:http://www.cnblogs.com/huxiao-tee/default.html?page=2。page=2表示了现在查看的是第二页,现在改下数据,改成page=1。相当于回到了首页,也不显示共几页博文了。那么我们知道了,在博文的第二页,我们可以看到博主共有几页博文。如果该博主只有一页博文呢?我们令page=2会怎样的?通过实践观察发现,如果指定的page页数大于实际存在的页面,那么显示的页面就是博文的第一页。然后博文第一页是没有显示博主共几页的博文的。通过这一发现,我们可以访问每个博客第二页的URL,如果能获取到“共x页”的数据,那么就获得了所有文章的页数了。如果不能获取该数据,说明文章只有一页(甚至连一页也没有,当然也包括在一页的情况中)。
//获取页数
public int getPages(String html){
int index = html.indexOf(PAGES);
System.out.println(index);
if(index == -1){
return 1;//不一定是0 可能是1
}
int begin = html.indexOf("共", index);
int end = html.indexOf("页", begin);
System.out.println(html);
String str = html.substring(begin+1, end);
return Integer.parseInt(str);
}
现在知道了页数这个数据了该如何利用呢?首先这个URL:http://www.cnblogs.com/huxiao-tee/default.html?page=2的变量是页数,如果我们知道了总页数,就可以获取第一页到最后一页的URL的数据。通过这些URL,就可以抓取每一页文章的链接了,然后就可以获得所有文章的链接,相当于也知道了文章的数量。这里怎么抓取文章链接,具体做法跟上面差不多,根据关键字定位,获取相应的数据。
现在来讲下,如果获取文章的阅读数、评论数、推荐数、反对数的数据。
通过读取文章的URL,获取文本的html发现,这部分数据也是通过异步加载的。直接读取文章的URL,是获得不了这部分数据的。到谷歌浏览器的控制台里查看,发现
http://www.cnblogs.com/mvc/blog/ViewCountCommentCout.aspx?postId=4590118 返回了阅读数
http://www.cnblogs.com/mvc/blog/GetComments.aspx?postId=4590118&blogApp=huxiao-tee&pageIndex=0&anchorCommentId=0&_=1437286041334 返回了关于评论的json其中包括评论数
http://www.cnblogs.com/mvc/blog/BlogPostInfo.aspx?blogId=166017&postId=4590118&blogApp=huxiao-tee&blogUserGuid=63156ca7-762b-e311-8d02-90b11c0b17d6&_=1437286041383 返回了包含推荐数、反对数信息的页面
URL都是常量和变量组合而成的,变量相当于请求参数。我们只要知道这些请求参数,就可以获取这些数据。通过观察URL,我们需要postId(文章编号)、blogApp(用户名)、blogUserGuid(用户唯一标识)、blogId(博客编号)。第二个URL中包含pageIndex=0&anchorCommentId=0这两个参数,这里仅仅获取评论数的话,都默认为0就可以了。然后还有一个变量时没有名字,第二个URL中是_=1437286041334,第三个URL中是_=1437286041383,那么这个数据是什么呢?其实是一个long类型的时间,表示当前请求数据的时间 ,以便返回最新的数据。所以我们总共需要postId、blogApp、blogUserGuid 、time这4项数据。time的话,可以通过new Date()然后转换成long类型来获取。postId、blogApp、BlogUserGuid可以通过文章的URL中获取,其中blogApp、BlogUserGuid只需要获取一次,因为一个博客里面他们是唯一的。
任意一篇文章中都包含以下JS,其中包括了cb_blogId、cb_blogApp、cb_blogUserGuid、cb_entryId变量。通过发现比较就可以知道cb_entryId就是postId,文章的URL中其实也包含了postId。那么这些请求参数,我们都知道了。现在就可以通过编码,获取这些参数,然后再根据参数构建URL,到指定的页面中抓取数据。
<script type="text/javascript">var allowComments=true,isLogined=true,cb_blogId=166017,cb_entryId=4590118,cb_blogApp=currentBlogApp,cb_blogUserGuid='63156ca7-762b-e311-8d02-90b11c0b17d6',cb_entryCreatedDate='2015/6/20 4:04:00';loadViewCount(cb_entryId);</script>