Python 爬取公眾號文章、評論

前段時間有個爬取公眾號評論小需求,花了幾天查了不少資料,實現(xiàn)方案有好幾種,最后其中一種得以實現(xiàn)。參考 【Python爬蟲】微信公眾號歷史文章和文章評論API分析

本人是 Python 小白,會忽略比較多的具體說明,只記錄關(guān)鍵的幾點,以便能快速實現(xiàn) 。給同樣有興趣的童鞋提供個淺顯的參考。

也建議正在嘗試的童鞋靜下心來,一步步嘗試下去,畢竟我這個小白都成功了呢。當(dāng)然有錯誤的地方也歡迎指正。

使用環(huán)境:

Mac 、 Anaconda、MySQL 和 MySQL workbench。

很多博客里使用的 Windows 安裝的 Python。但是 Mac 也沒關(guān)系,Anaconda 傳送門, MySQL傳送門, MySQL workbench 傳送門

  • Anaconda 中的 Spyder 用來運行 Python 代碼;
  • MySQL 是連接 Python 運行結(jié)果和數(shù)據(jù)庫的橋梁 (這個有個坑點:安裝 MySQL 只是作為一個橋梁,并不能作為數(shù)據(jù)庫使用,所以要創(chuàng)建數(shù)據(jù)庫,否則會報錯);
  • MySQL workbench 用來創(chuàng)建、查看數(shù)據(jù)庫,并可以到處 csv 格式的文件。

準(zhǔn)備工作

