Python爬虫爬取微信公众号

参考

记一次微信公众号爬虫的经历(微信文章阅读点赞的获取)

目标

获取“海南大学”微信公众号,2021年9月份至今全部推文的链接、时间、点赞数、阅读量、在看数等数据

分析

  • 在微信公众平台可以获取推文的url
  • 在微信的客户端打开推文可以查看到阅读量、点赞数、在看数这些信息
  • 通过抓包的方式来获取这些信息
image-20220729094523503

获取推文链接

在网页中进行抓包

可以通过在微信公众平台创作文章时插入超链接的方式,获得公众号文章的链接信息

  • 在插入超链接的界面打开调试界面(F12),在Network中进行抓包

    选择其它公众号,搜索海南大学,选择海南大学

image-20220729102510701

  • 可以在\appmsg中找到推文的信息(在搜索海南大学之前请先点击调式界面左上角的Clear,清理一下不需要的链接)

image-20220729103243888

  • 在Headers中可以获得获得链接的URL,Payload中获得链接的参数

    Request URL中?之前的部分是不带参数的URL,?之后的是参数

image-20220729104001472

  • 在Headers中可以获得cookie

image-20220821185956559

  • 在浏览器中打开URL可以看到我们想要的信息,之后可以使用正则表达式提取这些信息

image-20220729104545010

编写爬虫代码

  • 设置不使用系统代理,参考博客

    1
    2
    3
    import os

    os.environ['NO_PROXY'] = "mp.weixin.qq.com"
  • 设置标头

    1
    2
    3
    4
    headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
    "Cookie": "RK=N9tk1HjFWK; ...太长省略... skey=@ZhnRkc9u3"
    }
  • 设置url和参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ## url
    request_url = "https://mp.weixin.qq.com/cgi-bin/appmsg"
    ## 参数
    params = {
    'action': 'list_ex',
    'begin': 5*page_num, ## 起始项,page_num是页数
    'count': '5', ## 一页有5项内容
    'fakeid': 'MzIwMzUxNjgzNw==', ## 公众号的唯一标识
    'type': '9',
    'query': '',
    'token': '773287030',
    'lang': 'en_US',
    'f': 'json',
    'ajax': '1',
    }
  • 使用Get请求获取url源码

    1
    2
    3
    import requests

    resp = requests.get(request_url, params=params, headers=headers)
  • 编写正则表达式,用于提取create_timelinktitle

    可以先在regex101上面验证正则表达式是否正确

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import re

    ## 预加载正则表达式
    reg_exp = re.compile(r'"create_time":(?P<create_time>\d+).*?"link":"(?P<link>.*?)",.*?"title":"(?P<title>.*?)"', re.S)

    ## 使用finditer在resp中进行查找,finditer返回一个可迭代对象
    itr = reg_exp.finditer(resp.text)

    ## 循环获取迭代器中的内容,每次获取一个match对象,使用["组名"]可以获取对应组的内容
    for mch in itr:
    print(mch["create_time"])
    print(mch["title"])
    print(mch["link"])
  • 将int型时间转换成标准时间,参考博客

    1
    2
    3
    4
    5
    6
    from datetime import datetime

    create_time = mch.group("create_time")
    ## 1658971570 <class 'str'>
    create_date = datetime.fromtimestamp(int(create_time))
    ## 2022-07-28 09:26:10 <class 'datetime.datetime'>
  • 将获取的信息写入csv文件

    newline=''换行将不会空行,参考博客

    utf-8-sig编码,使用excel打开中文不会乱码,参考博客

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import csv

    f = open("hainanuniversity.csv", mode="w", encoding='utf-8-sig', newline='')
    csvwriter = csv.writer(f)

    for mch in itr:
    ## 安排一行的内容
    lst = [mch.group("create_time"), mch.group("title"), mch.group("link")]
    ## 按行写入
    csvwriter.writerow(lst)
  • 每爬完一页暂停3秒

    1
    2
    3
    import time

    time.sleep(3)
  • 整个代码要对页数进行循环

    1
    2
    for page_num in range(100):
    ...

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import requests ## request库
import re ## re库,用于正则提取字符串
import os ## 用于设置代理服务,使得在运行fiddler的同时(需要开启系统代理)也可以爬取网页
import csv ## 读写csv文件
import time ## 暂停时间
from datetime import datetime ## 转换时间

## 对指定网站不适用系统代理
os.environ['NO_PROXY'] = "mp.weixin.qq.com"

