查看: 882|回复: 0

[Java学习] 详细Java批量获取微信公众号方法

发表于 2018-1-5 08:00:00

最近需要爬取微信公众号的文章信息。在网上找了找发现微信公众号爬取的难点在于公众号文章链接在pc端是打不开的,要用微信的自带浏览器(拿到微信客户端补充的参数,才可以在其它平台打开),这就给爬虫程序造成很大困扰。后来在知乎上看到了一位大牛用php写的微信公众号爬取程序,就直接按大佬的思路整了整搞成java的了。改造途中遇到蛮多细节问题,拿出来分享一下。

系统的基本思路是在安卓模拟器上运行微信,模拟器设置代理,通过代理服务器拦截微信数据,将得到的数据发送给自己的程序进行处理。

需要准备的环境:nodejs,anyproxy代理,安卓模拟器

nodejs下载地址:http://nodejs.cn/download/,我下载的是windows版的,下好直接安装就行。安装好后,直接运行C:\Program Files\nodejs\npm.cmd 会自动配置好环境。

anyproxy安装:按上一步安装好nodejs之后,直接在cmd运行 npm install -g anyproxy 就会安装了

安卓模拟器随便在网上下一个就好了,一大堆。

首先为代理服务器安装证书,anyproxy默认不解析https链接,安装证书后就可以解析了,在cmd执行anyproxy --root 就会安装证书,之后还得在模拟器也下载这个证书。

然后输入anyproxy -i 命令 打开代理服务。(记得加上参数!)

记住这个ip和端口,之后安卓模拟器的代理就用这个。现在用浏览器打开网页:http://localhost:8002/ 这是anyproxy的网页界面,用于显示http传输数据。

点击上面红框框里面的菜单,会出一个二维码,用安卓模拟器扫码识别,模拟器(手机)就会下载证书了,安装上就好了。

现在准备为模拟器设置代理,代理方式设置为手动,代理ip为运行anyproxy机器的ip,端口是8001

到这里准备工作基本完成,在模拟器上打开微信随便打开一个公众号的文章,就能从你刚打开的web界面中看到anyproxy抓取到的数据:

上面红框内就是微信文章的链接,点击进去可以看到具体的数据。如果response body里面什么都没有可能证书安装有问题。

如果上面都走通了,就可以接着往下走了。

这里我们靠代理服务抓微信数据,但总不能抓取一条数据就自己操作一下微信,那样还不如直接人工复制。所以我们需要微信客户端自己跳转页面。这时就可以使用anyproxy拦截微信服务器返回的数据,往里面注入页面跳转代码,再把加工的数据返回给模拟器实现微信客户端自动跳转。

打开anyproxy中的一个叫rule_default.js的js文件,windows下该文件在:C:\Users\Administrator\AppData\Roaming\npm\node_modules\anyproxy\lib