首先需要提供公眾號文章列表鏈接和評論鏈接,用于分析修改 Python 代碼(具體的 Python 代碼實現(xiàn),我只看懂了流程,但是不會寫)。那么就需要抓包了,有一些博客是使用 Fiddler,不過我使用 Charles 也完全足夠了。只要能拿到接口相關(guān)數(shù)據(jù)即可。(不會抓包的請自行查閱資料

公眾號文章列表流程:打開要爬取的公眾號 - 點擊右上角的 '人物' 圖標(biāo) - 點擊 '消息'底部的 '全部消息'
嘗試了幾次,剛打開文章列表,并沒有文章列表的接口,可以上拉刷新一下。

抓包數(shù)據(jù)中找到微信 mp.weixin.qq.com 的域名。會有很多接口,使用 Focus 功能只關(guān)注微信的,這里簡化尋找過程,直接貼出來: https://mp.weixin.qq.com/mp/profile_ext?action=getmsg :即為公眾號的文章列表接口;
https://mp.weixin.qq.com/mp/appmsg_comment?action=getcomment :即為公眾號某篇文章的評論接口。

  1. 文章列表抓包截圖


    文章列表抓包示意圖.png
  1. 文章評論抓包截圖


    評論抓包示意圖 1.png
評論數(shù)據(jù)示意圖.png

我需要的就是 elected_comment 列表中每個 Object 中的 content 內(nèi)容。Python 代碼已實現(xiàn)獲取邏輯。

Python 代碼中需要提供接口中的幾個參數(shù):__bizpass_ticketapp_msg_tokenwap_sid2cookie。具體作用可參頂部博客的說明。

正式爬數(shù)據(jù)

提供代碼中所需的幾個字段后,可以在 Anaconda 中的 Spyder 運行 Python 代碼(代碼在文末),然后替換其中的字段。即下面的幾個字

biz = 'MzIwNTc4NTEwOQ=='  # "碼農(nóng)有道公眾號"   mnyd_article  mnyd_comment
pass_ticket = 'ZS3nqLX1df5GhZ+zf/t0FYyf7Nfp52yUJ+PuyJUKvQtyln78R3QzBU21Xo528IE+'
app_msg_token = '986_G0Sy%252FL2pNlAGA9PIXcqTRipxsKaGLurexidEyg~~'     # 歷史文章
wap_sid2 = 'CL3qgfIFElxMOFBzZ2dZOHQ1WTcxamRQLXUyMGFiU0tvNkZzUEJmRURhZmtJTkhLcEtYWU9rNm5WYmUtd29qd3Q3UmVqbmpZXzFxS21GMG13amVjM1NEaUVPajZNZG9EQUFBfjDH8K3gBTgNQAE='
cookie = 'wxuin=1581282621; version=2607033b; pass_ticket={}; wap_sid2={}'.format(pass_ticket, wap_sid2)

在開始運行代碼后開始遇到問題:
1 ModuleNotFoundError: No module named 'pymysql';

需要安裝數(shù)據(jù)庫,比如安裝 MySQL。
然后在代碼中替換自己的密碼

self.db = pymysql.connect(
       host="localhost",
       user="root",
       password="123456",
       port=3306,
       use_unicode=True,
       #charset="utf8",
       database="sunshine")
       self.cursor = self.db.cursor()

2 需要創(chuàng)建數(shù)據(jù)庫,比如代碼中數(shù)據(jù)庫名字為 sunshine;

3 代碼中 offset 大部分是 10,不過有些公眾號是 20,但是 10 也可以正常運行,不用擔(dān)心;

4 因為是模擬手機接口,爬取完數(shù)據(jù)后可能會出現(xiàn) '操作頻繁,請稍后再試',這個沒有關(guān)系,等下再刷新就正常了。


基本上 Anaconda 等軟件安裝正確,數(shù)據(jù)庫已安裝,抓包數(shù)據(jù)沒錯,替換 biz 等參數(shù),替換數(shù)據(jù)庫配置密碼(或數(shù)據(jù)庫名),是可以正常運行并且保存至數(shù)據(jù)庫。

注意
Python 代碼中需要修改的,都以 # ** 提示

當(dāng)時比較緊急,很多錯誤沒有保存,現(xiàn)在回憶也零零散散。有需要的湊合看參考下吧。

發(fā)布文章要既要綁定手機號又要綁定微信,讓人很不爽 (小聲BB)

代碼示例

作者: cacho_37967865

# -!- coding: utf-8 -!-
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
#作者:cacho_37967865
#博客:https://blog.csdn.net/sinat_37967865
#文件:wechatArticleList.py
#日期:2018-12-08
#備注:通過Fiddler抓包,獲取微信公眾號歷史文章信息和文章評論信息存儲到mysql數(shù)據(jù)庫表   
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
 
import requests
import json
import pymysql
from datetime import datetime
import re
 
 
class wechatArticle:
 
    def __init__(self,_biz,_pass_ticket,_appmsg_token,_cookie,_offset=0):
        self.offset = _offset       # 不同公眾號不一樣
        self.biz = _biz
        self.pass_ticket = _pass_ticket
        self.appmsg_token = _appmsg_token
        self.headers = {
            'cookie':_cookie,
            'User-Agent':'Mozilla/5.0 (Linux; Android 8.0; FRD-AL00 Build/HUAWEIFRD-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132'
        }
        self.db = pymysql.connect(

            # ** password 需要替換為自己的。數(shù)據(jù)庫名 database 視情況,是否修改看個人需要

            host="localhost",
            user="root",
            password="123456",
            port=3306,
            use_unicode=True,
            #charset="utf8",
            database="sunshine")
        self.cursor = self.db.cursor()
 
 
    def get_article_list(self):
        offset = self.offset
        while True:
            api = 'https://mp.weixin.qq.com/mp/profile_ext?action=getmsg&__biz={0}&f=json&offset={1}&count=10&is_ok=1&scene=126&uin=777&key=777&pass_ticket={2}&wxtoken=&appmsg_token={3}&x5=1&f=json'.format(self.biz, offset, self.pass_ticket, self.appmsg_token)
            resp = requests.get(api, headers=self.headers).json()
            print(type(resp), resp)  # 字典類型
            ret, status = resp.get('ret'), resp.get('errmsg')     # 狀態(tài)信息
            if ret == 0 or status == 'ok':
                offset = resp['next_offset']
                general_msg_list = resp['general_msg_list']
                #print(type(general_msg_list),general_msg_list)    # json類型
                msg_list = json.loads(general_msg_list)['list']    # 先轉(zhuǎn)化為字典類型再獲取列表類型
                for msg in msg_list:
                    comm_msg_info = msg['comm_msg_info']           # 字典類型,每次推送的消息(一次三篇)
                    msg_id = comm_msg_info['id']                   # 推送消息的id
                    post_time = datetime.fromtimestamp(comm_msg_info['datetime'])     # 發(fā)布時間
                    try:
                        app_msg_ext_info = msg['app_msg_ext_info']  # 字典類型,文章信息(一次三篇)
                        first_article_id = app_msg_ext_info['fileid']
                        first_article_title = app_msg_ext_info['title']  # 本次推送的首條文章標(biāo)題
                        first_article_digest = app_msg_ext_info['digest']  # 本次推送的首條文章摘要
                        first_article_url = app_msg_ext_info['content_url']
                        self.get_article_detail(first_article_id,first_article_url)
                        first_url = first_article_url.replace('amp;', '').split('&chksm')[0]
                        self.article_to_mysql(msg_id, first_article_id, first_article_title, first_article_digest,first_url, post_time)
                        multi_app_msg_item_list = app_msg_ext_info.get('multi_app_msg_item_list')
                        for article in multi_app_msg_item_list:
                            article_id = article['fileid']
                            multi_article_title = article['title']
                            multi_article_digest = article['digest']
                            multi_article_url = article['content_url']
                            self.get_article_detail(article_id,multi_article_url)
                            multi_url = multi_article_url.replace('amp;', '').split('&chksm')[0]
                            self.article_to_mysql(msg_id, article_id, multi_article_title, multi_article_digest,multi_url, post_time)
 
                    except Exception as f:
                        print(str(f))
 
 
    def get_article_detail(self,article_id,content_url):
        try:
            url = content_url.replace('amp;', '').replace('#wechat_redirect', '').replace('http', 'https')
            html = requests.get(url, headers=self.headers).text
            #print(html)
        except:
            print('獲取評論失敗' + content_url)
        else:
            str_comment = re.search(r'var comment_id = "(.*)" \|\| "(.*)" \* 1;', html)
            str_msg = re.search(r"var appmsgid = '' \|\| '(.*)'\|\|", html)   # 文章的id
            str_token = re.search(r'window.appmsg_token = "(.*)";', html)
 
            if str_comment and str_msg and str_token:
                comment_id = str_comment.group(1)  # 評論id(固定)
                app_msg_id = str_msg.group(1)      # 票據(jù)id(非固定)
                appmsg_token = str_token.group(1)  # 票據(jù)token(非固定)
 
                # 缺一不可
                if comment_id and app_msg_id and appmsg_token:
                    print("爬取評論的鏈接:" + url,html)
                    self.get_article_comments(app_msg_id,comment_id,appmsg_token,article_id)
 
 
    def get_article_comments(self,app_msg_id,comment_id,appmsg_token,article_id):
        api = 'https://mp.weixin.qq.com/mp/appmsg_comment?action=getcomment&scene=0&__biz={0}&appmsgid={1}&idx=2&comment_id={2}&offset=0&limit=100&uin=777&key=777&pass_ticket={3}&wxtoken=777&devicetype=android-26&clientversion=2607033b&appmsg_token={4}&x5=1&f=json'.format(
            self.biz, app_msg_id, comment_id, self.pass_ticket, appmsg_token)
        resp = requests.get(api, headers=self.headers).json()
        ret, status = resp['base_resp']['ret'], resp['base_resp']['errmsg']
        if ret =='0' or status == 'ok':
            elected_comment = resp['elected_comment']
            for comment in elected_comment:
                content_id = comment.get('content_id') # 評論ID
                nick_name = comment.get('nick_name')  # 評論人昵稱
                like_num = comment.get('like_num')     # 點贊
                comment_time = datetime.fromtimestamp(comment.get('create_time'))  # 評論時間
                content = comment.get('content')       # 評論內(nèi)容
                #print("評論內(nèi)容文章:",article_id,nick_name)
                self.comment_to_mysql(article_id,content_id,comment_time,nick_name,like_num,content)
 
 
    def create_article_table(self):
        sql1 = 'drop table if exists mnyd_article;'
        sql2 = 'create table mnyd_article(No INT(11) NOT NULL AUTO_INCREMENT,msg_id VARCHAR(15),article_id VARCHAR(15),post_time timestamp(2),title VARCHAR(200),digest VARCHAR(200),article_url varchar(300),PRIMARY KEY (No));'
        self.cursor.execute(sql1)
        self.cursor.execute(sql2)
        self.db.commit()
 
    def article_to_mysql(self,msg_id, article_id,title,digest,article_url,post_time):
        sql = "insert into mnyd_article(msg_id,article_id,title,digest,article_url,post_time) values('%s','%s','%s','%s','%s','%s')" % (msg_id,article_id,title, digest,article_url,post_time)
        try:
            # 使用 cursor() 方法創(chuàng)建一個游標(biāo)對象 cursor
            self.cursor.execute(sql)
        except Exception as e:
            # 發(fā)生錯誤時回滾
            self.db.rollback()
            print(str(e))
        else:
            self.db.commit()  # 事務(wù)提交
            print('事務(wù)處理成功')
 
 
 
    def create_comment_table(self):
        sql1 = 'drop table if exists mnyd_comment;'
        sql2 = "create table mnyd_comment(No INT(11) NOT NULL AUTO_INCREMENT,article_id VARCHAR(15),content_id VARCHAR(20),comment_time timestamp(2),nick_name VARCHAR(50),like_num int,content varchar(1000),PRIMARY KEY (No)) COLLATE='utf8mb4_unicode_ci';"
        self.cursor.execute(sql1)
        self.cursor.execute(sql2)
        self.db.commit()
 
    def comment_to_mysql(self,article_id,content_id,comment_time,nick_name,like_num,content):
        sql = "insert into mnyd_comment(article_id,content_id,comment_time,nick_name,like_num,content) values('%s','%s','%s','%s','%i','%s')" % (article_id,content_id,comment_time, nick_name,like_num,content)
        try:
            # 使用 cursor() 方法創(chuàng)建一個游標(biāo)對象 cursor
            self.cursor.execute(sql)
        except Exception as e:
            # 發(fā)生錯誤時回滾
            self.db.rollback()
            print(str(e))
        else:
            self.db.commit()  # 事務(wù)提交
            print('事務(wù)處理成功')
 
 
 
if __name__ == '__main__':
    #  ** 以下幾個字段需要替換為自己需要的,每個公眾號都是不同的
    biz = 'MzIwNTc4NTEwOQ=='  # "碼農(nóng)有道公眾號"   mnyd_article  mnyd_comment
    pass_ticket = 'ZS3nqLX1df5GhZ+zf/t0FYyf7Nfp52yUJ+PuyJUKvQtyln78R3QzBU21Xo528IE+'
    app_msg_token = '986_G0Sy%252FL2pNlAGA9PIXcqTRipxsKaGLurexidEyg~~'     # 歷史文章
    wap_sid2 = 'CL3qgfIFElxMOFBzZ2dZOHQ1WTcxamRQLXUyMGFiU0tvNkZzUEJmRURhZmtJTkhLcEtYWU9rNm5WYmUtd29qd3Q3UmVqbmpZXzFxS21GMG13amVjM1NEaUVPajZNZG9EQUFBfjDH8K3gBTgNQAE='
    cookie = 'wxuin=1581282621; version=2607033b; pass_ticket={}; wap_sid2={}'.format(pass_ticket, wap_sid2)

    # 以上信息不同公眾號每次抓取都需要借助抓包工具做修改
    wxarticles = wechatArticle(biz, pass_ticket, app_msg_token, cookie)
    wxarticles.create_article_table()         # 創(chuàng)建數(shù)據(jù)庫表記錄文章
    wxarticles.create_comment_table()         # 創(chuàng)建數(shù)據(jù)庫表記錄評論
    wxarticles.get_article_list()              # 開始爬取文章和評論

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內(nèi)容

  • # Python 資源大全中文版 我想很多程序員應(yīng)該記得 GitHub 上有一個 Awesome - XXX 系列...
    小邁克閱讀 3,016評論 1 3
  • 原文鏈接: http://www.lxweimin.com/p/9c6ae64a1bd7 GitHub 上有一個 A...
    李紹俊閱讀 6,116評論 0 92
  • 一、Python簡介和環(huán)境搭建以及pip的安裝 4課時實驗課主要內(nèi)容 【Python簡介】: Python 是一個...
    _小老虎_閱讀 5,777評論 0 10
  • python 也是很值得學(xué)習(xí)的一門工具。學(xué)好python和R。 1環(huán)境管理 管理 Python 版本和環(huán)境的工具 ...
    Liam_ml閱讀 4,840評論 1 51
  • 今天一整天看了一大堆的驚池故事,一開始還覺得有幾個故事很驚艷,到后來的幾個開始感覺雷同,語調(diào)風(fēng)格類似,故事...
    蔭霓閱讀 347評論 0 0