基于PC端的爬取公众号历史文章

前言

微信后台很多消息未回复:看到时已经回复不了。有问题可以添加我的微信:菜单 ->联系我 

由于最近需要公众号的历史文章信息,所以就尝试爬了一下,虽然目前可以爬到数据,但是还不能够大量的自动化爬取。原因是参数key值具有时效性(具体时间没有验证20分钟的样子),目前也不知道是如何生成的。

文章历史列表爬取

首先先到的是搜狗微信,但是搜狗微信只能看到前十篇文章并且查不到阅读量和在看的数量,尝试爬取手机包,发现没有抓取到信息,后来才知道原因:

1、安卓系统7.0以下,微信信任系统的证书。

2、安卓系统7.0以上,微信7.0一下版本,微信信任系统提供的证书。

3、安卓系统7.0以上,微信7.0以上版本,微信只信任自己的证书。

也尝试过使用appium自动化爬取,个人觉得有点麻烦。所以就尝试抓取PC端的请求。

进入正题,这次抓包使用的是Fiddler。下载链接:https://www.telerik.com/fiddler

Fiddler如何抓包这里不再一一阐述,首先第一次安装Fiddler是需要安装证书才可以抓取HTTPS请求的,

如何安装?

打开Fiddler,从菜单栏找到Tools -> Options -> 点击HTTPS -> 点击Actions 会安装证书 配置成如下:

这里以我自己的公众号为例:在PC端登陆微信,打开Fiddler,按F12是开启/停止抓包,进入公众号历史文章页面,看到Fiddler出现了很多请求,如下图:

由于查看历史记录是跳转到一个新的页面,可以从Body返回较多的看起,同时通过Content-Type也可以知道返回的是css或者html或者js,可以先从html看,于是乎就会找到如上图红色框中的链接,点击他,可以从右边看到返回结果和参数:

从右边的Headers中可以看到请求的链接,方式,参数等,如果想要更清晰的查看参数可以点击WebForms查看,也就是上图展示的结果。这里来描述一下其中重要的参数:

__biz:微信公众号的唯一标识(同一公众号不变)

uin:用户唯一标识(同一个微信用户不变)

key:微信内部算法,具有时效性,目前不知道是如何算出来的。

pass_ticket:是有一个阅读的权限加密,是变化的(在我实际的爬取中发现是不需要的,可以忽略不计)

走到这一步其实已经可以写代码爬取第一页的文章了,但是返回的是html页面,解析页面明显是比较麻烦的。

可以尝试往下滑动,加载下一页数据,看看返回的是json还是html,如果是json就好办,如果还是html,那就只好一点点的解析了。继续往下走会发现:

这个请求就是返回的文章列表,并且是json数据,这就很方便我们去解析了,从参数中发现有一个参数为offset为10,很明显这个参数就是分页的偏移量,这个请求为10加载的是第二页的历史记录,果断修改成0,再发送请求,得到的就是第一页的数据,那么就不需要再去解析html页面了,再次分析参数,发现看着看多参数,有很多一部分是没有用的,最终需要的参数有:

action:getmsg(固定值,应该表示获取更多信息吧)

__biz,uin,key这三个值在上面已经描述了,在这里也是必须的参数

f:json(定值,表示返回json数据吧)

offset:分页偏移量

想要获取公众号的历史列表,这6个参数是必须的,其他的参数可以不用带上。再来分析请求头中的hearders如图:

参数很多,我也不知道那些该带,那些不需要带,最后发现只需要携带UA就可以了,其他都可以不要。最终写出脚本来尝试获取一下:

import requests
url = "链接:http://链接:mp.weixin链接:.qq.com/mp/profile_ext"
headers= {
    'User-Agent':'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Mobile/14A403 MicroMessenger/6.5.18 NetType/WIFI Language/zh_CN'
}
param = {
    'action': 'getmsg',
    '__biz': 'MzU0NDg3NDg0Ng==',
    'f': 'json',
    'offset': 0,
    'uin': 'MTY5OTE4Mzc5Nw==',
    'key': '0295ce962daa06881b1fbddd606f47252d0273a7280069e55e1daa347620284614629cd08ef0413941d46dc737cf866bc3ed3012ec202ffa9379c2538035a662e9ffa3f84852a0299a6590811b17de96'
}

