如何爬取微信公众号的所有文章

准备阶段

为了实现该爬虫我们需要用到如下工具

  • Chrome浏览器
  • Python 3 语法知识
  • Python的Requests库

此外,这个爬取程序利用的是微信公众号后台编辑素材界面。原理是,当我们在插入超链接时,微信会调用专门的API(见下图),以获取指定公众号的文章列表。因此,我们还需要有一个公众号。

fig1

正式开始

我们需要登录微信公众号,点击素材管理,点击新建图文消息,然后点击上方的超链接。

fig2

接着,按F12,打开Chrome的开发者工具,选择Network

fig3

此时在之前的超链接界面中,点击「选择其他公众号」,输入你需要爬取的公众号(例如中国移动)

fig4

此时之前的Network就会刷新出一些链接,其中以"appmsg"开头的便是我们需要分析的内容

fig5

我们解析请求的URL

https://mp.weixin.qq.com/cgi-bin/appmsg?action=list_ex&begin=0&count=5&fakeid=MzI1MjU5MjMzNA==&type=9&query=&token=143406284&lang=zh_CN&f=json&ajax=1

它分为三个部分

  • https://mp.weixin.qq.com/cgi-bin/appmsg: 请求的基础部分
  • ?action=list_ex: 常用于动态网站,实现不同的参数值而生成不同的页面或者返回不同的结果
  • &begin=0&count=5&fakeid: 用于设置?里的参数,即begin=0, count=5

通过不断的浏览下一页,我们发现每次只有begin会发生变动,每次增加5,也就是count的值。

接着,我们通过Python来获取同样的资源,但直接运行如下代码是无法获取资源的

import requests
url = "https://mp.weixin.qq.com/cgi-bin/appmsg?action=list_ex&begin=0&count=5&fakeid=MzI1MjU5MjMzNA==&type=9&query=&token=1957521839&lang=zh_CN&f=json&ajax=1"
requests.get(url).json() 
# {'base_resp': {'ret': 200003, 'err_msg': 'invalid session'}}

我们之所以能在浏览器上获取资源,是因为我们登录了微信公众号后端。而Python并没有我们的登录信息,所以请求是无效的。我们需要在requests中设置headers参数,在其中传入Cookie和User-Agent,来模拟登陆

由于每次头信息内容都会变动,因此我将这些内容放入在单独的文件中,即"wechat.yaml",信息如下

cookie:  ua_id=wuzWM9FKE14...
user_agent: Mozilla/5.0...

之后只需要读取即可

# 读取cookie和user_agent
import yaml
with open("wechat.yaml", "r") as file:
    file_data = file.read()
config = yaml.safe_load(file_data) 

headers = {
    "Cookie": config['cookie'],
    "User-Agent": config['user_agent'] 
}

requests.get(url, headers=headers, verify=False).json()

在返回的JSON中,我们就看到了每个文章的标题(title), 摘要(digest), 链接(link), 推送时间(update_time)和封面地址(cover)等信息。

appmsgid是每一次推送的唯一标识符,aid则是每篇推文的唯一标识符。

fig6

实际上,除了Cookie外,URL中的token参数也会用来限制爬虫,因此上述代码很有可能输出会是{'base_resp': {'ret': 200040, 'err_msg': 'invalid csrf token'}}

接着我们写一个循环,获取所有文章的JSON,并进行保存。

import json
import requests
import time
import random

import yaml
with open("wechat.yaml", "r") as file:
    file_data = file.read()
config = yaml.safe_load(file_data) 

headers = {
    "Cookie": config['cookie'],
    "User-Agent": config['user_agent'] 
}

# 请求参数
url = "https://mp.weixin.qq.com/cgi-bin/appmsg"
begin = "0"
params = {
    "action": "list_ex",
    "begin": begin,
    "count": "5",
    "fakeid": config['fakeid'],
    "type": "9",
    "token": config['token'],
    "lang": "zh_CN",
    "f": "json",
    "ajax": "1"
}