在文件里面有个叫replaceServerResDataAsync: function(req,res,serverResData,callback)的方法,这个方法就是负责对anyproxy拿到的数据进行各种操作。一开始应该只有callback(serverResData);这条语句的意思是直接返回服务器响应数据给客户端。直接删掉这条语句,替换成大牛写的如下代码。这里的代码我并没有做什么改动,里面的注释也解释的给非常清楚,直接按逻辑看懂就行,问题不大。

  1. replaceServerResDataAsync: function(req,res,serverResData,callback){
  2. if(/mp\/getmasssendmsg/i.test(req.url)){//当链接地址为公众号历史消息页面时(第一种页面形式)
  3. //console.log("开始第一种页面爬取");
  4. if(serverResData.toString() !== ""){
  5. 6 try {//防止报错退出程序
  6. var reg = /msgList = (.*?);/;//定义历史消息正则匹配规则
  7. var ret = reg.exec(serverResData.toString());//转换变量为string
  8. HttpPost(ret[1],req.url,"/InternetSpider/getData/showBiz");//这个函数是后文定义的,将匹配到的历史消息json发送到自己的服务器
  9. var http = require('http');
  10. http.get('http://xxx/getWxHis', function(res) {//这个地址是自己服务器上的一个程序,目的是为了获取到下一个链接地址,将地址放在一个js脚本中,将页面自动跳转到下一页。后文将介绍getWxHis.php的原理。
  11. res.on('data', function(chunk){
  12. callback(chunk+serverResData);//将返回的代码插入到历史消息页面中,并返回显示出来
  13. })
  14. });
  15. }catch(e){//如果上面的正则没有匹配到,那么这个页面内容可能是公众号历史消息页面向下翻动的第二页,因为历史消息第一页是html格式的,第二页就是json格式的。
  16. //console.log("开始第一种页面爬取向下翻形式");
  17. try {
  18. var json = JSON.parse(serverResData.toString());
  19. if (json.general_msg_list != []) {
  20. HttpPost(json.general_msg_list,req.url,"/xxx/showBiz");//这个函数和上面的一样是后文定义的,将第二页历史消息的json发送到自己的服务器
  21. }
  22. }catch(e){
  23. console.log(e);//错误捕捉
  24. }
  25. callback(serverResData);//直接返回第二页json内容
  26. }
  27. }
  28. //console.log("开始第一种页面爬取 结束");
  29. }else if(/mp\/profile_ext\?action=home/i.test(req.url)){//当链接地址为公众号历史消息页面时(第二种页面形式)
  30. try {
  31. var reg = /var msgList = \'(.*?)\';/;//定义历史消息正则匹配规则(和第一种页面形式的正则不同)
  32. var ret = reg.exec(serverResData.toString());//转换变量为string
  33. HttpPost(ret[1],req.url,"/xxx/showBiz");//这个函数是后文定义的,将匹配到的历史消息json发送到自己的服务器
  34. var http = require('http');
  35. http.get('xxx/getWxHis', function(res) {//这个地址是自己服务器上的一个程序,目的是为了获取到下一个链接地址,将地址放在一个js脚本中,将页面自动跳转到下一页。后文将介绍getWxHis.php的原理。
  36. res.on('data', function(chunk){
  37. callback(chunk+serverResData);//将返回的代码插入到历史消息页面中,并返回显示出来
  38. })
  39. });
  40. }catch(e){
  41. //console.log(e);
  42. callback(serverResData);
  43. }
  44. }else if(/mp\/profile_ext\?action=getmsg/i.test(req.url)){//第二种页面表现形式的向下翻页后的json
  45. try {
  46. var json = JSON.parse(serverResData.toString());
  47. if (json.general_msg_list != []) {
  48. HttpPost(json.general_msg_list,req.url,"/xxx/showBiz");//这个函数和上面的一样是后文定义的,将第二页历史消息的json发送到自己的服务器
  49. }
  50. }catch(e){
  51. console.log(e);
  52. }
  53. callback(serverResData);
  54. }else if(/mp\/getappmsgext/i.test(req.url)){//当链接地址为公众号文章阅读量和点赞量时
  55. try {
  56. HttpPost(serverResData,req.url,"/xxx/getMsgExt");//函数是后文定义的,功能是将文章阅读量点赞量的json发送到服务器
  57. }catch(e){
  58. }
  59. callback(serverResData);
  60. }else if(/s\?__biz/i.test(req.url) || /mp\/rumor/i.test(req.url)){//当链接地址为公众号文章时(rumor这个地址是公众号文章被辟谣了)
  61. try {
  62. var http = require('http');
  63. http.get('http://xxx/getWxPost', function(res) {//这个地址是自己服务器上的另一个程序,目的是为了获取到下一个链接地址,将地址放在一个js脚本中,将页面自动跳转到下一页。后文将介绍getWxPost.php的原理。
  64. res.on('data', function(chunk){
  65. callback(chunk+serverResData);
  66. })
  67. });
  68. }catch(e){
  69. callback(serverResData);
  70. }
  71. }else{
  72. callback(serverResData);
  73. }
  74. //callback(serverResData);
  75. },
复制代码

这里简单解释一下,微信公众号的历史消息页链接有两种形式:一种以 mp.weixin.qq.com/mp/getmasssendmsg 开头,另一种是 mp.weixin.qq.com/mp/profile_ext 开头。历史页是可以向下翻的,如果向下翻将触发js事件发送请求得到json数据(下一页内容)。还有公众号文章链接,以及文章的阅读量和点赞量的链接(返回的是json数据),这几种链接的形式是固定的可以通过逻辑判断来区分。这里有个问题就是历史页如果需要全部爬取到该怎么做到。我的思路是通过js去模拟鼠标向下滑动,从而触发提交加载下一部分列表的请求。或者直接利用anyproxy分析下滑加载的请求,直接向微信服务器发生这个请求。但都有一个问题就是如何判断已经没有余下数据了。我是爬取最新数据,暂时没这个需求,可能以后要。如果有需求的可以尝试一下。

