Python爬虫五:微信公众号爬虫(2018.9.8)

环境:Windows7 +Python3.6+Pycharm2017

目标:抓取微信公众号全部历史文章(文章名+url)保存到本地csv。

             --------其他案例: 京东爬虫链家爬虫美团爬虫   ----------

分析:关于微信公众号的爬取,网上搜索了一下,主要有几种方法:

一、搜狗微信公众平台 http://weixin.sogou.com/ ,有个问题就是这里抓的文章一个不能把公众号文章全部抓全,还有就是文章的地址好像不是永久地址。

二、公众号平台文章调用接口 https://mp.weixin.qq.com/ ,就是你自己要先申请一个公众号,编辑文章的时候有个引用文章,但是这种方法好像有次数限制。具体可以参考以下文章 链接。 一二两种我自己都没试过。

三、APP抓包,通过抓包分析请求参数,然后构造请求。

基本思路:

    抓包工具这里用的是Anyproxy,打开手机微信,进入公众号的历史文章页面,下拉会不断加载历史文章,每次10条。抓包发现这是一个get请求,url如下:

https://mp.weixin.qq.com/mp/profile_ext?action=getmsg&__biz=MzU2MzA2ODk3Nw==&f=json&offset=10&count=10&is_ok=1&scene=124&uin=777&key=777&pass_ticket=%2F%2FMLfYzV4VmptckJ%2BGC%2FEMbuBaYdCzplfP7pig2nHKORwHh%2FKcSp5ufJNIY4R6Y6&wxtoken=&appmsg_token=973_9bX%252FVHxGVbz3AvGoFZHVNBOf1ygPLteCjhz8aA~~&x5=1&f=json

url中主要有三个参数:_biz   公众号的id;offset  翻页参数,每次增加10;appmsg_token 这是一个加密参数,有一段有效期。我们如果要构造请求的话,主要的问题就是这个appmsg_token参数,显然去破解这个参数是怎么生成的难度比较大。本文采用的办法是利用手机微信app发送请求,然后用代理软件Anyproxy截获这个请求的appmsg_token,还有cookie,返回给我们的爬虫程序。

原理就是这么简单,你可以用一个抓包软件抓包,然后手动的将appmsg_token、cookie、headers信息加到你的爬虫代码中,就可以抓取了。爬太快的话会封ip,实际测试每次请求sleep1秒,一个appmsg_token可以把人民日报历史文章全部抓完。本文内容比较多,但是基本上都是为了实现上面步骤的自动化。

本文用到的工具有:Anyproxy抓包工具,adb(一个安卓调试工具,借此可用实现电脑控制手机操作,如点击,滑动等),aiohttp(其他web框架也可以)搭建一个简单的web服务,用以将抓包获取的参数传递给爬虫代码。

实现逻辑如下:手机连接Anyproxy代理,再通过数据线连接电脑(连接adb),打开微信公众号历史文章页面。开启aiohttp的web服务,启动爬虫代码。首先会去获取token和cookie,方法是用adb控制手机下拉加载更多的历史文章触发请求。然后Anyproxy捕获到该请求,将请求的appmsg_token、cookie参数通过post请求发送到aiohttp的web服务器上,然后爬虫代码通过get请求访问aiohttp的web服务器获取appmsg_token、cookie参数,然后开始爬取。

具体实现:

一、Anyproxy安装使用

Anyproxy是阿里开发的一款代理服务器,支持基于nodejs的二次开发。我们可以在中间对请求或者响应做一些修改,如中间人攻击。

安装:1、首先需要安装nodejs,去官网下载,点击安装就行。2、装好nodejs后在cmd窗口输入 npm install -g anyproxy 进行安装。我自己安装过程中遇到个错误,提示是缺少一个react库,我自己去网上下了一个,文件夹名称改为react,然后放到nodejs下面的lib文件夹中。我看网上也没提到这个问题,写下。安装如果遇到其他问题自行百度下应该都能解决。

 3、装好anyproxy后,如果我们要监听https请求,还需要安装证书。电脑端、手机端都需要安装。电脑端整数安装方式:在cmd窗口输入 anyproxy-ca  (中间没有空格),会生成一个证书,找到这个证书点击设置为受信任的证书。手机端证书下载,我用的是安卓机,苹果好像稍微有点不一样。先在cmd中输入命令 anyproxy 运行代理,然后再用浏览器打开 http://127.0.0.1:8002/ ,可以在web界面上能看到所有的请求信息。

 http://127.0.0.1:8002/ web监控界面如上图所示,点击左边的RootCA会弹出二维码,用手机扫描二维码即可下载证书并安装。我自己在安装的时候还要求输入手机密码,就是设置指纹时设置的密码。上图右上角的圆球点击后会出现一串地址,第一个就是后面手机设置代理时的地址。

