[微信公众号文章抓取.二]构建Anyproxy来获取数据

上回书说到,啊啊呸。上一篇文章已经确定了抓取的思路是通过微信app来访问公众号文章的历史页面。从中获得数据。那么这就需要一个网关来拦截并获取其中的数据,同时这个网关最好还是可以基于它进行二次开发的,这样我们就可以让它自动提取我们需要的数据用来进行下一步的处理。

Anyproxy是什么?

AnyProxy是一个开放式的HTTP代理服务器。

主要特性包括:

  • 基于Node.js,开放二次开发能力,允许自定义请求处理逻辑
  • 支持Https的解析
  • 提供GUI界面,用以观察请求

如何安装Anyproxy

我想Anyproxy的文档可以更换的帮助您了解这个伟大的项目:anyproxy.io/cn/#安装

运行一个Anyproxy实例

首先我们需要写一段代码来让Anyproxy运行起来

const AnyProxy = require('anyproxy');
const rule = require('./rule');
const options = {
    port: 8001,
    rule: rule,
    webInterface: {
        enable: true,
        webPort: 8002
    },
    throttle: 10000,
    forceProxyHttps: true,
    wsIntercept: true, // 不开启websocket代理
    silent: false
};
const proxyServer = new AnyProxy.ProxyServer(options);

proxyServer.on('ready', () => { console.log("proxyServer is ready") });
proxyServer.on('error', (e) => { console.log("proxyServer Error:") + e.toString() });
proxyServer.start();

//when finished
//proxyServer.close();

第一行引入了anyproxy,第二行则是一会儿要去写的规则文件。第三行设置了一些anyproxy的参数。第十五行和第十九行分布是创建和运行anyproxy,在它们之间的则是一些log。最好被注释的语句则是让anyproxy停止运行。这些应该很好理解吧。

设置代理

运行起来了Anyproxy我们就可以对手机抓包了,将手机-》设置-》WALN-》长按连接的wifi-》修改网络-》将代理设置为手动-》代理服务器主机名填写运行anyproxy服务器的ip,端口填入8001。这块需要注意的是手机和运行anyproxy服务器的机器应该在一个网络内。之后访问http://localhost:8002/应该可以看见手机的通信数据。

不过现在anyproxy还不能抓取微信的数据,因为微信是使用https进行通信的,所以我们还需要导入并信任anyproxy的证书,这步操作也可以看官方的文档:anyproxy.io/cn/#代理https

编写规则文件

经过以上几个步骤以后我们可以获取微信app的数据了。那么我们可以开始编写让Anyproxy按照我们的需求来处理获得的数据。

从anyproxy的文档中可以得知:AnyProxy向客户端发送请求前,会调用beforeSendResponse,并带上参数requestDetail responseDetail。所以代码如下

var axios = require('axios');
var config = require('./config');
var sync_request = require('sync-request');

function decoder(data, encode) {
    var replace = ["&#39;", "'", "&quot;", '"', "&nbsp;", " ", "&gt;", ">", "&lt;", "<", "&amp;", "&", "&amp;", "&", "&yen;", "¥"];
    if (encode) {
        replace.reverse();
    }
    for (var i = 0, str = data; i < replace.length; i += 2) {
        str = str.replace(new RegExp(replace[i], 'g'), replace[i + 1]);
    }
    return str;
}

