python通過手機抓取微信公眾號
使用 Fiddler 抓包分析公眾號
開啟微信隨便選擇一個公眾號,檢視公眾號的所有歷史文章列表
在 Fiddler 上已經能看到有請求進來了,說明公眾號的文章走的都是HTTPS協議,這些請求就是微信客戶端向微信伺服器傳送的HTTP請求。
模擬微信請求
1、伺服器的響應結果,200 表示伺服器對該請求響應成功
2、請求協議,微信的請求協議都是基 於HTTPS 的,所以Fiddle一定要配置好,不然你看不到 HTTPS 的請求。
3、請求路徑,包括了請求方法(GET),請求協議(HTTP/1.1),請求路徑(/mp/profile_ext...後面還有很長一串引數)
4、包括Cookie資訊在內的請求頭。
5、微信伺服器返回的響應資料。
確定微信公眾號的請求HOST是 mp.weixin.qq.com 之後,我們可以使用過濾器來過濾掉不相關的請求。
爬蟲的基本原理就是模擬瀏覽器傳送 HTTP 請求,然後從伺服器得到響應結果,現在我們就用 Python 實現如何傳送一個 HTTP 請求。這裡我們使用 requests 庫來發送請求。
拷貝URL和請求頭
- 1:找到完整URL請求地址
- 2:找到完整的請求頭(headers)資訊,Headers裡面包括了cookie、User-agent、Host 等資訊。
我們直接從 Fiddler 請求中拷貝 URL 和 Headers, 右鍵 -> Copy -> Just Url/Headers Only
url = 'https://mp.weixin.qq.com/mp/profile_ext' \ '?action=home' \ '&__biz=MzA5MTAxMjEyMQ==' \ '&scene=126' \ '&bizpsid=0' \ '&devicetype=android-23' \ '&version=2607033c' \ '&lang=zh_CN' \ '&nettype=WIFI' \ '&a8scene=3' \ '&pass_ticket=LvcLsR1hhcMXdxkZjCN49DcQiOsCdoeZdyaQP3m5rwXkXVN7Os2r9sekOOQULUpL' \ '&wx_header=1'
因為 requests.get 方法裡面的 headers 引數必須是字典物件,所以,先要寫個函式把剛剛拷貝的字串轉換成字典物件。
def headers_to_dict(headers): """ 將字串 ''' Host: mp.weixin.qq.com Connection: keep-alive Cache-Control: max-age= ''' 轉換成字典物件 { "Host": "mp.weixin.qq.com", "Connection": "keep-alive", "Cache-Control":"max-age=" } :param headers: str :return: dict """ headers = headers.split("\n") d_headers = dict() for h in headers: if h: k, v = h.split(":", 1) d_headers[k] = v.strip() return d_headers
公眾號歷史文章資料就在 response.text 中。如果返回的內容非常短,而且title標籤是<title>驗證</title>,
那麼說明你的請求引數或者請求頭有誤,最有可能的一種請求就是 Headers 裡面的 Cookie 欄位過期,
從手機微信端重新發起一次請求獲取最新的請求引數和請求頭試試
response = requests.get(url, headers=headers_to_dict(headers), verify=False) print(response.text) if '<title>驗證</title>' in response.text: raise Exception("獲取微信公眾號文章失敗,可能是因為你的請求引數有誤,請重新獲取") # with open("weixin_history.html", "w", encoding="utf-8") as f: # f.write(response.text)
歷史文章封裝在叫 msgList 的陣列中(實際上該陣列包裝在字典結構中),這是一個 Json 格式的資料,但是裡面還有 html 轉義字元需要處理
寫一個方法提取出歷史文章資料,分三個步驟,首先用正則提取資料內容,然後 html 轉義處理,最終得到一個列表物件,返回最近釋出的10篇文章
def extract_data(html_content): """ 從html頁面中提取歷史文章資料 :param html_content 頁面原始碼 :return: 歷史文章列表 """ import re import html import json rex = "msgList = '({.*?})'" # 正則表達 pattern = re.compile(pattern=rex, flags=re.S) match = pattern.search(html_content) if match: data = match.group(1) data = html.unescape(data) # 處理轉義 # print('data: {}'.format(data)) data = json.loads(data) articles = data.get("list") for item in articles: print(item) return articles
最終提取出來的資料總共有10條,就是最近發表的10條資料,我們看看每條資料返回有哪些欄位。
傳送時間對應comm_msg_info.datetime,app_msg_ext_info中的欄位資訊就是第一篇文章的欄位資訊,分別對應:
- title:文章標題
- content_url:文章連結
- source_url:原文連結,有可能為空
- digest:摘要
- cover:封面圖
- datetime:推送時間
後面幾篇文章以列表的形式儲存在 multi_app_msg_item_list 欄位中。
詳細程式碼
import requests url = 'https://mp.weixin.qq.com/mp/profile_ext' \ '?action=home' \ '&__biz=MzA5MTAxMjEyMQ==' \ '&scene=126' \ '&bizpsid=0' \ '&devicetype=android-23' \ '&version=2607033c' \ '&lang=zh_CN' \ '&nettype=WIFI' \ '&a8scene=3' \ '&pass_ticket=LvcLsR1hhcMXdxkZjCN49DcQiOsCdoeZdyaQP3m5rwXkXVN7Os2r9sekOOQULUpL' \ '&wx_header=1' headers =''' Host: mp.weixin.qq.com Connection: keep-alive User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; OPPO R9s Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 MQQBrowser/6.2 TBS/044405 Mobile Safari/537.36 MMWEBID/5576 MicroMessenger/6.7.3.1360(0x2607033C) NetType/WIFI Language/zh_CN Process/toolsmp x-wechat-key: d2bc6fe213fd0db717e11807caca969ba1d7537e57fc89f64500a774dba05a4f1a83ae58a3d039efc6403b3fa70ebafb52cfd737b350b58d0dca366b5daf92027aaefcb094932df5a18c8764e98703dc x-wechat-uin: MTA1MzA1Nzk4Mw%3D%3D Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,image/wxpic,image/sharpp,image/apng,image/tpg,/;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,en-US;q=0.8 Q-UA2: QV=3&PL=ADR&PR=WX&PP=com.tencent.mm&PPVN=6.7.3&TBSVC=43620&CO=BK&COVC=044405&PB=GE&VE=GA&DE=PHONE&CHID=0&LCID=9422&MO= OPPOR9s &RL=1080*1920&OS=6.0.1&API=23 Q-GUID: edb298c301f35e6c59298f2313b788cb Q-Auth: 31045b957cf33acf31e40be2f3e71c5217597676a9729f1b ''' def headers_to_dict(headers): """ 將字串 ''' Host: mp.weixin.qq.com Connection: keep-alive Cache-Control: max-age= ''' 轉換成字典物件 { "Host": "mp.weixin.qq.com", "Connection": "keep-alive", "Cache-Control":"max-age=" } :param headers: str :return: dict """ headers = headers.split("\n") d_headers = dict() for h in headers: if h: k, v = h.split(":", 1) d_headers[k] = v.strip() return d_headers # with open("weixin_history.html", "w", encoding="utf-8") as f: # f.write(response.text) def extract_data(html_content): """ 從html頁面中提取歷史文章資料 :param html_content 頁面原始碼 :return: 歷史文章列表 """ import re import html import json rex = "msgList = '({.*?})'" # 正則表達 pattern = re.compile(pattern=rex, flags=re.S) match = pattern.search(html_content) if match: data = match.group(1) data = html.unescape(data) # 處理轉義 # print('data: {}'.format(data)) data = json.loads(data) articles = data.get("list") return articles def crawl(): """ 爬取文章 :return: """ response = requests.get(url, headers=headers_to_dict(headers), verify=False) print(response.text) if '<title>驗證</title>' in response.text: raise Exception("獲取微信公眾號文章失敗,可能是因為你的請求引數有誤,請重新獲取") data = extract_data(response.text) for item in data: print(item['app_msg_ext_info']) if __name__ == '__main__': crawl()