## 爬取链接,不带参数
request_url = "https://mp.weixin.qq.com/cgi-bin/appmsg"

## 设置标头
header = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
"Cookie": "RK=N9tk1HjFWK;...太长省略... skey=@ZhnRkc9u3"
}

## 正则表达式
reg_exp = re.compile(r'"create_time":(?P<create_time>\d+).*?"link":"(?P<link>.*?)",.*?"title":"(?P<title>.*?)"', re.S)

## 写入文件
f = open("hainanuniversity.csv", mode="w", encoding='utf-8-sig',newline='')
csvwriter = csv.writer(f)

for page_num in range(150):
## 设置参数
params = {
'action': 'list_ex',
'begin': 5*page_num, ## 起始项,page_num是页数
'count': '5', ## 一页有5项内容
'fakeid': 'MzIwMzUxNjgzNw==', ## 公众号的唯一标识
'type': '9',
'query': '',
'token': '773287030',
'lang': 'en_US',
'f': 'json',
'ajax': '1',
}

## get请求
resp = requests.get(request_url, params=param, headers=header)

## 正则提取关键信息,返回一个迭代器
itr = reg_exp.finditer(resp.text)

## 从迭代器中提取信息
for mch in itr:
create_time = mch.group("create_time")
## 将int型时间转换成日期
create_date = datetime.fromtimestamp(int(create_time))
## 按行写入csv文件
lst = [create_date, mch.group("title"), mch.group("link")]
csvwriter.writerow(lst)

## 输出提示信息
print("完成爬取{}".format(create_date))
## 关闭与服务器的连接
resp.close()
## 暂停3秒
time.sleep(3)

## 关闭文件
f.close()

获取点赞数和阅读量

点赞数和阅读量这些信息在网页上是没有的,在客户端才可以看见,对客户端的抓包就需要借助一些如Fiddler的抓包工具

Fiddler下载Download Fiddler Classic

Fiddler安装与配置参考博客

微信客户端抓包

在Fiddler中抓取微信客户端公众号推文的信息

  • 配置好Fiddler后打开Fiddler
  • 打开微信,点开一篇公众号文章
image-20220729152704081
  • 然后在Fiddler中找到对应的链接

image-20220729153959172

  • 可以在标头中查看cookies和user-agent等信息

image-20220729154612183

编写爬虫代码

  • 设置不使用系统代理,参考博客

    1
    2
    3
    import os

    os.environ['NO_PROXY'] = "mp.weixin.qq.com"
  • 设置标头

    1
    2
    3
    4
    headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
    "Cookie": "RK=N9tk1HjFWK; ...太长省略... skey=@ZhnRkc9u3"
    }
  • 编写正则表达式,用于在url中提取推文的mid、idx和sn三个参数

    请先在regex101上面验证正则表达式是否正确

    1
    2
    3
    import re

    reg_exp = re.compile(r'mid=(?P<mid>\w+?)\&.*?idx=(?P<idx>\d+)\&sn=(?P<sn>\w+?)\&', re.S)
  • 导入之前从网页上爬取的url,并从中提取出需要的参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    ## 打开csv文件
    with open("hainanuniversity.csv", encoding="utf-8", mode="r") as f_r:
    content = csv.reader(f_r) ## csv.reader返回一个可迭代的对象

    for row in content: ## 迭代的元素为列表<class list>
    if row == []: ## 列表非空判别
    continue

    ## 获取每篇文章的信息
    date = row[0] ## 日期<class 'str'>
    title = row[1] ## 标题<class 'str'>
    url = row[2] ## 链接<class 'str'>

    ## 正则提取url中的mid和sn
    ## findall返回一个列表,列表中包含一个元组[(mid,idx,sn)]
    result = reg_exp.findall(url)
    mid = result[0][0]
    idx = result[0][1]
    sn = result[0][2]
  • 设置post参数

    1
    2
    3
    4
    5
    6
    ## 需要传入这三个参数,服务器才会返回点赞数、阅读量这些数据
    data = {
    "is_only_read": "1",
    "is_temp_url": "0",
    "appmsg_type": "9"
    }
  • 设置url参数

    1
    2
    3
    4
    5
    6
    7
    params = {
    "__biz": "MzIwMzUxNjgzNw==", ## 这是公众号的唯一标识
    "mid": mid, ## mid、sn、idx是文章的标识
    "sn": sn,
    "idx": idx,
    "appmsg_token": "1176...太长省略...5tn" ## appmsg_token隔一段时间要更换
    }
  • 使用requests.post()命令向服务器发出post请求,获取相关的参数

    1
    2
    3
    4
    5
    6
    7
    8
    ## post请求,并以json格式输出
    ## resp类型为字典<class 'dict'>,可以通过字段对其进行访问
    resp = requests.post(request_url, headers=headers, data=data, params=params).json()

    ## 通过字段提取有效信息
    read_num = resp["appmsgstat"]["read_num"] ## 阅读量
    like_num = resp["appmsgstat"]["like_num"] ## 在看数
    old_like_num = resp["appmsgstat"]["old_like_num"] ## 点赞数
  • 将结果写入新的csv文件

    1
    2
    3
    4
    5
    6
    7
    8
    ## 打开csv文件
    f_w = open("HNU.csv", mode="w", encoding='utf-8-sig', newline='')
    csvwriter = csv.writer(f_w)

    ## 一行的信息,分别对应日期、标题、阅读量、点赞数、在看数
    item = [date, title, read_num, old_like_num, like_num, url]
    ## 按行写入csv文件
    csvwriter.writerow(item)

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import requests ## request库
import re ## re库,用于正则提取字符串
import os ## 用于设置代理服务,使得在运行fiddler的同时(需要开启系统代理)也可以爬取网页
import csv ## 读写csv文件
import time ## 暂停时间

