閱讀文本大概須要 10 分鐘。數據庫
場景:有時候咱們想爬取某個大 V 的發佈的所有的文章進行學習或者分析。json
這個爬蟲任務咱們須要藉助「 Charles 」這個抓包工具,設置好手機代理 IP 去請求某個頁面,經過分析,模擬請求,獲取到實際的數據。bash
咱們要爬取文章的做者、文章標題、封面圖、推送時間、文件內容、閱讀量、點贊數、評論數、文章實際連接等數據,最後要把數據存儲到「 MongoDB 」數據庫中。微信
首先,在 PC 上下載 Charles,並獲取本地的 IP 地址。cookie
而後,手機連上同一個網段,並手動設置代理 IP,端口號默認填 8888 。最後配置 PC 和手機上的證書及 SSL Proxying,保證能順利地抓到 HTTPS 的請求。具體的方法能夠參考下面的文章。session
「https://www.jianshu.com/p/595e8b556a60?from=timeline&isappinstalled=0 」app
首先咱們選中一個微信公衆號,依次點擊右上角的頭像、歷史消息,就能夠進入到所有消息的主界面。默認展現的是前 10 天曆史消息。框架
而後能夠查看 Charles 抓取的請求數據,能夠經過「 mp.weixin.qq.com 」去過濾請求,獲取到消息首頁發送的請求及請求方式及響應內容。ide
繼續往下滾動頁面,能夠加載到下一頁的數據,一樣能夠獲取到請求和響應的數據。工具
爬取的數據最後要保存在 MongoDB 文檔型數據庫中,因此不須要創建數據模型,只須要安裝軟件和開啓服務就能夠了。MongoDB 的使用教程能夠參考下面的連接:
「 https://www.jianshu.com/p/4c5deb1b7e7c 」
爲了操做 MongoDB 數據庫,這裏使用「 MongoEngine 」這個相似於關係型數據庫中的 ORM 框架來方便咱們處理數據。
pip3 install mongoengine
複製代碼
從上面的分析中能夠知道首頁消息、更多頁面消息的請求 URL 規律以下:
# 因爲微信屏蔽的關鍵字, 字段 netloc + path 用 ** 代替
# 首頁請求url
https://**?action=home&__biz=MzIxNzYxMTU0OQ==&scene=126&bizpsid=0&sessionid=1545633855&subscene=0&devicetype=iOS12.1.2&version=17000027&lang=zh_CN&nettype=WIFI&a8scene=0&fontScale=100&pass_ticket=U30O32QRMK6dba2iJ3ls6A3PRbrhksX%2B7D8pF3%2Bu3uXSKvSAa1hnHzfsSClawjKg&wx_header=1
# 第二頁請求url
https://**?action=getmsg&__biz=MzIxNzYxMTU0OQ==&f=json&offset=10&count=10&is_ok=1&scene=126&uin=777&key=777&pass_ticket=U30O32QRMK6dba2iJ3ls6A3PRbrhksX%2B7D8pF3%2Bu3uXSKvSAa1hnHzfsSClawjKg&wxtoken=&appmsg_token=988_rETfljlGIZqE%252F6MobN1rEtqBx5Ai9wBDbbH_sw~~&x5=0&f=json
# 第三頁請求url
https://**?action=getmsg&__biz=MzIxNzYxMTU0OQ==&f=json&offset=21&count=10&is_ok=1&scene=126&uin=777&key=777&pass_ticket=U30O32QRMK6dba2iJ3ls6A3PRbrhksX%2B7D8pF3%2Bu3uXSKvSAa1hnHzfsSClawjKg&wxtoken=&appmsg_token=988_rETfljlGIZqE%252F6MobN1rEtqBx5Ai9wBDbbH_sw~~&x5=0&f=json
複製代碼
能夠經過把 offset 設置爲可變數據,請求全部頁面的數據 URL能夠寫成下面的方式:
https://**?action=getmsg&__biz=MzIxNzYxMTU0OQ==&f=json&offset={}&count=10&is_ok=1&scene=126&uin=777&key=777&pass_ticket=U30O32QRMK6dba2iJ3ls6A3PRbrhksX%2B7D8pF3%2Bu3uXSKvSAa1hnHzfsSClawjKg&wxtoken=&appmsg_token=988_rETfljlGIZqE%252F6MobN1rEtqBx5Ai9wBDbbH_sw~~&x5=0&f=json
複製代碼
另外,經過 Charles 獲取到請求頭。因爲微信的反爬機制,這裏的 Cookie 和 Referer 有必定的時效性,須要定時更換。
self.headers = {
'Host': 'mp.weixin.qq.com',
'Connection': 'keep-alive',
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16B92 MicroMessenger/6.7.4(0x1607042c) NetType/WIFI Language/zh_CN',
'Accept-Language': 'zh-cn',
'X-Requested-With': 'XMLHttpRequest',
'Cookie': 'devicetype=iOS12.1; lang=zh_CN; pass_ticket=fXbGiNdtFY050x9wsyhMnmaSyaGbSIXNzubjPBqiD+c8P/2GyKpUSimrtIKQJsQt; version=16070430; wap_sid2=CMOw8aYBElx2TWQtOGJfNkp3dmZHb3dyRnpRajZsVlVGX0pQem4ycWZSNzNFRmY3Vk9zaXZUM0Y5b0ZpbThVeWgzWER6Z0RBbmxqVGFiQ01ndFJyN01LNU9PREs3OXNEQUFBfjC409ngBTgNQJVO; wxuin=349984835; wxtokenkey=777; rewardsn=; pac_uid=0_f82bd5abff9aa; pgv_pvid=2237276040; tvfe_boss_uuid=05faefd1e90836f4',
'Accept': '*/*',
'Referer': 'https://**?action=home&__biz=MzIxNzYxMTU0OQ==&scene=126&sessionid=1544890100&subscene=0&devicetype=iOS12.1&version=16070430&lang=zh_CN&nettype=WIFI&a8scene=0&fontScale=100&pass_ticket=pg%2B0C5hdqENXGO6Fq1rED9Ypx20C2vuodaL8DCwZwVe22sv9OtWgeL5YLjUujPOR&wx_header=1'
}
複製代碼
最後經過 requests 去模擬發送請求。
response = requests.get(current_request_url, headers=self.headers, verify=False)
result = response.json()
複製代碼
經過 Charles 返回的數據格式能夠得知消息列表的數據存儲在 general_msg_list 這個 Key 下面。所以能夠須要拿到數據後進行解析操做。
has_next_page 字段能夠判斷是否存在下一頁的數據;若是有下一頁的數據,能夠繼續爬取,不然終止爬蟲程序。
ps:因爲 Wx 反爬作的很完善,因此儘可能下降爬取的速度。
response = requests.get(current_request_url, headers=self.headers, verify=False)
result = response.json()
if result.get("ret") == 0:
msg_list = result.get('general_msg_list')
# 保存數據
self._save(msg_list)
self.logger.info("獲取到一頁數據成功, data=%s" % (msg_list))
# 獲取下一頁數據
has_next_page = result.get('can_msg_continue')
if has_next_page == 1:
# 繼續爬取寫一頁的數據【經過next_offset】
next_offset = result.get('next_offset')
# 休眠2秒,繼續爬下一頁
time.sleep(2)
self.spider_more(next_offset)
else: # 當 has_next 爲 0 時,說明已經到了最後一頁,這時纔算爬完了一個公衆號的全部歷史文章
print('爬取公號完成!')
else:
self.logger.info('沒法獲取到更多內容,請更新cookie或其餘請求頭信息')
複製代碼
def _save(self, msg_list):
""" 數據解析 :param msg_list: :return: """
# 1.去掉多餘的斜線,使【連接地址】可用
msg_list = msg_list.replace("\/", "/")
data = json.loads(msg_list)
# 2.獲取列表數據
msg_list = data.get("list")
for msg in msg_list:
# 3.發佈時間
p_date = msg.get('comm_msg_info').get('datetime')
# 注意:非圖文消息沒有此字段
msg_info = msg.get("app_msg_ext_info")
if msg_info: # 圖文消息
# 若是是多圖文推送,把第二條第三條也保存
multi_msg_info = msg_info.get("multi_app_msg_item_list")
# 若是是多圖文,就從multi_msg_info中獲取數據插入;反之直接從app_msg_ext_info中插入
if multi_msg_info:
for multi_msg_item in multi_msg_info:
self._insert(multi_msg_item, p_date)
else:
self._insert(msg_info, p_date)
else:
# 非圖文消息
# 轉換爲字符串再打印出來
self.logger.warning(u"此消息不是圖文推送,data=%s" % json.dumps(msg.get("comm_msg_info")))
複製代碼
from datetime import datetime
from mongoengine import connect
from mongoengine import DateTimeField
from mongoengine import Document
from mongoengine import IntField
from mongoengine import StringField
from mongoengine import URLField
__author__ = 'xag'
# 權限鏈接數據庫【數據庫設置了權限,這裏必須指定用戶名和密碼】
response = connect('admin', host='localhost', port=27017,username='root', password='xag')
class Post(Document):
""" 文章【模型】 """
title = StringField() # 標題
content_url = StringField() # 文章連接
source_url = StringField() # 原文連接
digest = StringField() # 文章摘要
cover = URLField(validation=None) # 封面圖
p_date = DateTimeField() # 推送時間
author = StringField() # 做者
content = StringField() # 文章內容
read_num = IntField(default=0) # 閱讀量
like_num = IntField(default=0) # 點贊數
comment_num = IntField(default=0) # 評論數
reward_num = IntField(default=0) # 點贊數
c_date = DateTimeField(default=datetime.now) # 數據生成時間
u_date = DateTimeField(default=datetime.now) # 數據最後更新時間
複製代碼
def _insert(self, msg_info, p_date):
""" 數據插入到 MongoDB 數據庫中 :param msg_info: :param p_date: :return: """
keys = ['title', 'author', 'content_url', 'digest', 'cover', 'source_url']
# 獲取有用的數據,構建數據模型
data = sub_dict(msg_info, keys)
post = Post(**data)
# 時間格式化
date_pretty = datetime.fromtimestamp(p_date)
post["p_date"] = date_pretty
self.logger.info('save data %s ' % post.title)
# 保存數據
try:
post.save()
except Exception as e:
self.logger.error("保存失敗 data=%s" % post.to_json(), exc_info=True)
複製代碼
推薦使用工具 Robo3T 鏈接 MongoDB 數據庫,能夠查看到公號文章數據已經所有保存到數據庫中。
本文首發於公衆號「 AirPython 」,後臺回覆「公號1」便可獲取完整代碼。