05
2018
05

微信公众号数据采集历史页、文章阅读量点赞量

由于在微信之外无法访问到阅读量和点赞量还有历史页面,所以需要研究怎么在此之外来获取到数据。

最终实现接口:http://www.xiaoxiangzi.com/68.html

之前看过几篇前辈的文章,主要利用Anyproxy作为中间人代理来获取微信公众号的文章数据,开始用了一段时间,发现并不是很理想。

使用Anyproxy有几个不是很好的地方:

一,采集时间慢;

二、需要注入JS跳转页面,翻页;

三、不稳定。

后来我想出了另外一种方案,因为我是iOS开发,也有前后端开发经验,后面又学了iOS逆向,所以刚好有这个能力来实现这个方案。

方案简述

一、经过研究发现获取数据所需要的COOKIE里有几个关键参数,wap_sid2、appmsg_token、pass_ticket,而经过抓包发现,这几个COOKIE参数是首次访问页面的时候被setCookie的。而这两个参数是X-WECHAT-UIN与X-WECHAT-KEY。

二、我只需要通过逆向手段分析到X-WECHAT-UIN与X-WECHAT-KEY的来源就可以获得COOKIE。

三、业务流程:向服务器提交一个需要抓取的页面(HTTP请求开始),服务器告诉手机微信需要抓取的网页(SOCKET),手机微信开始去访问这个页面,同时它会自动构造X-WECHAT-UIN与X-WECHAT-KEY,此时我们拦截这个请求并告诉服务器(SOCKET),服务器通过X-WECHAT-UIN与X-WECHAT-KEY去访问需要抓取的页面,抓取成功之后告诉请求者(HTTP请求结束),并且我们可以把这个COOKIE保存,用于访问同一个公众号下的其他页面。

由于采集数据量过大会导致微信封号,所以我们可以支持很多安装了微信的手机跟服务器建立长连接,每次请求由服务器分发请求。

最终实现:

硬件:一台或N台已越狱iPhone手机、一台服务器

向服务器上架设的接口POST一个历史页或文章页,服务器返回历史数据或阅读量,获取到KEY再获取COOKIE,耗时2s内,另外可以将这个COOKIE保存用于下一次访问同一个公众号的请求,可以节省不少时间。

所需技术:iOS正逆向、PHP、NodeJS

iOS篇

使用Theos创建tweak,HOOK微信的方法

微信启动时,我们实例化一个MMWebViewController,并且后面都用这一个,同时我们与服务器建立长连接,当我们需要访问某个页面,就调用这个对象的goToURL方法,同时拦截加载X-WECHAT-UIN与X-WECHAT-KEY的方法,向服务器回传这个UIN和KEY。

NodeJS篇

搭建Socket服务器,监听两个端口,一个端口给手机微信连接,另外一个给PHP传URL参数。这里我只做了只有一个手机在线的情况,如果获取多个手机微信的KEY,那只需把ServerSocket设置成一个数组,同时判断哪一个客户端正处于闲置状态即可。

var net= require('net');

var HOST = '192.168.10.209';

var WEBPORT= 9001;

var APPPORT= 9002;

var ServerSocket;

var clientSockets= new Array();

net.createServer(function(sock) {

    console.log('CONNECTION: ' + sock.remoteAddress+ ':' + sock.remotePort);

    clientSockets.push({data:sock});

    // 为这个socket实例添加一个"data"事件处理函数

    sock.on('data',function(data) {

        //console.log('DATA' + sock.remoteAddress + ': ' + data);

        if(ServerSocket==null){// APP服务器不在线

var jsonString = '{"meta":{"code":0,"message":"解码服务器不在线"},"data":""}';

var httpContent = 'HTTP\/1.1 200\nContent-Type:application\/json\n\n'+jsonString;

    sock.write(Buffer.from(httpContent));

    sock.end();

        }else{

ServerSocket.write(sock.remoteAddress+'\n'+sock.remotePort+'\n'+data);

// 超时结束

setTimeout(function() {

// 如果还没有响应则超时处理

for (var i= 0; i< clientSockets.length; i++) {

var socket= clientSockets[i]['data']; 

if(socket.remoteAddress==sock.remoteAddress&&socket.remotePort==sock.remotePort){

var jsonString = '{"meta":{"code":0,"message":"请求超时"},"data":""}';

var httpContent = 'HTTP\/1.1 200\nContent-Type:application\/json\n\n'+jsonString;

    sock.write(Buffer.from(httpContent));

    sock.end();

        }

    }

},15000);

        }


    });

    // 为这个socket实例添加一个"close"事件处理函数

    sock.on('close',function(data) {

removeSocket(sock);

        console.log('CLOSED: ' +

            sock.remoteAddress+ ' ' + sock.remotePort);

    });

}).listen(WEBPORT,HOST);

// 与APP建立连接

net.createServer(function(sock) {

ServerSocket= sock;

console.log('iPhone is Online!');

    sock.on('data',function(data) {// APP向服务器回传数据

        var dataString= data+'';

        var arr= dataString.split('\n');

        var host= arr[0];

        var port= arr[1];

        var content= arr[2];

        for (var i= 0; i< clientSockets.length; i++) {

var socket= clientSockets[i]['data']; 

if(socket.remoteAddress==host&&socket.remotePort==port){

if(content=='close'){

var jsonString = '{"meta":{"code":0,"message":"非法请求"},"data":""}';

var httpContent = 'HTTP\/1.1 200\nContent-Type:application\/json\n\n'+jsonString;

socket.write(Buffer.from(httpContent));

socket.end();

break;

}

var jsonString = '{"meta":{"code":1,"message":"请求成功!"},"data":"'+content+'"}';

var httpContent = 'HTTP\/1.1 200\nContent-Type:application\/json\n\n'+jsonString;

socket.write(Buffer.from(httpContent));

socket.end();

break;

}

        }

    });

    sock.on('close',function(data) {

console.log('iPhone is OffLine!');

        ServerSocket= null;

    });

}).listen(APPPORT,HOST);

// 从连接池删除指定Socket连接

function removeSocket(sock){

for (var i= 0; i< clientSockets.length; i++) {

var socket= clientSockets[i]['data']; 

if(socket.remoteAddress==sock.remoteAddress&&socket.remotePort==sock.remotePort){

clientSockets.splice(i,1);

break;

        }

    }

}

console.log('Server is OPEN');

PHP篇

用户端POST一个URL到PHP,PHP向Node服务器以HTTP协议发送这个URL数据,Node服务器把这个数据转发给手机端口,这里我用的TP5,数据库设计如下。

CREATE TABLE `wc_log` (

  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,

  `original_url` varchar(512) DEFAULT NULL,

  `conversion_url` varchar(512) DEFAULT NULL,

  `wx_uin` varchar(512) DEFAULT NULL,

  `wx_key` varchar(512) DEFAULT NULL,

  `uid` varchar(128) DEFAULT NULL,

  `biz` varchar(512) DEFAULT NULL,

  `cookie` varchar(1024) DEFAULT NULL,

  `pass_ticket` varchar(128) DEFAULT NULL,

  `appmsg_token` varchar(256) DEFAULT NULL,

  `create_time` int(11) DEFAULT NULL,

  `update_time` int(11) DEFAULT NULL,

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

实际上大可将手机直接做成Web服务器,但是有一个问题就是外网无法访问到手机,所以用服务器做中转,同时可以分发请求。


« 上一篇 下一篇 »

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。