下图是上文中的HttpPost方法内容。

  1. function HttpPost(str,url,path) {//将json发送到服务器,str为json内容,url为历史消息页面地址,path是接收程序的路径和文件名<br /> console.log("开始执行转发操作");<br /> try{<br /> var http = require('http');<br /> var data = {<br /> str: encodeURIComponent(str),<br /> url: encodeURIComponent(url)<br /> };<br /> data = require('querystring').stringify(data);<br /> var options = {<br /> method: "POST",<br /> host: "xxx",//注意没有http://,这是服务器的域名。<br /> port: xxx,<br /> path: path,//接收程序的路径和文件名<br /> headers: {<br /> 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',<br /> "Content-Length": data.length<br /> }<br /> };<br /> var req = http.request(options, function (res) {<br /> res.setEncoding('utf8');<br /> res.on('data', function (chunk) {<br /> console.log('BODY: ' + chunk);<br /> });<br /> });<br /> req.on('error', function (e) {<br /> console.log('problem with request: ' + e.message);<br /> });<br /> <br /> req.write(data);<br /> req.end();<br /> }catch(e){<br /> console.log("错误信息:"+e);<br /> }<br /> console.log("转发操作结束");<br /> }
复制代码

做完以上工作,接下来就是按自己业务来完成服务端代码了,我们的服务用于接收代理服务器发过来的数据进行处理,进行持久化操作,同时向代理服务器发送需要注入到微信的js代码。针对代理服务器拦截到的几种不同链接发来的数据,我们就需要设计相应的方法来处理这些数据。从anyproxy处理微信数据的js方法replaceServerResDataAsync: function(req,res,serverResData,callback)中,我们可以知道至少需要对公众号历史页数据、公众号文章页数据、公众号文章点赞量和阅读量数据设计三种方法来处理。同时我们还需要设计一个方法来生成爬取任务,完成公众号的轮寻爬取。如果需要爬取更多数据,可以从anyproxy抓取到的链接中分析出更多需要的数据,然后往replaceServerResDataAsync: function(req,res,serverResData,callback)中添加判定,拦截到需要的数据发送到自己的服务器,相应的在服务端添加方法处理该类数据就行了。

我是用java写的服务端代码。

处理公众号历史页数据方法:

  1. public void getMsgJson(String str ,String url) throws UnsupportedEncodingException {
  2. // TODO Auto-generated method stub
  3. String biz = "";
  4. Map<String,String> queryStrs = HttpUrlParser.parseUrl(url);
  5. if(queryStrs != null){
  6. biz = queryStrs.get("__biz");
  7. biz = biz + "==";
  8. }
  9. /**
  10. * 从数据库中查询biz是否已经存在,如果不存在则插入,
  11. * 这代表着我们新添加了一个采集目标公众号。
  12. */
  13. List<WeiXin> results = weiXinMapper.selectByBiz(biz);
  14. if(results == null || results.size() == 0){
  15. WeiXin weiXin = new WeiXin();
  16. weiXin.setBiz(biz);
  17. weiXin.setCollect(System.currentTimeMillis());
  18. weiXinMapper.insert(weiXin);
  19. }
  20. //System.out.println(str);
  21. //解析str变量
  22. List<Object> lists = JsonPath.read(str, "['list']");
  23. for(Object list : lists){
  24. Object json = list;
  25. int type = JsonPath.read(json, "['comm_msg_info']['type']");
  26. if(type == 49){//type=49表示是图文消息
  27. String content_url = JsonPath.read(json, "$.app_msg_ext_info.content_url");
  28. content_url = content_url.replace("\\", "").replaceAll("amp;", "");//获得图文消息的链接地址
  29. int is_multi = JsonPath.read(json, "$.app_msg_ext_info.is_multi");//是否是多图文消息
  30. Integer datetime = JsonPath.read(json, "$.comm_msg_info.datetime");//图文消息发送时间
  31. /**
  32. * 在这里将图文消息链接地址插入到采集队列库tmplist中
  33. * (队列库将在后文介绍,主要目的是建立一个批量采集队列,
  34. * 另一个程序将根据队列安排下一个采集的公众号或者文章内容)
  35. */
  36. try{
  37. if(content_url != null && !"".equals(content_url)){
  38. TmpList tmpList = new TmpList();
  39. tmpList.setContentUrl(content_url);
  40. tmpListMapper.insertSelective(tmpList);
  41. }
  42. }catch(Exception e){
  43. System.out.println("队列已存在,不插入!");
  44. }
  45. /**
  46. * 在这里根据$content_url从数据库post中判断一下是否重复
  47. */
  48. List<Post> postList = postMapper.selectByContentUrl(content_url);
  49. boolean contentUrlExist = false;
  50. if(postList != null && postList.size() != 0){
  51. contentUrlExist = true;
  52. }
  53. if(!contentUrlExist){//'数据库post中不存在相同的$content_url'
  54. Integer fileid = JsonPath.read(json, "$.app_msg_ext_info.fileid");//一个微信给的id
  55. String title = JsonPath.read(json, "$.app_msg_ext_info.title");//文章标题
  56. String title_encode = URLEncoder.encode(title, "utf-8");
  57. String digest = JsonPath.read(json, "$.app_msg_ext_info.digest");//文章摘要
  58. String source_url = JsonPath.read(json, "$.app_msg_ext_info.source_url");//阅读原文的链接
  59. source_url = source_url.replace("\\", "");
  60. String cover = JsonPath.read(json, "$.app_msg_ext_info.cover");//封面图片
  61. cover = cover.replace("\\", "");
  62. /**
  63. * 存入数据库
  64. */
  65. // System.out.println("头条标题:"+title);
  66. // System.out.println("微信ID:"+fileid);
  67. // System.out.println("文章摘要:"+digest);
  68. // System.out.println("阅读原文链接:"+source_url);
  69. // System.out.println("封面图片地址:"+cover);
  70. Post post = new Post();
  71. post.setBiz(biz);
  72. post.setTitle(title);
  73. post.setTitleEncode(title_encode);
  74. post.setFieldId(fileid);
  75. post.setDigest(digest);
  76. post.setSourceUrl(source_url);
  77. post.setCover(cover);
  78. post.setIsTop(1);//标记一下是头条内容
  79. post.setIsMulti(is_multi);
  80. post.setDatetime(datetime);
  81. post.setContentUrl(content_url);
  82. postMapper.insert(post);
  83. }
  84. if(is_multi == 1){//如果是多图文消息
  85. List<Object> multiLists = JsonPath.read(json, "['app_msg_ext_info']['multi_app_msg_item_list']");
  86. for(Object multiList : multiLists){
  87. Object multiJson = multiList;
  88. content_url = JsonPath.read(multiJson, "['content_url']").toString().replace("\\", "").replaceAll("amp;", "");//图文消息链接地址
  89. /**
  90. * 这里再次根据$content_url判断一下数据库中是否重复以免出错
  91. */
  92. contentUrlExist = false;
  93. List<Post> posts = postMapper.selectByContentUrl(content_url);
  94. if(posts != null && posts.size() != 0){
  95. contentUrlExist = true;
  96. }
  97. if(!contentUrlExist){//'数据库中不存在相同的$content_url'
  98. /**
  99. * 在这里将图文消息链接地址插入到采集队列库中
  100. * (队列库将在后文介绍,主要目的是建立一个批量采集队列,
  101. * 另一个程序将根据队列安排下一个采集的公众号或者文章内容)
  102. */
  103. if(content_url != null && !"".equals(content_url)){
  104. TmpList tmpListT = new TmpList();
  105. tmpListT.setContentUrl(content_url);
  106. tmpListMapper.insertSelective(tmpListT);
  107. }
  108. String title = JsonPath.read(multiJson, "$.title");
  109. String title_encode = URLEncoder.encode(title, "utf-8");
  110. Integer fileid = JsonPath.read(multiJson, "$.fileid");
  111. String digest = JsonPath.read(multiJson, "$.digest");
  112. String source_url = JsonPath.read(multiJson, "$.source_url");
  113. source_url = source_url.replace("\\", "");
  114. String cover = JsonPath.read(multiJson, "$.cover");
  115. cover = cover.replace("\\", "");
  116. // System.out.println("标题:"+title);
  117. // System.out.println("微信ID:"+fileid);
  118. // System.out.println("文章摘要:"+digest);
  119. // System.out.println("阅读原文链接:"+source_url);
  120. // System.out.println("封面图片地址:"+cover);
  121. Post post = new Post();
  122. post.setBiz(biz);
  123. post.setTitle(title);
  124. post.setTitleEncode(title_encode);
  125. post.setFieldId(fileid);
  126. post.setDigest(digest);
  127. post.setSourceUrl(source_url);
  128. post.setCover(cover);
  129. post.setIsTop(0);//标记一下不是头条内容
  130. post.setIsMulti(is_multi);
  131. post.setDatetime(datetime);
  132. post.setContentUrl(content_url);
  133. postMapper.insert(post);
  134. }
  135. }
  136. }
  137. }
  138. }
  139. }
复制代码

处理公众号文章页的方法:

  1. public String getWxPost() {
  2. // TODO Auto-generated method stub
  3. /**
  4. * 当前页面为公众号文章页面时,读取这个程序
  5. * 首先删除采集队列表中load=1的行
  6. * 然后从队列表中按照“order by id asc”选择多行(注意这一行和上面的程序不一样)
  7. */
  8. tmpListMapper.deleteByLoad(1);
  9. List<TmpList> queues = tmpListMapper.selectMany(5);
  10. String url = "";
  11. if(queues != null && queues.size() != 0 && queues.size() > 1){
  12. TmpList queue = queues.get(0);
  13. url = queue.getContentUrl();
  14. queue.setIsload(1);
  15. int result = tmpListMapper.updateByPrimaryKey(queue);
  16. System.out.println("update result:"+result);
  17. }else{
  18. System.out.println("getpost queues is null?"+queues==null?null:queues.size());
  19. WeiXin weiXin = weiXinMapper.selectOne();
  20. String biz = weiXin.getBiz();
  21. if((Math.random()>0.5?1:0) == 1){
  22. url = "http://mp.weixin.qq.com/mp/getmasssendmsg?__biz=" + biz +
  23. "#wechat_webview_type=1&wechat_redirect";//拼接公众号历史消息url地址(第一种页面形式)
  24. }else{
  25. url = "https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=" + biz +
  26. "#wechat_redirect";//拼接公众号历史消息url地址(第二种页面形式)
  27. }
  28. url = "https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=" + biz +
  29. "#wechat_redirect";//拼接公众号历史消息url地址(第二种页面形式)
  30. //更新刚才提到的公众号表中的采集时间time字段为当前时间戳。
  31. weiXin.setCollect(System.currentTimeMillis());
  32. int result = weiXinMapper.updateByPrimaryKey(weiXin);
  33. System.out.println("getPost weiXin updateResult:"+result);
  34. }
  35. int randomTime = new Random().nextInt(3) + 3;
  36. String jsCode = "<script>setTimeout(function(){window.location.href='"+url+"';},"+randomTime*1000+");</script>";
  37. return jsCode;
  38. }
复制代码

处理公众号点赞量和阅读量的方法:

  1. public void getMsgExt(String str,String url) {
  2. // TODO Auto-generated method stub
  3. String biz = "";
  4. String sn = "";
  5. Map<String,String> queryStrs = HttpUrlParser.parseUrl(url);
  6. if(queryStrs != null){
  7. biz = queryStrs.get("__biz");
  8. biz = biz + "==";
  9. sn = queryStrs.get("sn");
  10. sn = "%" + sn + "%";
  11. }
  12. /**
  13. * $sql = "select * from `文章表` where `biz`='".$biz."'
  14. * and `content_url` like '%".$sn."%'" limit 0,1;
  15. * 根据biz和sn找到对应的文章
  16. */
  17. Post post = postMapper.selectByBizAndSn(biz, sn);
  18. if(post == null){
  19. System.out.println("biz:"+biz);
  20. System.out.println("sn:"+sn);
  21. tmpListMapper.deleteByLoad(1);
  22. return;
  23. }
  24. // System.out.println("json数据:"+str);
  25. Integer read_num;
  26. Integer like_num;
  27. try{
  28. read_num = JsonPath.read(str, "['appmsgstat']['read_num']");//阅读量
  29. like_num = JsonPath.read(str, "['appmsgstat']['like_num']");//点赞量
  30. }catch(Exception e){
  31. read_num = 123;//阅读量
  32. like_num = 321;//点赞量
  33. System.out.println("read_num:"+read_num);
  34. System.out.println("like_num:"+like_num);
  35. System.out.println(e.getMessage());
  36. }
  37. /**
  38. * 在这里同样根据sn在采集队列表中删除对应的文章,代表这篇文章可以移出采集队列了
  39. * $sql = "delete from `队列表` where `content_url` like '%".$sn."%'"
  40. */
  41. tmpListMapper.deleteBySn(sn);
  42. //然后将阅读量和点赞量更新到文章表中。
  43. post.setReadnum(read_num);
  44. post.setLikenum(like_num);
  45. postMapper.updateByPrimaryKey(post);
  46. }
复制代码

处理跳转向微信注入js的方法:

  1. public String getWxHis() {
  2. String url = "";
  3. // TODO Auto-generated method stub
  4. /**
  5. * 当前页面为公众号历史消息时,读取这个程序
  6. * 在采集队列表中有一个load字段,当值等于1时代表正在被读取
  7. * 首先删除采集队列表中load=1的行
  8. * 然后从队列表中任意select一行
  9. */
  10. tmpListMapper.deleteByLoad(1);
  11. TmpList queue = tmpListMapper.selectRandomOne();
  12. System.out.println("queue is null?"+queue);
  13. if(queue == null){//队列表为空
  14. /**
  15. * 队列表如果空了,就从存储公众号biz的表中取得一个biz,
  16. * 这里我在公众号表中设置了一个采集时间的time字段,按照正序排列之后,
  17. * 就得到时间戳最小的一个公众号记录,并取得它的biz
  18. */
  19. WeiXin weiXin = weiXinMapper.selectOne();
  20. String biz = weiXin.getBiz();
  21. url = "https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=" + biz +
  22. "#wechat_redirect";//拼接公众号历史消息url地址(第二种页面形式)
  23. //更新刚才提到的公众号表中的采集时间time字段为当前时间戳。
  24. weiXin.setCollect(System.currentTimeMillis());
  25. int result = weiXinMapper.updateByPrimaryKey(weiXin);
  26. System.out.println("getHis weiXin updateResult:"+result);
  27. }else{
  28. //取得当前这一行的content_url字段
  29. url = queue.getContentUrl();
  30. //将load字段update为1
  31. tmpListMapper.updateByContentUrl(url);
  32. }
  33. //将下一个将要跳转的$url变成js脚本,由anyproxy注入到微信页面中。
  34. //echo "<script>setTimeout(function(){window.location.href='".$url."';},2000);</script>";
  35. int randomTime = new Random().nextInt(3) + 3;
  36. String jsCode = "<script>setTimeout(function(){window.location.href='"+url+"';},"+randomTime*1000+");</script>";
  37. return jsCode;
  38. }
复制代码

以上就是对处理代理服务器拦截到的数据进行处理的程序。这里有一个需要注意的问题,程序会对数据库中的每个收录的公众号进行轮循访问,甚至是已经存储的文章也会再次访问,目的是为了一直更新文章的阅读数和点赞数。如果需要抓取大量的公众号建议对添加任务队列的代码进行修改,添加条件限制,否则公众号一多 轮循抓取重复数据将十分影响效率。

至此就将微信公众号的文章链接全部爬取到,而且这个链接是永久有效而且可以在浏览器打开的链接,接下来就是写爬虫程序从数据库中拿链接爬取文章内容等信息了。

我是用webmagic写的爬虫,轻量好用。

  1. public class SpiderModel implements PageProcessor{
  2. private static PostMapper postMapper;
  3. private static List<Post> posts;
  4. // 抓取网站的相关配置,包括编码、抓取间隔、重试次数等
  5. private Site site = Site.me().setRetryTimes(3).setSleepTime(100);
  6. public Site getSite() {
  7. // TODO Auto-generated method stub
  8. return this.site;
  9. }
  10. public void process(Page page) {
  11. // TODO Auto-generated method stub
  12. Post post = posts.remove(0);
  13. String content = page.getHtml().xpath("//div[@id='js_content']").get();
  14. //存在和谐文章 此处做判定如果有直接删除记录或设置表示位表示文章被和谐
  15. if(content == null){
  16. System.out.println("文章已和谐!");
  17. //postMapper.deleteByPrimaryKey(post.getId());
  18. return;
  19. }
  20. String contentSnap = content.replaceAll("data-src", "src").replaceAll("preview.html", "player.html");//快照
  21. String contentTxt = HtmlToWord.stripHtml(content);//纯文本内容
  22. Selectable metaContent = page.getHtml().xpath("//div[@id='meta_content']");
  23. String pubTime = null;
  24. String wxname = null;
  25. String author = null;
  26. if(metaContent != null){
  27. pubTime = metaContent.xpath("//em[@id='post-date']").get();
  28. if(pubTime != null){
  29. pubTime = HtmlToWord.stripHtml(pubTime);//文章发布时间
  30. }
  31. wxname = metaContent.xpath("//a[@id='post-user']").get();
  32. if(wxname != null){
  33. wxname = HtmlToWord.stripHtml(wxname);//公众号名称
  34. }
  35. author = metaContent.xpath("//em[@class='rich_media_meta rich_media_meta_text' and @id!='post-date']").get();
  36. if(author != null){
  37. author = HtmlToWord.stripHtml(author);//文章作者
  38. }
  39. }
  40. // System.out.println("发布时间:"+pubTime);
  41. // System.out.println("公众号名称:"+wxname);
  42. // System.out.println("文章作者:"+author);
  43. String title = post.getTitle().replaceAll(" ", "");//文章标题
  44. String digest = post.getDigest();//文章摘要
  45. int likeNum = post.getLikenum();//文章点赞数
  46. int readNum = post.getReadnum();//文章阅读数
  47. String contentUrl = post.getContentUrl();//文章链接
  48. WechatInfoBean wechatBean = new WechatInfoBean();
  49. wechatBean.setTitle(title);
  50. wechatBean.setContent(contentTxt);//纯文本内容
  51. wechatBean.setSourceCode(contentSnap);//快照
  52. wechatBean.setLikeCount(likeNum);
  53. wechatBean.setViewCount(readNum);
  54. wechatBean.setAbstractText(digest);//摘要
  55. wechatBean.setUrl(contentUrl);
  56. wechatBean.setPublishTime(pubTime);
  57. wechatBean.setSiteName(wxname);//站点名称 公众号名称
  58. wechatBean.setAuthor(author);
  59. wechatBean.setMediaType("微信公众号");//来源媒体类型
  60. WechatStorage.saveWechatInfo(wechatBean);
  61. //标示文章已经被爬取
  62. post.setIsSpider(1);
  63. postMapper.updateByPrimaryKey(post);
  64. }
  65. public static void startSpider(List<Post> inposts,PostMapper myPostMapper,String... urls){
  66. long startTime, endTime;
  67. startTime = System.currentTimeMillis();
  68. postMapper = myPostMapper;
  69. posts = inposts;
  70. HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
  71. SpiderModel spiderModel = new SpiderModel();
  72. Spider mySpider = Spider.create(spiderModel).addUrl(urls);
  73. mySpider.setDownloader(httpClientDownloader);
  74. try {
  75. SpiderMonitor.instance().register(mySpider);
  76. mySpider.thread(1).run();
  77. } catch (JMException e) {
  78. e.printStackTrace();
  79. }
  80. endTime = System.currentTimeMillis();
  81. System.out.println("爬取时间" + ((endTime - startTime) / 1000) + "秒--");
  82. }
  83. }
复制代码

其它的一些无关逻辑的存储数据代码就不贴了,这里我把代理服务器抓取到的数据存在了mysql,把自己的爬虫程序爬到的数据存储在了mongodb。

下面是自己爬取到的公众号号的信息:



回复

使用道具 举报