本文章为lonter首创,只发布在csdn平台,严禁转载
这几天接到任务,需要开发一个微信榜单的功能,因此需要采集微信公众号文章的阅读数,点赞数和评论数,榜单内的微信公众号有一百多个,每个月出一次榜单。
接到这个任务,我开始研究如何抓取微信阅读数,点赞数和评论数,通过大量参考网上的技术文章,最终确定了我所使用的方案:使用Fiddler进行采集
本文章为lonter首创,只发布在csdn平台,严禁转载
第一步:设置Fiddler
如图设置,此处为设置Fiddler支持https
本文章为lonter首创,只发布在csdn平台,严禁转载
第二步:设置Fiddler脚本
Fiddler工具打开后,选择Rules ->Customize Rules打开Fiddler ScriptEditor编辑器,编辑器如下:
这里我们只需要了解OnBeforeResponse方法,本方法为在http请求返回给客户端之前执行的方法,我们主要在本方法内进行脚本的编写
本文章为lonter首创,只发布在csdn平台,严禁转载
第三步:选择性截取responsebody存储到文本中
研究各个请求,找到返回点赞数与评论的请求,具体请求如图:
然后开始在Fiddler ScriptEditor的方法中编写具体的存储脚本:
// 首先判断请求域名是否是自己感兴趣的,以及URL中是否含有自己感兴趣的特征字符串。如果是,则将该请求的URL和QueryString记录到日志文件 "c:/fiddler-token.log"中。 if (oSession.HostnameIs("mp.") && oSession.uriContains("https://mp./mp/getappmsgext")){ var filename = "C:/fiddler-token.log"; var curDate = new Date(); var logContent = "[" + curDate.toLocaleString() + "] " + oSession.PathAndQuery + "\r\n"+oSession.GetResponseBodyAsString()+"\r\n"; var sw : System.IO.StreamWriter; if (System.IO.File.Exists(filename)){ sw = System.IO.File.AppendText(filename); sw.Write(logContent); } else{ sw = System.IO.File.CreateText(filename); sw.Write(logContent); } sw.Close(); sw.Dispose(); }
本代码功能为将涉及阅读数点赞数的数据存储到文本中,结果如图:
本文章为lonter首创,只发布在csdn平台,严禁转载
第四步:篡改公众号文章页面的js代码,让页面自动按照你的意思跳转
由于本功能可能涉及灰色地带,so,声明一下,不要用来干坏事!!!
我们先来看看公众号文章的主页面:
很明显,每段js脚本都是以 script nonce=”1007993124” 开头,而nonce字段的作用是防止xxs的,如果js的nonce与原来的不符,js是不会被执行的,因此,需要在脚本编写上费点功夫,具体的逻辑代码如图:
这段js加载后,保存一下Fiddler ScriptEditor,然后你点击微信公众号文章,你回在Fiddler中看到如下:
然后,你回发现页面会自动的跳转
本文章为lonter首创,只发布在csdn平台,严禁转载
第五步:开发任务获取页面
我们需要开发一个微信的中转页面,这个页面会从后台获取一篇微信公众号文章,然后让微信浏览器打开
具体html如下:
<html><head><meta charset="utf-8"></head><body><script src="./js/hm.js"></script><script src="./js/jquery-3.2.1.min.js"></script><script language="javascript" > window.onload=function(){nextdoor();}function nextdoor(){var taskid=GetQueryString("taskid")var ob={task:taskid};$.ajax({type: "POST",url: "rest/wxCrawler/wxTask",contentType: "application/json; charset=utf-8",data: JSON.stringify(ob),dataType: "json",success: function (message) {var url=message.url;var taskid=message.task;//每个微信客户端的id,这个id应该在后端自动生成if(url==("http://127.0.0.1:8080/Externalservice/test.html?taskid="+taskid)){setTimeout(function(){window.location="http://127.0.0.1:8080/Externalservice/test.html?taskid="+taskid;},10000);}else{//alert(url+"&taskid="+taskid);window.location=url+"&taskid="+taskid+"#rd"; }},error: function (message) {alert("提交数据失败");}});}function GetQueryString(name){var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");var r = window.location.search.substr(1).match(reg);if(r!=null)return unescape(r[2]); return null;}</script> <div align="center"><p>阅读刷新中转页面,页面正在跳转中...</p><p>如一直刷新本页面,则一直等待后台分配任务</p></div></body></html>
至于后端接口,想必很多人都能写吧,我就只搞一部分出来:
package test.springmvc;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.http.MediaType;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.ResponseBody;import com.mangofactory.swagger.plugin.EnableSwagger;import com.wordnik.swagger.annotations.ApiOperation;import net.sf.json.JSONObject;import test.springmvc.Artmodel.WxTask;import test.springmvc.redis.JedisUtil;/*** * @author Administrator**/@Controller@EnableSwagger@RequestMapping("/wxCrawler")public class TopController {private final static Logger logger = LoggerFactory.getLogger(TopController.class);JedisUtil ju=new JedisUtil();@ApiOperation(value = "微信任务调度接口", notes = "notes", httpMethod = "POST", produces = MediaType.APPLICATION_JSON_VALUE)@RequestMapping(value = "wxTask", method = RequestMethod.POST)@ResponseBody// 使用了@RequestBody,不能在拦截器中,获得流中的数据,再json转换,拦截器中,也不清楚数据的类型,无法转换成java对象// 只能手动调用方法public String WeixinTask(@RequestBody WxTask wt) {String task=wt.getTask();byte[] redisKey= task.getBytes();//队列名称byte[] bys=ju.rpop(redisKey);if(bys==null){JSONObject json=new JSONObject();json.put("url", "http://127.0.0.1:8080/Externalservice/test.html?taskid="+task);json.put("task", task);return json.toString();}else{String info=new String(bys);JSONObject json=JSONObject.fromObject(info);String url=json.getString("url");url=url.replace("#rd", "");json.put("url", url);json.put("task", task);return json.toString();}}}
这段java和js的主要特点是可以多任务的进行分布式抓取,到这里,已经完成了所有的开发
你只需要写入几百几千的公众号文章链接,然后用微信打开http://localhost:8080/Externalservice/test.html?taskid=xxxxxl类似这样的中转页,你就会发现微信浏览器不停的跳转
本文章为lonter首创,只发布在csdn平台,严禁转载
第六步:解析Fiddler存储下来的文本
package com.crawler.top;import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.InputStreamReader;import java.util.ArrayList;import com.mysql.jdbc.UpdatableResultSet;import com.util.DBUtil;import net.sf.json.JSONObject;/*** 读取Fiddler写入的内容,并将结果写入数据库* @author Administrator**/public class ReaderTxt {DBUtil dbu=new DBUtil();public static void main(String[] args){ReaderTxt rt=new ReaderTxt();ArrayList<String> list=rt.InitTxt();for(int i=0;i<list.size();i+=2){String urlmsg=list.get(i);int start=urlmsg.indexOf("&sn=")+4;urlmsg=urlmsg.substring(start);int end=urlmsg.indexOf("&");urlmsg=urlmsg.substring(0, end);String info=list.get(i+1);JSONObject json=JSONObject.fromObject(info);if(json.has("appmsgstat")){JSONObject appmsgstat=json.getJSONObject("appmsgstat");int read_num=appmsgstat.getInt("read_num");int like_num=appmsgstat.getInt("like_num");rt.updata(urlmsg, read_num, like_num);}}rt.dbu.destroy();}/*** 更新数据库中的文章数据* @param urlmsg* @param read_num* @param like_num*/public void updata(String urlmsg,int read_num,int like_num){String sql="update task_info set readers="+read_num+", attitudes="+like_num+" where url like '%"+urlmsg+"%'";try {dbu.Update(sql);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}public ArrayList<String> InitTxt(){ArrayList<String> list=new ArrayList<String>();try { // 防止文件建立或读取失败,用catch捕捉错误并打印,也可以throw /* 读入TXT文件 */ String pathname = "C:\\fiddler-token.log"; // 绝对路径或相对路径都可以,这里是绝对路径,写入文件时演示相对路径 File filename = new File(pathname);InputStreamReader reader = new InputStreamReader( new FileInputStream(filename),"utf-8"); // 建立一个输入流对象reader BufferedReader br = new BufferedReader(reader); // 建立一个对象,它把文件内容转成计算机能读懂的语言 String line = ""; while (line != null) { line = br.readLine(); // 一次读入一行数据 if(line==null){ break;}list.add(line);} } catch (Exception e) { e.printStackTrace(); } return list;}}
这个挺简单的,嘿嘿
好啦,搞定啦
我的爬虫交流群:142351055