安装好证书后,在cmd窗口输入命令运行anyproxy,几条常用命令如下:

anyproxy     #运行anyproxy,不监听https请求

anyproxy -i  #运行anyproxy,监听https请求

anyproxy -i --rule F:\XX\X\sample.js  
             #运行anyproxy,监听https请求,并执行sample.js中的代码(二次开发)

手机设置代理,手机和电脑连接一个wifi,不同手机设置可能不一样。我的是点进当前连接的wifi,代理设置--手动--然后输入主机名和端口,端口默认8001,主机名就是上面提到的那个地址。

 手机设置好代理后,点开微信公众号文章,就能在web页面观察到请求,点击请求可以查看详细信息,左边的Fliter可以做筛选。

以新京报为例,点击进入公众号历史消息,首页的10条消息是一个get请求,返回是一段html代码,如下图一。继续往下拉,加载更多后,我们发现加载更多的也是一个get请求,返回的是json数据。这两个get请求不一样,但是发现首页的10条消息也可以用加载更多的get请求获得,offset设置为0。

 

 

 正如上文所述,构造加载更多的get请求需要三个参数,_biz  公众号的id;offset  翻页参数;appmsg_token 这是一个加密参数,一段时间内有效。主要的我们就是需要appmsg_token参数和请求的cookie,这里就需要使用anyproxy的二次开发功能,当代理检测到app发送了加载更多的请求,获取这个请求的url,cookie,通过post请求发送到aiohttp搭建的web服务器上,然后爬虫代码再从服务器上获取这些参数。anyproxy监听和发post请求的代码如下,是基于nodejs,本人也不会js,都是百度依样画葫芦。

sample.js文件:


var http = require('http'); //引用http模块
var querystring = require('querystring');//引用querystring模块
//构造一个函数,用于发送post请求,发送的内容为contents
function post_data(contents) {
    var options = {
        host: '127.0.0.1',   //本地web服务器的地址端口
        port: '8080',
        path: '/',
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': contents.length }
    }

    var req = http.request(options, function (res) {
        res.setEncoding('utf8');
        res.on('data', function (data) {
            console.log("data:", data);
        });
    });
    req.write(contents);
    req.end;
}


//anyproxy固定格式
module.exports = {
  summary: 'a rule to get url and cookie', //一段代码功能介绍
    //在request发送前执行,下面的if通过正则匹配请求的url来找到加载更多的请求,然后获取url、cookie
  *beforeSendRequest(requestDetail) {
    if (/mp.weixin.qq.com\/mp\/profile_ext\?action=getmsg&__biz/i.test(requestDetail.url)) {
        console.log('***********抓取到目标**********')
        console.log(requestDetail.requestOptions['headers'])
        var contents=querystring.stringify({
             'url':requestDetail.url,
             'cookie':requestDetail.requestOptions['headers']['Cookie']});
        post_data(contents) //发送post请求

        return null
    }

  },
};

二、aiohttp搭建本地web服务

aiohttp是一个异步的框架,可以建web服务,也可以用来发请求。当然也可以用其他的web框架。

首先安装 : pip install aiohttp

路由系统定义两个函数,get和give,当有post请求过来时执行函数get,当有get请求过来时执行give函数。代码如下:

from aiohttp import web
import re
#接收anyproxy的post请求并处理
async def get(request):
    n=await request.post()
    url=n['url']
    cookie=n['cookie']
    # biz=re.search('biz=.{14}==',url).group()   #从url中提取出_biz
    token = re.search('appmsg_token.+json', url).group() #从url中提取出appmsg_token
    print('获取token:  ',token)
    print('获取cookie:  ',cookie)
    data={'token':token,'cookie':cookie}
    p_list.append(data)
    print('当前列表长度:',len(p_list))
    return web.Response(text='你好,收到信息')