# 存放结果
app_msg_list = []
# 在不知道公众号有多少文章的情况下,使用while语句
# 也方便重新运行时设置页数
i = 0
while True:
    begin = i * 5
    params["begin"] = str(begin)
    # 随机暂停几秒,避免过快的请求导致过快的被查到
    time.sleep(random.randint(1,10))
    resp = requests.get(url, headers=headers, params = params, verify=False)
    # 微信流量控制, 退出
    if resp.json()['base_resp']['ret'] == 200013:
        print("frequencey control, stop at {}".format(str(begin)))
        break
    
    # 如果返回的内容中为空则结束
    if len(resp.json()['app_msg_list']) == 0:
        print("all ariticle parsed")
        break
        
    app_msg_list.append(resp.json())
    # 翻页
    i += 1

在上面代码中,我将fakeid和token也存放在了"wechat.yaml"文件中,这是因为fakeid是每个公众号都特有的标识符,而token则会经常性变动,该信息既可以通过解析URL获取,也可以从开发者工具中查看

fig7

在爬取一段时间后,就会遇到如下的问题

{'base_resp': {'err_msg': 'freq control', 'ret': 200013}}

此时你在公众号后台尝试插入超链接时就能遇到如下这个提示

fig8

这是公众号的流量限制,通常需要等上30-60分钟才能继续。为了完美处理这个问题,你可能需要申请多个公众号,可能需要和微信公众号的登录系统斗智斗勇,或许还需要设置代理池。

但是我并不需要一个工业级别的爬虫,只想爬取自己公众号的信息,因此等个一小时,重新登录公众号,获取cookie和token,然后运行即可。我可不想用自己的兴趣挑战别人的饭碗。

最后将结果以JSON格式保存。

# 保存结果为JSON
json_name = "mp_data_{}.json".format(str(begin))
with open(json_name, "w") as file:
    file.write(json.dumps(app_msg_list, indent=2, ensure_ascii=False))

或者提取文章标识符,标题,URL,发布时间这四列信息,保存成CSV。

info_list = []
for msg in app_msg_list:
    if "app_msg_list" in msg:
        for item in msg["app_msg_list"]:
            info = '"{}","{}","{}","{}"'.format(str(item["aid"]), item['title'], item['link'], str(item['create_time']))
            info_list.append(info)
# save as csv
with open("app_msg_list.csv", "w") as file:
    file.writelines("\n".join(info_list))         

下一篇,将介绍如何根据每个文章的连接地址,来获取每篇文章的阅读量信息。

参考资料

最终代码如下,使用方法为python wechat_parser.py wechat.yaml

import json
import requests
import time
import random
import os
import yaml
import sys

if len(sys.argv) < 2:
    print("too few arguments")
    sys.exit(1)

yaml_file = sys.argv[1]
if not os.path.exists(yaml_file):
    print("yaml_file is not exists")
    sys.exit(1)
    

with open(yaml_file, "r") as file:
    file_data = file.read()
config = yaml.safe_load(file_data)

headers = {
    "Cookie": config['cookie'],
    "User-Agent": config['user_agent'] 
}

# 请求参数
url = "https://mp.weixin.qq.com/cgi-bin/appmsg"
begin = "0"
params = {
    "action": "list_ex",
    "begin": begin,
    "count": "5",
    "fakeid": config['fakeid'],
    "type": "9",
    "token": config['token'],
    "lang": "zh_CN",
    "f": "json",
    "ajax": "1"
}

# 存放结果
if os.path.exists("mp_data.json"):
    with open("mp_data.json", "r") as file:
        app_msg_list = json.load(file)
else:
    app_msg_list = []
# 在不知道公众号有多少文章的情况下,使用while语句
# 也方便重新运行时设置页数
i = len(app_msg_list) // 5
while True:
    begin = i * 5
    params["begin"] = str(begin)
    # 随机暂停几秒,避免过快的请求导致过快的被查到
    time.sleep(random.randint(1,10))
    resp = requests.get(url, headers=headers, params = params, verify=False)
    # 微信流量控制, 退出
    if resp.json()['base_resp']['ret'] == 200013:
        print("frequencey control, stop at {}".format(str(begin)))
        break
    
    # 如果返回的内容中为空则结束
    if len(resp.json()['app_msg_list']) == 0:
        print("all ariticle parsed")
        break
        
    app_msg_list.append(resp.json())
    # 翻页
    i += 1

# 保存结果为JSON
json_name = "mp_data.json"
with open(json_name, "w") as file:
    file.write(json.dumps(app_msg_list, indent=2, ensure_ascii=False))
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335