Python+fiddler:爬取微信公众号的文章
优采云 发布时间: 2020-08-13 00:35这几天师傅有个小项目,挺有意思,如何使用python爬微信公众号中的新闻信息。大体流程如下。
图1:流程
其实我们看见,这里并没有想像中的“智能”——依然须要自动刷公众号文章,然后就能够搜集到信息。(误:更新的第9部份是愈发智能的操作,减少手刷)
1. 电脑下载fiddler
图2:下载fiddler2. 安装以后,点开第一眼听到的是这样
图3:fiddler第一次点开以后
这里附上fiddler的介绍。
3. 设置
图4:设置Tools-Options-HTTPS
然后设置Actions:点击Actions,选择Trust root certificate以及export root certificate to desktop(弹下来的提示都选Yes)。
图5:设置Actions
图6:设置Tools-Options-Connections4. 手机设置(我使用小米手机,其他手机大致一样)
图7:设置手机WiFi代理(proxy)
图8:手机步入网址192.168.124.14:8888
图9:点击FiddlerRoot Certificate
图10:下载以后安装它,随意命名,我命名为“Fiddler2”
5. 重启笔记本的Fiddler,手机点开公众号文章,电脑Fiddler搜集信息
图11:记录
图12:具体剖析
图13:复制Fiddler记录的链接,在浏览器中点开
图14:过滤
图15:过滤以后的信息
可以看见,序号存在着跳跃,因为过滤起到了作用。
6. 将所有信息导入
图16:导出手机浏览记录
图17:txt信息7. Python抽取公众号信息
import numpy as np
data = []
with open(r'...\1_Full.txt', 'r', encoding='utf-8') as fp:
for line in fp:
if 'Referer: https://mp.weixin.qq.com/' in line: //将含有重要信息的链接保留到data中
data.append(line[9:])
// 去重
data = np.unique(data)
8. 通过笔记本陌陌客户端抓取公众号的信息
在一遍一遍刷手机以后,本人勿必厌恶。。如果还能通过键盘点击笔记本为内心客户端,然后通过fiddler搜集信息,那么就不用刷手机了。。注意,在调整fiddler的时侯,anaconda的jupyter关掉(可以使用spyder),否则fiddler会出问题。
操作差不多。
首先,将fiddler-Tools-Options-HTTPS,将Decrypt HTTPS traffic更改为“from all processes”.
图18:电脑搜集微信公众号的操作
然后,同样在自己的浏览器中,输入IP地址+8888,下载证书。
图19:下载FiddlerRoot证书
下载以后进行安装。
图20:安装证书
其他设置filter和前面手机设置一样,都是把关于wp.weixin的内容筛选下来。
然后,刷笔记本端微信公众号,那么filter才能够记录下所有的公众号文章。注意,一旦打开fiddler,那么笔记本难以访问其他网页,因为百度等防爬机制太严格,会检查到fiddler早已启动。
9. 更加手动和智能的操作
无论是刷手机搜集信息,还是通过笔记本端刷公众号,依然是须要人点击信息,不够智能。这里在参考了新的案例以后,能够进行颠覆性的改进。
首先,本文后面的模块仍然须要了解。当早已才能在笔记本端刷微信公众号的文章、同时fiddler才能搜集https的信息,那么继续往下。以“首都之窗”微信公众号为例。
(1)电脑陌陌端的操作
打开fiddler。
点击设置-通用设置-使用系统默认浏览器打开网页。
图21:电脑陌陌端设置
然后,随意点击“首都之窗”的任意一篇文章,会在浏览器中弹下来。放在哪里,不用理会。顺便把fiddler中记录的这个文章信息删了。留着fiddler空白,记录第25图的重点内容!
这一步的目的是为了才能顺利在浏览器中打开公众号的历史消息但是刷新。
图22:先点一篇文章
图23:该文章在浏览器弹下来
图24:完整操作
接着,进入“首都之窗”公众号,点击查看历史消息。
图25:查看历史消息
同样,“历史消息”在浏览器(绝不能在陌陌客户端下拉、因为fiddler收不到信息)中弹下来,然后往下开始刷几下,需要听到有新的内容弹下来,同时见到fiddler正在记录更新的信息。fiddler更新的消息就是最重要的内容。
图26:在浏览器中下拉几次“历史消息”
(2)fiddler信息剖析
刚刚通过在浏览器下拉公众号历史消息,fiddler采集到了更新的信息。我们开始剖析。
图27:分析由于下拉历史消息而搜集到的某一条记录
选择第8条记录(该记录来自浏览器中下拉历史记录而搜集到的消息),重点部份早已在headers中圈下来了。
(3)链接分析(看不下去的话,直接看代码如何拼出链接)
首先,在Request headers中,该链接简拼是 /mp/profile_ext?action=getmsg&__biz=MzA5NDY5MzUzMQ==&f=json&offset=20&count=10&is_ok=1&scene=124&uin=777&key=777&pass_ticket=&wxtoken=&appmsg_token=1052_D6g2L7mM%252BaKLoVQK33V8q4D4wk3doi7QeR3Zog~~&x5=0&f=json HTTP/1.1
分析这个链接。可以看见,它是由几个部份组成。
①/mp/profile_ext?②action=getmsg ③&__biz=MzA5NDY5MzUzMQ== ④&f=json⑤&offset=20 ⑥&count=10 ⑦&is_ok=1 ⑧&scene=124 ⑨&uin=777 ⑩&key=777 &pass_ticket= &wxtoken= &appmsg_token=1052_D6g2L7mM%252BaKLoVQK33V8q4D4wk3doi7QeR3Zog~~ &x5=0&f=json HTTP/1.1
那么我们须要关注的信息是:
③__biz:公众号的id(公众号的biz惟一),⑤offset:翻页标志,appmsg_token:某个有时效性的token(隔一段时间会变化)
我们再看下边几个链接
GET /mp/profile_ext?action=getmsg&__biz=MzA5NDY5MzUzMQ==&f=json&offset=40&count=10&is_ok=1&scene=124&uin=777&key=777&pass_ticket=&wxtoken=&appmsg_token=1052_D6g2L7mM%252BaKLoVQK33V8q4D4wk3doi7QeR3Zog~~&x5=0&f=json HTTP/1.1
GET /mp/profile_ext?action=getmsg&__biz=MzA5NDY5MzUzMQ==&f=json&offset=60&count=10&is_ok=1&scene=124&uin=777&key=777&pass_ticket=&wxtoken=&appmsg_token=1052_D6g2L7mM%252BaKLoVQK33V8q4D4wk3doi7QeR3Zog~~&x5=0&f=json HTTP/1.1
biz和appmsg_token一致,offset改变,即为新的一页。因此,第一步,我们早已找到了翻页的规律。链接中只有这三个在变化,其他没有变动。因此,链接在python中才能写成:
api = 'https://mp.weixin.qq.com/mp/profile_ext?action=getmsg&__biz={0}&f=json&offset={1}&count=10&is_ok=1&scene=124&uin=777&key=777&pass_ticket=&wxton=&appmsg_token={2}&x5=0&f=json HTTP/1.1'.format(
__biz, offset, appmsg_token)
(4)cookie和headers
cookie保存的是陌陌登陆的信息,在爬虫的时侯须要填进去。我们只要关注wsp_sid2的cookies信息。
cookies同样来自图27。找到wap_sid2=CK6vyK4CElxLdmda............
headers同样来自图27。找到 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
(5)爬取
好的,以上找到了好多信息。初步的python如下:
import requests
import json
# 链接拼接三个信息
__biz = "MzA5NDY5MzUzMQ=="
appmsg_token = "1052_D6g2L7mM%252BaKLoVQK33V8q4D4wk3doi7QeR3Zog~~"
offset = 20
# cookies和headers
cookies = "wap_sid2=CK6vyK4CElxLdmda......."
headers = {'Cookie':cookies,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"
}
# api拼出来
api = 'https://mp.weixin.qq.com/mp/profile_ext?action=getmsg&__biz={0}&f=json&offset={1}&count=10&is_ok=1&scene=124&uin=777&key=777&pass_ticket=&wxton=&appmsg_token={2}&x5=0&f=json HTTP/1.1'.format(
__biz, offset, appmsg_token)
# 抓取并且json化
resp = requests.get(api, headers = headers, verify=False).json()
为什么须要把resp进行json化?我们可以尝试着打开9(3)一开始的链接的网页,
图28:拼下来的api打开的网页长这样
很明显这是个json信息。因此须要json化。复制该网页的全部文本,放在网页中,可以看见完整的json结构。这就是resp的网页结果。
图29:把图28的文本信息复制置于中的结果
那么resp在python中被json化以后的结果如下
图30:resp在python中的结构
(6)网页解析
接下来,关注该resp的结构以及一层一层剖析。图30中resp中errmsg=ok和ret=0,均表示网页可以正常打开(如果报错的话,ret=-3)。next_offset是下一次翻页的标志,需要保存上去。
next_offset = resp.get('next_offset')
general_msg_list = resp.get('general_msg_list')
# 将general_msg_list转为json格式
msg_list = json.loads(general_msg_list)['list']
general_msg_list 是 重要的内容。点击general_msg_list,这仍然是一个json结构。
图31:general_msg_list仍然是json结构。
复制上面的文本,放到中瞧瞧是哪些。
图32:general_msg_list放在中的结果
因此被json化以后的msg_list,在python中长这样
图33:把general_msg_list进行json化在python中的样子
可以看见,msg_list中富含10个记录。我们抽出一个记录,进行具体剖析。在剖析之前,我们要明晰一个东西。msg_list中收录了10个记录,不是指10篇文章,而是10次推送。某一次公众号推送消息,可能同时发布好几条文章,也有可能是一篇文章。因此,要明白,单个msg记录,是指一个推送(and可能一次性发布了好几篇文章)。
图34:某一次推送,一起发布了3篇文章
msg = msg_list[0]
图35:某一个具体的msg
该msg上面收录了“app_msg_ext_info”和“comm_msg_info”两个内容。在中,这两个内容分别长这样子。
图36:某一个msg具体的两部份---app_msg_ext_info和comm_msg_info
那么comm_msg_info收录了该推送的基本信息:推送ID,时间等。
app_msg_ext_info是哪些?且听我渐渐剖析。首先,title,digest仍然到is_multi,都是该次推送的打头文章(就是图34中带图片的那种文章的信息)。例如title标题/digest关键词/content_url链接/source_url原链接等。
is_multi是判定该次推送是不是有读篇文章;=1表示yes,=0表示no。那么这儿等于1,说明该次推送还有其他文章,存在于multi_app_msg_item_list中。
把multi_app_msg_item_list取下来。
multi_app_msg_item_list = app_msg_ext_info.get('multi_app_msg_item_list')
图37:该推送剩下的两篇文章藏在multi_app_msg_item_list中
到目前为止,我们早已剖析完了整体的流程。
总结
图38:总结怎样走出第一步
图39:具体剖析结构
(7)具体代码如下
<p>import requests
import json
from datetime import datetime
import pandas as pd
import time
class WxMps:
def __init__(self, biz, appmsg_token, cookies, offset, city):
self.biz = biz
self.msg_token = appmsg_token
self.offset = offset
self.headers = {'Cookie':cookies, 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36'
}
self.city = city
def parse1(self, resp):
# 控制下一个抓取的offset
offset = resp.get('next_offset')
# 将包含主要内容的list转为json格式
general_msg_list = resp.get('general_msg_list')
# 一个msg_list中含有10个msg
msg_list = json.loads(general_msg_list)['list']
df1 = pd.DataFrame(columns = ['msg_id', 'post_time', 'msg_type', 'title', 'cover', 'author', 'digest', 'source_url', 'content_url'])
# 循环message列表
for msg in msg_list:
# msg是该推送的信息,包含了comm_msg_info以及app_msg_ext_info两个信息,注意某一个推送中可能含有多个文章。
comm_msg_info = msg.get('comm_msg_info')
app_msg_ext_info = msg.get('app_msg_ext_info')
# 该推送的id
msg_id = comm_msg_info.get('id')
# 该推送的发布时间,例如1579965567需要转化为datetime,datetime.fromtimestamp(1579965567)
post_time = datetime.fromtimestamp(comm_msg_info['datetime'])
# 该推送的类型
msg_type = comm_msg_info.get('type')
if app_msg_ext_info:
# 推送的第一篇文章
title, cover, author, digest, source_url, content_url = self.parse2(app_msg_ext_info)
df2 = self.df_process(msg_id, post_time, msg_type, title, cover, author, digest, source_url, content_url)
df1 = pd.concat([df1, df2])
# 判断是不是多篇文章
is_multi = app_msg_ext_info.get("is_multi")
# 如果是1,继续爬取;如果是0,单条推送=只有一篇文章
if is_multi:
multi_app_msg_item_list = app_msg_ext_info.get('multi_app_msg_item_list')
for information in multi_app_msg_item_list:
(title, cover, author, digest, source_url, content_url) = self.parse2(information)
df2 = self.df_process(msg_id, post_time, msg_type, title, cover, author, digest, source_url, content_url)
df1 = pd.concat([df1, df2])
return df1, offset
def start(self):
offset = self.offset
df1 = pd.DataFrame(columns = ['msg_id', 'post_time', 'msg_type', 'title', 'cover', 'author', 'digest', 'source_url', 'content_url'])
while offset