#接收爬虫代码的get请求并返回appmsg_token和cookie
async def give(request):
    print('开始发送appmsg_token和cookie')
    d=p_list.pop()
    return web.Response(text=str(d))

p_list=[]  #用于存储appmsg_token和cookie
app=web.Application()
app.add_routes([web.post('/',get),web.get('/',give)])  #路由定义,有post请求访问127.0.0.1:8080,执行get函数,有get请求访问127.0.0.1:8080,执行give函数
web.run_app(app,host='127.0.0.1',port=8080) #定义web服务器运行的位置 端口

三、adb的安装使用

adb主要用来实现电脑对手机的控制,用代码自动完成手机微信公众号的操作。

adb安装可以参考 文章 ,直接下载 下面的文件,解压后有四个文件,添加到系统环境变量。cmd输入adb,出现一长串信息就安装ok了。

百度网盘链接: https://pan.baidu.com/s/1pMxVo0lLnSoAmUp9Xpgzng   密码: yh8q  

手机用数据线连接电脑,打开usb调试,可以在cmd中输入命令控制,常见命令如下

adb shell input swipe 100 200  800 200 100 
    #从点(100,200)滑动到(800,200)用时100ms(时间可以不加),前面是x坐标,后面是y,坐标圆点是屏幕左上角

adb shell input tap 557 1578  #点击屏幕上的点(557,1578)


用python实现:直接调用os库

import os

os.system('adb shell input swipe 100 200  800 200 100')
    
os.system('adb shell input tap 557 1578')

四、爬虫主程序

首先手机usb连好电脑,开启usb调试,开启anyproxy代理,手机设置好代理,开启web服务。手机点开人民日报历史消息页面,如下图一,运行爬虫主程序,利用adb命令 屏幕左边缘向右滑动返回公众号的基本信息页面,如下图二,然后再点击【查看历史消息】再次进入到图一历史消息页面,然后利用滑动操作触发加载更多。每次重新进入历史消息页面生成新的appmsg_token参数。程序运行过程手机需要保持亮屏。这里还利用一点就是一个公众号的appmsg_token也可以用以请求其他公众号,但是也不是全部都适用,有的也不行。

 爬虫程序代码如下,输入为公众号biz号,输出为公众号历史文章的名称和url。

import requests
import json
import csv
import time
import os

def get_token():
    os.system('adb shell input swipe 0 200  800 200')  #从屏幕左边缘右滑返回上一级
    time.sleep(0.5)
    os.system('adb shell input tap 557 1578')          #点击 【查看历史消息】 进入历史消息页面,坐标每个手机不一样
    time.sleep(2)
    os.system('adb shell input swipe 500 1800  500 300 100') #两次向上滑动触发加载更多
    os.system('adb shell input swipe 500 1800  500 300 100')
    time.sleep(0.1)
    url='http://127.0.0.1:8080'
    r=requests.get(url)   #向本地web服务器发送get请求获取appmsg_token和cookie
    d=eval(r.text)
    print('获取token:',d)
    # token = d['token']
    # cookie = d['cookie']
    return(d)