## 设置该地址不使用系统代理
os.environ['NO_PROXY'] = "mp.weixin.qq.com"

## 去掉参数的链接
request_url = "https://mp.weixin.qq.com/mp/getappmsgext"

## 标头
headers={
"Cookie": "appmsg_token=...太长省略...cTluVVNBQUF+MPLDjpcGOA1AAQ==",
"User-Agent": "Mozilla/5.0...太长省略...WindowsWechat(0x63070517)"
}

## 编写正则表达式,用于提取url中的mid和sn
reg_exp = re.compile(r'mid=(?P<mid>\w+?)\&.*?idx=(?P<idx>\d+)\&sn=(?P<sn>\w+?)\&', re.S)

## 将结果写入新的csv文件
f_w = open("HNU.csv", mode="w", encoding='utf-8-sig', newline='')
csvwriter = csv.writer(f_w)

## 打开csv文件
with open("hainanuniversity.csv", encoding="utf-8", mode="r") as f_r:
content = csv.reader(f_r) ## <class '_csv.reader'>

count = 1 ## 用于计数
for row in content: ## <class list>
if row == []: ## 列表非空判别
continue

## 获取每篇文章的信息
date = row[0] ## 日期<class 'str'>
title = row[1] ## 标题<class 'str'>
url = row[2] ## 链接<class 'str'>

## 正则提取url中的mid和sn
result = reg_exp.findall(url) ## 返回一个列表,只有一个元素,该元素为一个元组[(mid,idx,sn)]
mid = result[0][0]
idx = result[0][1]
sn = result[0][2]

## 链接参数
params = {
"__biz": "MzIwMzUxNjgzNw==",
"mid": mid,
"sn": sn,
"idx": idx,
"appmsg_token": "1176_8G2ScIoMfwMxgT%2FDFGPVSHPonGZFy5ym8t3R-2GuoO4PptUgcElOyUpfKx2Sw7M70svEvJ1OWa1U_eJG"
}

## post参数
data = {
"is_only_read": "1",
"is_temp_url": "0",
"appmsg_type": "9"
}

## post请求,并以json格式输出
## resp类型为字典<class 'dict'>,可以通过字段对其进行访问
resp = requests.post(request_url, headers=headers, data=data, params=params)
resp_json = resp.json()

## 通过字段提取有效信息
read_num = resp_json["appmsgstat"]["read_num"] ## 阅读量
like_num = resp_json["appmsgstat"]["like_num"] ## 在看数
old_like_num = resp_json["appmsgstat"]["old_like_num"] ## 点赞数

## 一篇文章的信息,用列表存储
item = [date, title, read_num, old_like_num, like_num, url]
csvwriter.writerow(item) ## 按行写入csv文件

## 完成一篇文章的爬取,输出提示信息
print("article {} is over!".format(count))
count += 1

## 关闭post
resp.close()
## 暂停3秒
time.sleep(3)

## 关闭文件
f_w.close()