Python爬虫五:微信公众号爬虫

来自:老王的小船(微信号:NebulaShip),作者:老王的小船

环境:Win7 +Python3.6+Pycharm2017

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

注:有些微信域名下的url贴不出来,所以代码有删减,完整版请点击阅读原文查看。

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

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

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

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

基本思路:

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

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,几条常用命令如下:

1anyproxy     #运行anyproxy,不监听https请求
2
3anyproxy -i  #运行anyproxy,监听https请求
4
5anyproxy -i --rule F:\XX\X\sample.js  
6             #运行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文件:

 1var http = require('http'); //引用http模块
2var querystring = require('querystring');//引用querystring模块
3//构造一个函数,用于发送post请求,发送的内容为contents
4function post_data(contents{
5    var options = {
6        host'127.0.0.1',   //本地web服务器的地址端口
7        port: '8080',
8        path'/',
9        method'POST',
10        headers: {
11            'Content-Type''application/x-www-form-urlencoded',
12            'Content-Length': contents.length }
13    }
14
15    var req = http.request(options, function (res{
16        res.setEncoding('utf8');
17        res.on('data'function (data{
18            console.log("data:", data);
19        });
20    });
21    req.write(contents);
22    req.end;
23}
24
25//anyproxy固定格式
26module.exports = {
27  summary'a rule to get url and cookie'//一段代码功能介绍 公众号:老王的小船
28    //在request发送前执行,下面的if通过正则匹配请求的url来找到加载更多的请求,然后获取url、cookie
29  *beforeSendRequest(requestDetail) {
30    if (/-此处有删减,原文为一个url正则表达式-/i.test(requestDetail.url)) {
31        console.log('***********抓取到目标**********')
32        console.log(requestDetail.requestOptions['headers'])
33        var contents=querystring.stringify({
34             'url':requestDetail.url,
35             'cookie':requestDetail.requestOptions['headers']['Cookie']});
36        post_data(contents) //发送post请求
37
38        return null
39    }
40  },
41};

二、aiohttp搭建本地web服务

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

首先安装 : pip install aiohttp

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

 1from aiohttp import web
2import re
3#接收anyproxy的post请求并处理
4async def get(request):
5    n=await request.post()
6    url=n['url']
7    cookie=n['cookie']
8    # biz=re.search('biz=.{14}==',url).group()   #从url中提取出_biz
9    token = re.search('appmsg_token.+json', url).group() #从url中提取出appmsg_token
10    print('获取token:  ',token)
11    print('获取cookie:  ',cookie)
12    data={'token':token,'cookie':cookie}
13    p_list.append(data)
14    print('当前列表长度:',len(p_list))
15    return web.Response(text='你好,收到信息')
16#接收爬虫代码的get请求并返回appmsg_token和cookie
17async def give(request):
18    print('开始发送appmsg_token和cookie')
19    d=p_list.pop()
20    return web.Response(text=str(d))
21
22p_list=[]  #用于存储appmsg_token和cookie
23app=web.Application()
24app.add_routes([web.post('/',get),web.get('/',give)])  #路由定义,有post请求访问127.0.0.1:8080,执行get函数,有get请求访问127.0.0.1:8080,执行give函数
25web.run_app(app,host='127.0.0.1',port=8080#定义web服务器运行的位置 端口 公众号:老王的小船

三、adb的安装使用

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

adb安装可以参考 https://blog.csdn.net/L_201607/article/details/78150107。直接下载下面的文件,解压后有四个文件,添加到系统环境变量。cmd输入adb,出现一长串信息就表示安装ok了。

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


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

 1adb shell input swipe 100 200  800 200 100 
2    #从点(100,200)滑动到(800200)用时100ms(时间可以不加),前面是x坐标,后面是y,坐标圆点是屏幕左上角
3
4adb shell input tap 557 1578  #点击屏幕上的点(557,1578
5
6
7用python实现:直接调用os
8
9import os
10
11os.system('adb shell input swipe 100 200  800 200 100')
12
13os.system('adb shell input tap 557 1578')

四、爬虫主程序

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

 
爬虫程序代码如下,输入为公众号biz号,输出为公众号历史文章的名称和url。代码较长,做了些删减,完整代码请点击
阅读原文查看

 1import requests
2import json
3import csv
4import time
5import os
6
7def get_token():
8    os.system('adb shell input swipe 0 200  800 200')  #从屏幕左边缘右滑返回上一级
9    time.sleep(0.5)
10    os.system('adb shell input tap 557 1578')          #点击 【查看历史消息】 进入历史消息页面,坐标每个手机不一样
11    time.sleep(2)
12    os.system('adb shell input swipe 500 1800  500 300 100'#两次向上滑动触发加载更多
13    os.system('adb shell input swipe 500 1800  500 300 100')
14    time.sleep(0.1)
15    url='http://127.0.0.1:8080'
16    r=requests.get(url)   #向本地web服务器发送get请求获取appmsg_token和cookie
17    d=eval(r.text)
18    print('获取token:',d)
19    # token = d['token']
20    # cookie = d['cookie']
21    return(d)
22
23def crow(biz):
24    MARK=0 #一个公众号文章是否抓完的标志位,1表示抓完
25    data=get_token()
26    token=data['token']
27    cookie=data['cookie']
28    n=0
29    while MARK==0:
30        time.sleep(0.5)
31        pages = str(10 * n)
32        url = ‘-此处有删减,原文为url的拼接-’
33        head = {-此处有删减,原文为header信息-
34                 }
35        try:
36            r = requests.get(url, headers=head,timeout=5)
37            html = json.loads(r.text)
38            if len(html)==9:  #判断下返回的json消息体是否是正常的消息体
39                datas0 = html['general_msg_list']
40                datas0 = json.loads(datas0)
41                datas = datas0['list']
42                l = len(datas)
43                if l<10:
44                    MARK=1
45                end=time.time()-start
46                print('*********************')
47                print('Page:%d' % n, 'Num:%d' % l,'Time:%d'%end)
48                n += 1
49                for data in datas:
50                    try:
51                        url_1 = data['app_msg_ext_info']['content_url']
52                        title_1 = data['app_msg_ext_info']['title']
53                        print(title_1)
54                        print(url_1)
55                        dd = data['app_msg_ext_info']['multi_app_msg_item_list']
56                        #保存到本地
57                        with open('weixin.csv','a',newline='',encoding='gb18030')as f:
58                            write=csv.writer(f)
59                            if len(dd) > 0:
60                                for d in dd:
61                                    url_d = d['content_url']
62                                    title_d = d['title']
63                                    print(title_d)
64                                    print(url_d)
65                                    write.writerow([title_d,url_d])
66                    except Exception as e:
67                        print(e)
68                        print(r.text)
69            else:
70                #如果访问失效重新获得token、cookie
71                print('error')
72                print(r.text)
73                data = get_token()
74                token = data['token']
75                cookie = data['cookie']
76
77        except:
78            pass
79    print('*******************',biz,'抓取完成***************')
80
81
82if __name__=='__main__':
83    start=time.time()
84    biz_list=['MzIxNDEzNzI4Mg==','MzA5OTA0NDIyMQ==','MTgwNTE3Mjg2MA==','MzA3MDM5ODY4Ng==','MjM5MDMyMzg2MA==','MzA4MjQxNjQzMA==','MzU2MzA2ODk3Nw==','MTI0MDU3NDYwMQ=='#局座召忠、占豪、冷兔、美闻参阅、十点读书、新华网、新京报、央视新闻
85    for biz in biz_list:
86        crow(biz)


五、测试结果与总结

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

下图是抓取的一些数据。

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

推荐↓↓↓
Python编程
上一篇:Python爬虫六:字体反爬处理 下一篇:GitHub星数13200!用Python实现所有排序算法的开源项目你见过么?