基於PC端的爬取公眾號歷史文章

  • 2019 年 10 月 6 日
  • 筆記

前言

微信後台很多消息未回復:看到時已經回復不了。有問題可以添加我的微信:菜單 ->聯繫我

由於最近需要公眾號的歷史文章資訊,所以就嘗試爬了一下,雖然目前可以爬到數據,但是還不能夠大量的自動化爬取。原因是參數key值具有時效性(具體時間沒有驗證20分鐘的樣子),目前也不知道是如何生成的。

文章歷史列表爬取

首先先到的是搜狗微信,但是搜狗微信只能看到前十篇文章並且查不到閱讀量和在看的數量,嘗試爬取手機包,發現沒有抓取到資訊,後來才知道原因:

1、Android系統7.0以下,微信信任系統的證書。

2、Android系統7.0以上,微信7.0一下版本,微信信任系統提供的證書。

3、Android系統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就可以了,其他都可以不要。最終寫出腳本來嘗試獲取一下:

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,必須攜帶)

獲取閱讀量和再看量的程式碼為:

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'))  

最終整理腳本如下

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中由於微信限制,我添加了鏈接兩個字,去掉就好了。

Exit mobile version