module.exports = {
    summary: "抓取微信公众号历史记录",
    * beforeSendResponse(requestDetail, responseDetail) {
        if (/mp\/profile_ext\?action=home/i.test(requestDetail.url)) {//当链接地址为公众号历史消息页面
            console.log('[INFO] find url->html')
            try {
                var reg = /var msgList = (\"|\')(.*?)(\"|\');/i;//定义历史消息正则匹配规则(和第一种页面形式的正则不同)
                var ret = decoder(reg.exec(responseDetail.response.body.toString())[2], false);//转换变量为string
                var __biz = /__biz=(.*?)&/i.exec(requestDetail.url)[1];
                var next_offset = /var next_offset = (\"|\')(.*?)(\"|\')/i.exec(responseDetail.response.body.toString())[2];
                var can_msg_continue = /var can_msg_continue = (\"|\')(.*)(\"|\')/i.exec(responseDetail.response.body.toString())[2];
                var nickname = /var nickname = (\"|\')(.*?)(\"|\')/i.exec(responseDetail.response.body.toString())[2];
                var appmsg_token = /window.appmsg_token = (\"|\')(.*)(\"|\')/i.exec(responseDetail.response.body.toString())[2];
                console.log('[INFO]data->' + ret);
                // 提交json
                // ret是个list,使用ret[1]
                axios.post(config.api_url + '/api/post', {
                    type: 1,
                    data: ret,
                    __biz: __biz,
                    next_offset: next_offset,
                    can_msg_continue: can_msg_continue,
                    appmsg_token: appmsg_token,
                    nickname: nickname
                }, {
                    timeout: 0
                }).then().catch(err => {
                    console.log('Error', err.message);
                });
                var h = sync_request('GET', config.api_url + '/api/html').getBody().toString();
                // console.log('[h]->' + h);
                const newResponse = responseDetail.response;
                newResponse.body = h + newResponse.body;
                // console.log('[body]->' + newResponse.body);
                return new Promise((resolve, reject) => {
                    resolve({response: newResponse});
                });
            } catch (e) {
                // 错误log
                console.log('[ERROR]' + e.toString())
                return null;
            }
        } else if (/mp\/profile_ext\?action=getmsg/i.test(requestDetail.url)) {//上滑加载的内容
            console.log('[INFO] find url->json')
            try {
                // 提交json
                var json = JSON.parse(responseDetail.response.body.toString());
                if (json.general_msg_list != []) {
                    console.log('[INFO]data->' + json.general_msg_list);
                    __biz = /__biz=(.*?)&/i.exec(requestDetail.url)[1];
                    appmsg_token = /appmsg_token=(.*?)&/i.exec(requestDetail.url)[1];
                    axios.post(config.api_url + '/api/post', {
                        // TODO
                        type: 1,
                        data: json.general_msg_list,
                        __biz: __biz,
                        next_offset: json.next_offset,
                        can_msg_continue: json.can_msg_continue,
                        appmsg_token: appmsg_token
                    }, {
                        timeout: 0
                    });
                }
            } catch (e) {
                // 错误log
                console.log('[ERROR]' + e.toString())
                return null;
            }
        } else {
            return null
        }
    }
};

其中config是在下一章介绍的用来维护抓取队列和数据解析入库的的后端程序。decoder是从微信网页中获取的用来替换转义字符的函数。在beforeSendResponse中的if和else分别对应了微信公众号的历史html页面和后续加载的json数据。如果是html页面我们还需要注入一段js代码,这段代码也是从后端获取的(当然写在这里也是可以的),这段代码就是让整套程序能够自动运行的关键(卖个关子,逃。

代码

代码已经托管至Github:bafflingbug/wechat_cralwer 欢迎star

下期预告 抓取后端

 

相关日志

  1. 2018.03.22

    关于python中yield和协程的一点理解

    yield一般应用于生成循环中的那个变量,一…

  2. 2018.08.08

    [微信公众号文章抓取.一]探索各种办法的可行性

    最近在公司做了一个这方面的项目,总结一下。顺…

  3. 2018.03.20

    python-单向链表

    一个简单的单向链表类 (更多……

  4. 2018.09.14

    再用dict.keys()就是狗

    最近刷题用python比较多,毕竟list比…

  5. 2018.06.07

    python爬虫:提取页面时间

    def date_check(date_st…

  6. 2018.03.20

    使用正则表达式解析url

    #!/usr/bin/env python …

评论

还没有评论。

在此评论中不能使用 HTML 标签。