index_josn = requests.get(url, params=param, headers=headers)
print(index_josn.json())
print(index_josn.json().get('general_msg_list'))

获取json对象中的general_msg_list,得到的结果:

获取文章详情

上面已经拿到了链接,请求解析html页面就可以了。这里不再阐述(在全部代码中可以查看)。

获取阅读量和再看量

抓包方式等上面已经说了,在这里就不再废话了

点进文章,滑动到最下方(在快到达底部的时候才会去请求阅读量和再看量),很容易就会捕捉到的请求:

获取阅读量和在看量:

/mp/getappmsgext?f=json&mock=&uin=...(太长了)

获取评论:

/mp.weixin.qq.com/mp/appmsg_comment...

这里我只获取了阅读量和在看量(评论没有去获取但是都是一样的)查看需要的参数:

分析这个请求的参数(这个请求参数真的太多了,心中mmp)发现:

url需要参数:在url中只需要携带uin(用户id)和key值

hearders需要参数:至需要UA

body需要参数:

__biz:公众号唯一标识

appmsg_type:9 (目前来看都是9,必须携带)

mid和sn必须携带,更具这两个参数来判断是那篇文章。

inx:文章的排序,必须携带,对应错获取不到。

is_only_read:1(目前来看都是1,必须携带)

获取阅读量和再看量的代码为:

import requests

# 查询评论接口  重要参数:uin :微信用户唯一ID   key:具有失效性的key
url = '链接:https://链接:mp.weixin链接:.qq.com/mp/getappmsgext?uin={你的uin}&key={你的key}
hearder = {
    'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16D57 MicroMessenger/7.0.3(0x17000321) NetType/WIFI Language/zh_CN',
}

# body参数重要参数:__biz: 微信公众号唯一ID   appmsg_type:定值, 必须有
# mid 和 sn 变化值 从上一个页面可以获取       inx 定值  is_only_read 定值
data = {
    '__biz': 'MzIwMjM5ODY4Mw==',  # 公众号唯一ID  必须
    'appmsg_type': '9',  # 和 在看 有关 必须
    'mid': '2247500578',  # 必须   # 不同文章 不同
    'sn': 'bcfbfe204ac8d6fb561c6a8e330f4c55',  # 必须 和文章有关
    'idx': '1',  # 必须
    'is_only_read': 1,  # 必须 和阅读,在看有关
}

index = requests.post(url, headers=hearder, data=data)
print('结果')
print(index.json())
print('在看')
print(index.json().get('appmsgstat').get('like_num'))
print('浏览')
print(index.json().get('appmsgstat').get('read_num'))

最终整理脚本如下

import requests
import json
from urllib import parse
import re
from lxml import etree
import html
import time

headers = {
    'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Mobile/14A403 MicroMessenger/6.5.18 NetType/WIFI Language/zh_CN'
}

articles_url = "链接http:链接//mp.weixin.qq.com链接/mp/profile_ext"
yuedu_url = '链接https:链接//mp.weixin.qq.com/mp链接/getappmsgext'
y_param = {}
param = {
    'action': 'getmsg',
    '__biz': 'MzIyNTY4MDcxNA==',
    'f': 'json',
    'offset': 0,
    'uin': 'MTY5OTE4Mzc5Nw==',
    'key': 'c072b2c2faef4d94fcb6bd27030bdbbb60fc420b14aad30b763f17d4b0e872c5b68bd45fd7392cb9c554e236d16b84310e7ff377e5b3dbdc5732cd8346ea721a3d1c6ef7dc2f2ac0106ac04a6b540948'
}
data = {
    'is_only_read': '1',
    'appmsg_type': '9'
}

is_bottom = False

def get_articles_list():
    '''
    获取文章列表
    :return: 返回文章列表 list
    '''
    articles_json = requests.get(articles_url, params=param, headers=headers).json()
    if 'base_resp' in articles_json.keys():
        print('key值可能失效')
        return None
    return articles_json