def crow(biz):
    MARK=0 #一个公众号文章是否抓完的标志位,1表示抓完
    data=get_token()
    token=data['token']
    cookie=data['cookie']
    n=0
    while MARK==0:
        time.sleep(0.5)
        pages = str(10 * n)
        url = 'https://mp.weixin.qq.com/mp/profile_ext?action=getmsg&__biz='+biz+'&f=json&offset=' +pages+ '&count=10&is_ok=1&scene=124&uin=777&key=777&pass_ticket=%2F%2FMLfYzV4VmptckJ%2BGC%2FEMbuBaYdCzplfP7pig2nHKORwHh%2FKcSp5ufJNIY4R6Y6&wxtoken=&'+token
        head = {'Host': 'mp.weixin.qq.com',
                'Connection': 'keep-alive',
                'X-Requested-With': 'XMLHttpRequest',
                'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; GN8002 Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 MQQBrowser/6.2 TBS/044205 Mobile Safari/537.36 MicroMessenger/6.6.3.1260(0x26060339) NetType/WIFI Language/zh_CN',
                'Accept': '*/*',
                'Referer': 'https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MjM5NzAwMzU0MA==&scene=124&devicetype=android-23&version=26060339&lang=zh_CN&nettype=WIFI&a8scene=3&pass_ticket=5IruuVAhjeQ22KmpbBJX10LUhvreqP%2F4zCi0%2FlKRAYVSGufFu4EDGCytd7oIWNkV&wx_header=1',
                'Accept-Encoding': 'gzip, deflate',
                'Accept-Language': 'zh-CN,en-US;q=0.8',
                'Q-UA2': 'QV=3&PL=ADR&PR=WX&PP=com.tencent.mm&PPVN=6.6.3&TBSVC=43603&CO=BK&COVC=044205&PB=GE&VE=GA&DE=PHONE&CHID=0&LCID=9422&MO= GN8002 &RL=1080*1920&OS=6.0&API=23',
                'Q-GUID': '37319016da4caf7783df5bf913b788cb',
                'Q-Auth': '31045b957cf33acf31e40be2f3e71c5217597676a9729f1b',
                'cookie': cookie
                }
        try:
            r = requests.get(url, headers=head,timeout=5)
            html = json.loads(r.text)
            if len(html)==9:  #判断下返回的json消息体是否是正常的消息体
                datas0 = html['general_msg_list']
                datas0 = json.loads(datas0)
                datas = datas0['list']
                l = len(datas)
                if l<10:
                    MARK=1
                end=time.time()-start
                print('*********************')
                print('Page:%d' % n, 'Num:%d' % l,'Time:%d'%end)
                n += 1
                for data in datas:
                    try:
                        url_1 = data['app_msg_ext_info']['content_url']
                        title_1 = data['app_msg_ext_info']['title']
                        print(title_1)
                        print(url_1)
                        dd = data['app_msg_ext_info']['multi_app_msg_item_list']
                        #保存到本地
                        with open('weixin.csv','a',newline='',encoding='gb18030')as f:
                            write=csv.writer(f)
                            if len(dd) > 0:
                                for d in dd:
                                    url_d = d['content_url']
                                    title_d = d['title']
                                    print(title_d)
                                    print(url_d)
                                    write.writerow([title_d,url_d])
                    except Exception as e:
                        print(e)
                        print(r.text)
            else:
                #如果访问失效重新获得token、cookie
                print('error')
                print(r.text)
                data = get_token()
                token = data['token']
                cookie = data['cookie']

        except:
            pass
    print('*******************',biz,'抓取完成***************')


if __name__=='__main__':
    start=time.time()
    biz_list=['MzIxNDEzNzI4Mg==','MzA5OTA0NDIyMQ==','MTgwNTE3Mjg2MA==','MzA3MDM5ODY4Ng==','MjM5MDMyMzg2MA==','MzA4MjQxNjQzMA==','MzU2MzA2ODk3Nw==','MTI0MDU3NDYwMQ=='] #局座召忠、占豪、冷兔、美闻参阅、十点读书、新华网、新京报、央视新闻
    for biz in biz_list:
        crow(biz)

五、结果与总结

总结一下,微信对访问次数是有限制的,首先是封ip,访问越快封的越快。其次对每个微信号每天的请求次数也是有限制的,每秒发一次请求,大概测试下来是1500次请求左右。达到后就进不了公众号历史页面(禁24h),微信其他功能不影响。不清楚请求速度如果降下来次数是不是能增加。1500次请求,每次10条消息,一个公众号一天一条消息,按五年算就是1800条消息,可以爬完8个这样的公众号。但是有些公众号比如人民日报【官媒】这种是可以一天发多条消息的,所以这种账号数据量会比较大一些。还有就是需要关注你爬取的公众号,不关注好像也有影响。比较尴尬的一点是本来利用anyproxy、adb是为了自动化抓取,持续的抓取。结果发现瓶颈其实在微信号每天的访问次数,好像一个appmsg_token+cookie就可以差不多抓完1000多次,还不如手动复制下参数好!!!

下图是抓取的一些数据,以及应该是人民日报 第一篇文章

水平有限,如有错误请指正。

--------其他案例: 京东爬虫链家爬虫美团爬虫   ----------

欢迎关注个人微信公众号,更多案例持续更新!

                    打赏博主 

猜你喜欢

转载自blog.csdn.net/xing851483876/article/details/82493412