微信公众平台的信息爬取
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
微信公众号的信息获取分析
目录
1微信公众平台背景 (2)
2采集的方案及难点 (2)
2.1微信没有公开的搜索接口 (2)
2.2常规思路及问题 (3)
3国内研究现状 (3)
3.1提出的方案 (3)
3.1.1旧方案 (4)
3.1.2新方案 (4)
3.2成熟的分布式微信公众平台信息爬取 (6)
3.3公众号信息存储 (13)
3.4文章信息存储 (14)
4个人设想 (15)
5总结与展望 (17)
1微信公众平台背景
微信是腾讯公司于 2011 年推出的移动社交平台,目前已累计超过 6亿的注册用户。
而 2012 年推出的微信公众平台依托于微信的海量用户也迅速流行起来,目前该平台的注册公众号账号早已超过 800 万,累计发布了超过 2亿的文章。
依托大量的移动端用户,微信公众平台推出服务号,订阅号和企业号,通过这三种账号达到消息推送,信息交流,信息互动的目的,且越来越多的企业和用户通过微信公众平台进行网络营销,网络信息化服务,网络宣传等,微信公众平台已经成为了继微博和QQ之后新型网络平台,平台通过提供私密接口的方式让微信用户与服务端进行信息交流与互动,这大大节省了服务端企业对其服务产品的再包装和再推广的费用,再加上公众号内简单易操作的界面使大量的用户对其青睐有加。
所以对微信公众平台的关键信息爬取,能够及时获取有价值的信息,为企业和个人提供信息指导,对企业和个人的决策做到参考作用。
因此对微信公众平台的信息爬取的研究有重要意义。
2采集的方案及难点
2.1微信没有公开的搜索接口
由于微信尚未有公开的搜索接口供第三方的搜索引擎爬取,而且微信也未提供官方权威的微信公众号的导航网站或推荐服务,因此指望完成对所有微信公众号的爬取并不现实,只能对指定微信公众号的内容进行爬取。
2.2常规思路及问题
已知对所有公众号直接爬取占时不现实,那么对微信公众号的指定内容爬取主要分为如下一些步骤:
第一步就是获得需要爬取的微信公众号列表
微信公众号列表可以参考那些微信导航站的做法,人工维护维护行业精品微信号列表。
当然也可以直接爬取那些微信导航站,但质量很差。
好在真正高质量值得爬取微信公众号也就至多上万个。
第二部就是要获取每一个微信公众号的内容入口页面。
随便留意一下某个微信公众号,会发现每个微信公众号的“查看历史消息”中有此公众号已发布的所有微信内容,剩下的问题是怎样获取这个地址。
一般程序员的思路是通过抓包、反编译等手段来获取此入口地址。
好消息是要获取此微信公众号的入口地址并不复杂,你会欣喜发现此入口地址是一个普通的网页。
坏消息是:当你多测试一下,你会发现如下问题:
1)、此入口地址并不是固定不变的,一天左右就会变化的,主要是里面的key 值。
因此指望通过人工手工抓包一劳永逸地获取的地址并无太多实用价值2)、此入口页面对未关注的用户只能看第一页,需要关注后才能看后续页面,要获取后续页面,只能关注此账号,但要人工关注上万个来自更多账号的关注并不现实
3)、微信对一个账号关注的公众号数是有上限限制的
应对此难题最一劳永逸的方案当然是反编译代码,获取微信的通信协议,但就研究结果来看,成本过高,破解的可能性也不大。
3国内研究现状
3.1提出的方案
3.1.1旧方案
在2015年的时候微信网页版限制还是没那么严格的, 当时采用的主要思路是使用微信网页版, 然后用requests去模拟登陆一下,
然后不停的去访问类似下面的接口爬取信息: >
当时为了能让爬虫多个实例跑, 用了一下 Celery 框架(现在想简直智障, 多个实例跑直接把程序启动N次就行了啊。
摔), 由于是模拟登陆, 所以又写了一套复杂的东西去生成二维码, 然后获取登陆URL, 具体的模拟登陆原理参考这个wechat-deleted-friends, 另外相关的Celery Task里写的逻辑太复杂了, 一个Task里就带上了 requests断线重连机制, 模拟登陆机制, 解析列表, 解析文章等, 另外由于是web版微信有一套蛮复杂的sync机制, 有时候直接掉线需要再次的去手动登陆, 很是麻烦。
之后web版微信已经无法的获取Key了(2016年开始), 此方案就废弃了。
3.1.2新方案
经leader提醒, 改了一下架构, 其中项目的整体结构如下:
微信爬虫架构图
Seeds 是一个producer, 在此处指通过某种方式获取 uin, key, pass_ticket 信息, 思路类似中间人攻击+解析squid日志
Consumer C1从Q1队列中取出seeds后爬取某个公众号的文章列表, 解析后将文章Meta信息放入队列Q2
Consumer C2获取文章原信息后就可以直接做入库&爬取操作了
之后可以继续加队列然后去实现爬取文章阅读点赞的相关数据了, 由于有频率限制。
一个微信号一天只能最多获取8000篇文章的阅读点赞信息
抛弃了Celery和其默认选用的RabbitMQ队列, 这种东西实在太重了。
改用beanstalkd做消息队列
目前的效果是单微信号每日更新4w左右的公众号文章, 如果想继续增加数量可以通过加机器来扩展
3.2成熟的分布式微信公众平台信息爬取
3.2.1爬取策略分析
由于目前微信公众平台并没有开放官方的Web网站,因此,无法直接对微信公众号官方网站进行采集。
然而,搜狗搜索引擎已针对微信公众平台进行资源整合,并提供搜索功能。
因此,本系统基于搜狗微信搜索进行爬取。
在使用 Chrome浏览器 Web开发工具对搜狗微信搜索网站的网页组成结构及特
性进行分析后发现,每个公众号账号在搜狗微信搜索平台都有一个主页。
例如,公众号“英国那些事儿”的主页网址为:
/gzh?openid=o IWs Ftyww Cs Yrq K8-7v QQ_tf Lphc 实际上,搜狗微信搜索使用 28位字符的 openid参数对每个公众号账号进行唯一标识。
而在该主页下,有该公众号的发布文章列表,按文章的发布时间从近至远排序,每页
10篇文章,默认显示的第一页为最近的 10篇文章,点击“查看更多”链接后,会触发浏览器发出 Ajax请求以获取下一页内容,其URL如下:
/gzhjs?cb=sogou.weixin.gzhcb&openid=o IWs Ftyww CsYrq K8-7v QQ_tf Lphc&page=2
仔细观察上述的URL,可知 openid参数对应该公众号账号的 openid, page参数代表第几页,而 URL的其余内容则固定不变。
因此,可以通过不断地改变page 参数的值,来获取该公众号所发布的所有文章。
浏览器发出 Ajax请求并获取到响应数据后,会通过 Java Script实现无刷新
地更新当前网页内容,将获取到的新的 10篇文章的内容信息延伸到当前网页的南华大学硕士论文 22底部。
每个 URL的HTTP响应数据,实质上主要是一个 json 格式的字符串,
其内容如下所示:
{
"page":2,
"items": [… 略…]
"total Items":3035,
"total Pages":100
}
各个参数的含义也比较明显,其中, page 值代表当前页号,对应 HTTP请求 URL 中的
page参数; total Items值代表该公众号所发布的文章总数; total Pages值代表可供查询的最大页号(目前最多只能查询至 100页); items值代表本次查询返回的数据,其数据类型是数组,数组的每一个元素则是一个xml格式的字符串。
并且,能够从该字符串中提取到关于这篇文章的标题、摘要、封面图片地址、文章页面地址、公众号头像地址等信息。
通过上述的分析可以得知,如果已知某公众号账号的 openid标识,即可爬取到该公众号所发布的所有文章。
因此,为了获取所有公众号的所有文章,就必须获取到所有公众号账号的 openid标识。
目前,经过探索后发现,可以通过以下两种途径来获取公众号账号的 openid标识:
第一种途径是借助搜狗微信搜索首页提供的分类导航列表,如热门、汽车迷、养生堂等导航列表,其 URL地址如下:
/pcindex/pc/pc_{digit1}/{digit2}.html
其中, digit1取值为 0~19的数字,代表导航类别; digit2为 1~14的数字,代表该类别下的第几页内容。
每一个导航列表都有大量的文章,文章旁则就有该篇文章的发布者的主页链接,从而也就能够提取出其openid
标识。
并且,这
些导航列表的内容是会随时间而动态更新的。
因此,可以定时地采集这些导航
列表页面,这样一来,当经过足够长的时间后,就可以采集到足够多的公众号
openid标识。
第二种途径是借助搜狗微信搜索的文章搜索功能,可以通过输入某个关键字如“搞笑”进行搜索。
搜索结果中会包含大量由不同公众号账号发布的文章,而文章旁也有其发布公众号的主页链接,从而也就可以提取出其 openid标识。
而且,搜索结果中往往会包含数万条结果,因此可以快速地获取到大量的公众号openid标识。
考虑到目前微信公众平台认证账号已经超过 800万,总数较大,对所有公众号进行爬取需要较长时间,故需要按照某种原则来设定公众号的爬取先后顺序。
第一种途径是通过各个类别的导航页面来采集公众号,导航页面的公众号往往是热门账号,故本文采用第一种途径,优先爬取这些热门公众号。
3.2.2爬取流程设计
本文主要是使用节第一种途径来采集微信公众号的 openid标识,再进行文章的采集。
整个系统中,主要涉及有以下 4种类型的 URL:
/pcindex/pc/pc_{digit1}/{digit2}.html ①
/gzh?openid=o IWs Ft2hz LQ_Ra TIXp5B5ti0Yx Lw ②
/gzhjs?cb=sogou.weixin.gzhcb&openid=o IWs Ft2hz LQ_
Ra TIXp5B5ti0Yx Lw&page=1 ③
/s?__biz=Mz A4NDIz Nj Qz NQ==&mid=206132589&id
x=1&sn=feea5e7f33bf1cfe18b71f25d495f27c&3rd=Mz A3MDU4NTYz Mw==&s
cene=6#rd ④
其中,第一种类型的 URL 为某个导航类别下某一页的地址;第二种类型的URL 为某个公众号的主页地址;第三种类型的 URL 为某个公众号所发布的文章的某一页的地址;第四种类型的 URL 为某篇文章的地址。
本文将这 4 种类型的 URL 分别称为 init 类型(种子 URL)、index 类型(主页 URL)、page 类型(文章分页 URL)、msg 类型(文章 URL)。
除第一种类型的URL 是在配置文件中指定的种子 URLs 外,其余三种类型的 URL 都是从后续爬取到的网页中提取出来的。
当爬虫模块抓取到这些 URL 后,会通过爬虫引擎将其交给调度器模块,由调度器模块负责将其放入 URL 队列中,并且调度器模块会针对 init 类型、index 类型、page 类型这三种不同类型的 URL,在放入队列时将其优先级分别设置为3、2、1,数字越大,则优先级越高。
最终使得调度器模块从 URL 队列中取出URL 时,会优先取出 init 类型的 URL,其次才是 index 类型、page 类型。
即,仅当 URL 队列中不存在 init 类型的 URL 时,调度器才会取出 index 类型的URL,以此类推。
而在爬取到 msg 类型的 URL 后,无需将其放入待爬取 URL 队列中。
之所以将 init 类型 URL 设置为最高爬取优先级,主要是考虑到系统的首要任务是尽可能多地爬取公众号账号,因为一旦爬取到某个公众号,后续系统即可无依赖地对该公众号的账号信息、发布文章信息依次进行爬取。
而一旦不能爬取到新的未爬取公众号,后续的爬取流程无法顺利进行。
而 init 类型的导航页会定时更新,应尽可能地将每次更新后出现的新公众号都爬取到。
因此,采取优先
爬取所有导航页面中出现的所有公众号的策略,即设置 init 类型 URL为最高爬取优先级。
考虑到对于每个公众号而言,其 index 类型的 URL 只有一个,而 page 类型的 URL 则有多个,故先爬取 index 类型的 URL。
另外,在 index 类型 URL 爬取到的 Public Item 需要存入 public 表,在 page 类型 URL 爬取到的 Msg Item 需要存入 msg 表,而为了能够确定每个 msg 记录的所属 public 记录,msg 表中需要有对 public 表的外键引用字段,因此也决定了必须先爬取 index 类型 URL。
图 3.6 说明了针对 URL 队列中 3 种不同类型的 URL,爬虫模块需要对其进
行的相应操作的概要说明,具体过程如下:
(1)首先,爬虫系统启动后,会从配置文件中读取 280 个(20x14=280)init 类型的 URL 作为初始爬取 URLs 集合,并由调度器模块以优先级为 3 将其放入URL 队列中。
(2)此时,URL 队列中仅有 init 类型的 URL,故调度器模块会将队头的
init 类型 URL 取出,经爬虫引擎转发给下载器模块。
下载器模块将该网页爬取
下来后,封装成一个 Response 对象,经爬虫引擎转发给爬虫模块。
(3)爬虫模块针对 init 类型的 URL 的处理操作是:从该页面中分析提取出index 类型的 URL,并将其经爬虫引擎转发给调度器模块,由调度器模块以优先级为 2 将其放入 URL 队列中。
(4)此时,URL 队列中有 init 类型、index 类型的 URL,由于 init 类型URL 的优先级更高,故会继续重复第 2-3 步骤的操作,直至 URL 队列中所有的init 类型 URL 都被取出为止。
(5)现在,URL 队列中仅有 index 类型的 URL,故调度器模块会将队头的index 类型 URL 取出,经爬虫引擎转发给下载器模块。
下载器模块将该网页爬取下来后,封装成一个 Response 对象,经爬虫引擎转发给爬虫模块。
(6)爬虫模块针对 index 类型的 URL 的处理操作是:生成一个与当前 URL中openid 参数所对应的 page=1 的 page 类型 URL,经爬虫引擎转发给调度器模块,由调度器模块以优先级为 1 将其放入 URL 队列中;并且,从该页面中分析提取出该公众号的相关信息,封装成一个 Public Item 对象,将其经爬虫引擎转发给数据流水线模块。
数据流水线模块收到 Public Item 对象后,将该公众
号的头像图片、二维码图片上传至 Fast DFS 集群,同时将公众号的相关信息存入My SQL 数据库的 public 表中。
(7)此时,URL 队列中有 index 类型、page 类型的 URL,由于 index 类型URL 的优先级更高,故会继续重复第 5-6 步骤的操作,直至 URL 队列中所有的index 类型 URL 都被取出为止。
(8)现在,URL 队列中仅有 page 类型的 URL,故调度器模块会将队头的page 类型 URL 取出,经爬虫引擎转发给下载器模块。
下载器模块将该网页爬取下来后,封装成一个 Response 对象,经爬虫引擎转发给爬虫模块。
(9)爬虫模块针对 page 类型的 URL 的处理操作是:判断响应数据中的total Pages 参数是否大于当前 URL 的 page 参数,若是则将当前 URL 的 page 参数值+1 后,将其经爬虫引擎转发给调度器模块,由调度器模块以优先级为 1 将其放入 URL 队列中;从该页面中分析提取出文章的相关信息,封装成若干个Msg Item 对象,将其经爬虫引擎转发给数据流水线模块。
数据流水线模块收到Msg Item 对象后,将文章的封面图片、网页 HTML 源文件上传至 Fast DFS 集群,同时将文章的相关信息存入 My SQL 数据库的 msg 表中。
并且,每爬取到一个Msg Item 对象后,都会判断该对象之前是否已经爬取到,若是则停止对当前公众号的爬取,即不会将当前 URL 的 page 参数值+1 的 page 类型 URL 放入URL 队
列中。
(10)当 URL 队列中所有的 page 类型 URL 都已经出队被处理完后, URL队列为空,此时,调度器模块会再次从配置文件中读取 280 个 init 类型的 URL,并将其放入 URL 队列中,从而继续重复第 2-9 步骤的操作。
以上爬取流程会一直执行,直至人为关闭系统或系统出现故障为止。
必须说明的是,调度器模块在每次收到 URL 时,除 init 类型 URL 外,都会对收到的 URL 进行请求指纹去重,以保证放入 URL 队列中的 URL 没有重复。
对公众号信息的存储是通过流水线中的 Public Pipeline模块实现,其负责将公众号的详细信息存储至 My SQL数据库中,其主要代码如下:
class Public Pipeline(object):
def process_item(self, item, spider): #
流水线模块的接口函数
if item and item['type'] == 'public': #
仅对
Public Item
进行处理
i = item
sql = "insert into public(username, nickname, openid, \
two_url, two_name, pic_url, pic_name, brief, create_at) \
value(%s,%s,%s,%s,%s,%s,%s,%s,now())"
self.mysql.execute(sql, i['username'], i['nickname'], i['openid'], \
i['two_url'], i['two_name'], i['pic_url'], \
i['pic_name'], i['brief'])
return item
其中, self.mysql为已经创建的 My SQL连接对象。
Item对象的type字段用于标识当前 Item所存储的信息类型,其值包括 'public' 和 'msg' 两种。
当值为 'public'时表明, Item对象所带的信息是公众号信息。
另外,
pic_name, two_name字段分别为公众号头像图片、二维码图片上传至 Fast DFS 集群后的保存文件名,故在进行此操作前,还需要先完成文件上传,由 Upload Pipeline模块负责。
对文章信息的存储是通过流水线中的 Msg Pipeline模块实现,其负责将文章的详细信息存储至 My SQL数据库中,其主要代码如下:
class Msg Pipeline(object): def process_item(self, item, spider): # 流水线模块的接口函数if item and item['type'] == 'msg': # 仅对Msg Item进行处理
# 根据URL中的__biz参数获取pid值
pid, i = self.get_pid_by_biz(item['biz']), item
sql = "insert into msg(title, brief, msg_url, msg_name, cover_url, \
cover_name, post_at, create_at) value(%s, %s, %s, %s, %s, %s,
\ %s, now())"
self.mysql.execute(sql, i['title'], i['brief'], i['msg_url'], i['msg_name'], \
i['cover_url'], i['cover_name'], i['post_at'])
return item
Item对象中的biz字段为 URL地址中的__biz参数,其余各字段的含义见表 3.2。
另外, msg_name, cover_name字段分别为文章的 HTML源文件、封面图片文件上传至 Fast DFS
集群后的保存文件名,故在进行此操作前,还需要先完成文件上传,由 Upload Pipeline模块负责。
4个人设想
微信公众号存在不少精彩的文章,如果善于挖掘,可以得到不少的收获。
但由于微信对PC端的支持并不友好,虽然有搜狗搜索可以用,但其结果仍然不全,一些公众号发的不是文章类型的只是一段话,搜狗就不收录。
想要得到一个账号所有的文章,还是要从爬虫着手。
网上对于微信公众号文章爬取的方法几乎没有介绍,不过有几个网站,比如传送门就做出来了。
这就告诉我们这个目标是可以达到的。
要想得到一个公众号发送的所有文章,需要从微信手机端入手。
点击公众号右上角小人图标,会有查看历史消息的链接。
点了之后可查看所有历史文章。
所以自然要从这里入手了。
上抓包工具,Fiddler4,手机和电脑接入同一网络,手机wlan设置代理,指向电脑的ip 地址,端口默认8888,这样在电脑上就可以监听手机的http流量了。
通过抓包,在点击‘查看历史消息’选项时,请求的地址是
通过对多个账号进行抓包分析,可以确定biz这个14位的字符串是每个公众号的“id”,uin似乎与访问者有关,key也和所访问的公众号有关,可以在下面的抓取中得到这两个参数,其他的查询参数都可以去掉。
所以,必须得到三个参数才可以得到文章列表。
这三个参数biz最容易获得,在搜狗的微信平台,搜索目标公众号,会有对应的文章列表,连接到相应的文章页面。
解析文章列表,即可得到公共账号的biz。
可以通过请求
/weixin?query=,填入目标账号名称,返回的结果里解析最新文章的url,里面包含biz。
当然,模糊搜索会出现多个候选账号,这个就比较难办了。
现在,已知biz,如何继续?我曾经也困扰了好久,也算是偶然发现的。
电脑登陆微信,在手机上访问某个公众号的查看历史消息
页面,点击右上角,发送给朋友,发送给文件助手即可,电脑上查看。
似乎看到连接了,打开,果然跳转到了文章列表的页面!查看元素,这个存在span标签内,仔细观察发现和刚才抓包看到的url是一样的!继续寻找,这个span是a标签的子元素,看这个a标签的href,
加上https://的前缀,就可以访问了,最后301跳转到了span 的内容那个url。
多看几个不同的公众号,可以发现a标签的内容基本一样,不同的只是biz而已。
所以可以把这个url记为raw_url,每次请求时,用不同的biz替换即可。
当然,要带上ua,cookie的header。
用
request.get(url,headers=m_header,verify=False),因为请求的是https 域,所以要加上verify=False,返回的内容文章列表的那个页面。
分析那个页面,即可以找到uin,key,
可以用正则表达式提取出来。
紧接着下面就是这个页面的msgList,类似json 的文本,当前页面就是通过这里的文本渲染的。
可以用正则提取出来直接解析出来,然后用json.loads方法就可以得到dict,里面的信息包括content_url,文章发表的datetime时间戳,摘要信息,文章的id编号mid,还有是否是一次发多篇is_multi等。
鼠标向下滚动,可以看到动态请求更久远的文章,抓包可得,是通过json返回的。
请求的url 就是类似
这种的,前面获取的参数就都派上用场了。
只要这几个参数就可以抓取了,后面几个参数不要也可以。
要获得一个公众号的全部文章,可以将frommsgid 改到比他最新文章的id大一点的数字(实验证明若刚好取最新文章的id,则会忽略最新的那篇),count值设置大一点,比如5000,基本上就会把所有文章信息都返回了。
根据返回的json,从中解析出文章的url,然后就可以去爬取文章了,这个比较常规,貌似连cookie都不用带,就不赘述了。
5总结与展望
微信公众平台作为近几年来新兴的互联网媒体,已产生了海量的优质信息资源。
对这些信息资源进行分析研究能够对和应用具有重大意义,而在此之前则需要先使用网络爬虫技术将这些公众号的内容爬取下来并合理存储。