def analysis_articles_list():
    '''
    解析文章列表参数
    获取除 文章,点赞,在看的所有信息
    :return: 一个字典
    '''
    # 获取 10 篇
    articles_json = get_articles_list()
    articles_info = {}
    # 不为空  获取当前文章数 等于0表示没有了
    if articles_json and articles_json.get('msg_count') > 0:
        # 获取文章列表
        articles_lsit = json.loads(articles_json.get('general_msg_list'))
        if articles_lsit.get('list'):
            for articles in articles_lsit.get('list'):
                articles_info['datetime'] = articles.get('comm_msg_info').get('datetime')

                if articles.get('app_msg_ext_info'):
                    articles_info = dict(articles_info, **articles.get('app_msg_ext_info'))
                    articles_info['is_Headlines'] = 1
                    yield articles_info
                    if articles_info.get('is_multi'):
                        for item in articles_info.get('multi_app_msg_item_list'):
                            articles_info = dict(articles_info, **item)
                            articles_info['is_Headlines'] = 0
                            yield articles_info
    else:
        global is_bottom
        is_bottom = True


def get_articles_digset(articles_info):
    time.sleep(5)
    content_url = articles_info.get('content_url').replace('amp;', '')
    cansu = parse.parse_qs(parse.urlparse(content_url).query)
    html_text = requests.get(content_url, headers=headers).text
    html_text = etree.HTML(html_text)
    html_text = html_text.xpath('//div[@id="js_content"]')[0]
    html_text = etree.tostring(html_text).decode('utf-8')
    dr = re.compile(r'<[^>]+>', re.S)
    wenzhang_text = dr.sub('', str(html_text))
    articles_info['text'] = html.unescape(wenzhang_text).strip()
    y_param['uin'] = param['uin']
    y_param['key'] = param['key']
    data['__biz'] = param['__biz']
    data['mid'] = cansu['mid'][0]
    data['sn'] = cansu['sn'][0]
    data['idx'] = cansu['idx'][0]
    y_json = requests.post(yuedu_url, headers=headers, params=y_param, data=data).json()
    try:
        articles_info['read_num'] = y_json.get('appmsgstat').get('read_num', '0')
        articles_info['like_num'] = y_json.get('appmsgstat').get('like_num', '0')
    except Exception as e:
        articles_info['read_num'] = 0
        articles_info['like_num'] = 0
        print(e)
    return articles_info


def insert_data(all_data):
    print(all_data)

def get_dime(timestamp):
    # 利用localtime()函数将时间戳转化成时间数组
    localtime = time.localtime(timestamp)
    dt = time.strftime('%Y-%m-%d %H:%M:%S', localtime)
    return dt


def main():
    # 主入口
    for offset in range(1, 1000):
        # 分页获取文章列表
        if not is_bottom:
            print('正在爬取第%d页' % offset)
            if offset % 2 == 0:
                time.sleep(5)
            param['offset'] = (offset-1) * 10
            for articles in analysis_articles_list():
                articles_info = get_articles_digset(articles)
                insert_data(articles_info)
        else:
            break


if __name__ == "__main__":
    main()

还存在的问题

参数uin:用户的唯一id,是不用改变的,问题不大

参数__biz:可以通过搜狗微信获取(通过搜狗微信搜索公众号可以在页面找到__biz)

参数key:问题很大,暂时没办法获取到

但是单独爬取一个公众号(文章不是特别多的时候)时间是够的。我在爬取的途中遇见了443的问题,可能是爬取太快,不知道加上代理ip有没有用(还没有尝试)

既然key要手动修改上去,我就索性没有去搜狗获取__biz。(有兴趣的可以去尝试一下)

key过期怎么办?

用Fiddler从新抓包获取新的key值,替换上去就可以了。

上面的源码复制下来需要把uin,__biz,key值换成自己的,url中由于微信限制,我添加了链接两个字,去掉就好了。

福利

最后送给大家一套爬虫视频:

后台回复爬虫视频获取。

下面这本书目前电子版只有第二版本,也放在上面的云盘中了。Python必看的书之一。

 

 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页