前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于PC端的爬取公众号历史文章

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

作者头像
不断折腾
发布2019-09-23 11:28:25
2.4K0
发布2019-09-23 11:28:25
举报
前言

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

由于最近需要公众号的历史文章信息,所以就尝试爬了一下,虽然目前可以爬到数据,但是还不能够大量的自动化爬取。原因是参数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就可以了,其他都可以不要。最终写出脚本来尝试获取一下:

代码语言:javascript
复制
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,必须携带)

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

代码语言:javascript
复制
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'))

最终整理脚本如下

代码语言:javascript
复制
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中由于微信限制,我添加了链接两个字,去掉就好了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 python入门到放弃 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档