天天看點

python爬取“微網誌”移動端評論資料目的實作過程

目的

爬取微網誌移動端的評論資料(如下圖),然後将資料儲存到.txt檔案和.xlsl檔案中。

python爬取“微網誌”移動端評論資料目的實作過程

實作過程

實作的方法很簡單,就是模拟浏覽器發送ajax請求,然後擷取後端傳過來的json資料。

一、找到擷取評論資料的ajax請求

按下F12,打開控制台,找到以下請求

python爬取“微網誌”移動端評論資料目的實作過程

以 https://m.weibo.cn/detail/4467454577673256 為例,得到的ajax請求是這樣的:

https://m.weibo.cn/comments/hotflow?id=4467454577673256&mid=4467454577673256&max_id_type=0

然後,我們往下滾動螢幕,再觀察幾個擷取評論資料的ajax請求(篇幅有限,隻看兩個),看有什麼規律。

https://m.weibo.cn/comments/hotflow?id=4467454577673256&mid=4467454577673256&max_id=142552137895298&max_id_type=0
https://m.weibo.cn/comments/hotflow?id=4467454577673256&mid=4467454577673256&max_id=139116183376416&max_id_type=0

可以看到這幾個ajax都有幾個共同的部分:

  • https://m.weibo.cn/comments/hotflow?
  • id=4467454577673256
  • mid=4467454577673256
  • max_id_type=0

其中,id和mid的數值為微網誌連結的最後一串數字,max_id_type都為0(但事實上,不總為0,後面會講)。到這一步,我們可以模拟擷取評論資料的第一個ajax請求了,因為它就是由上面這些組成的。而後面的那些ajax請求,則是多了一個max_id參數。經過觀察,該參數是從前一個ajax請求傳回的資料中得到的。以前面第二個ajax請求為例:

python爬取“微網誌”移動端評論資料目的實作過程

該請求中的max_id參數就是由從第一個ajax請求的max_id字段中得到。是以,後續的操作就是:

  1. 根據baseurl,id,mid,擷取第一個ajax請求中的資料,然後将有用的評論資訊資料儲存到數組中,并傳回一個max_id字段。
  2. 模拟浏覽器,根據前面獲得的max_id字段,以及baseurl,id,mid發送下一個ajax請求,将評論資料結果append到前一次的結果中。
  3. 循環第2步max次,其中max為發送ajax的次數(好像是錯的,循環次數應該是≤max,自行列印試試),該字段由上面截圖的max字段可得,初始值為1。
  4. 最終,将評論資料結果儲存到.txt和.xlsx檔案中。

二、擷取ajax請求的資料

擷取資料的方式有很多,我選擇了request.get()來擷取。

web_data = requests.get(url, headers=headers,timeout=5)
js_con = web_data.json()
           

使用以上代碼,我們可以很輕松的擷取第一個請求的資料。但是到擷取第二第三個請求的資料時,就會報錯。原因是沒有加入Cookies。于是乎,我就在Request Headers中找含有Coookies的資料資訊,結果全部檔案找了一遍都沒有。是以,我決定把微網誌站點的所有Cookies字段值都敲進去。如下。

python爬取“微網誌”移動端評論資料目的實作過程

是以,解決了cookie問題之後,我們測試一下看是否能擷取後面所有ajax請求的資料。答案是:no.

經無數次測試,在爬取第17次請求的資料時,總是抛出異常。而原因則是我們前面所提及的,max_id_type不總是等于0,當發送第17次請求時max_id_type等于1。

python爬取“微網誌”移動端評論資料目的實作過程

于是到這一步,我們基本就可以爬取所有的資料了。但還是有一個小問題。就是有些微網誌的評論開啟了精選模式,隻顯示回複數最多的幾個評論。比如這個示例:

python爬取“微網誌”移動端評論資料目的實作過程

雖然上面寫着有5757條評論,但是後端隻傳回17條資料到前端。是以,如果按照前面的思維,循環max次發送ajax請求的話是會報錯的,是以解決該問題的方法就是判斷該ajax請求傳回的資料中是否包含{ok: 0}(其他有效的傳回結果都為ok:1)。如果有,則說明這個請求及之後的請求是無效的,不用一直循環下去,直接将結果儲存到.txt和.xlsx檔案即可。

三、将資料儲存到txt和excel中

将資料導出到這兩種檔案的代碼,網上應有盡有,這裡不加贅述。但是,這裡要說明的是:因為要存儲多個微網誌的評論資料,是以excel中要分不同sheet來存儲;而txt中的資料僅僅是為了随便看看,是以微網誌的評論資料都追加到這裡面。

python爬取“微網誌”移動端評論資料目的實作過程
python爬取“微網誌”移動端評論資料目的實作過程

四、源代碼

comment.py(主檔案)

import requests
import pandas as pd
import time
import openpyxl #導出excel需要用到
from config import headers,url,Cookie,base_url,weiboComment,excel_name,txt_name

#将中國标準時間(Sat Mar 16 12:12:03 +0800 2019)轉換成年月日
def formatTime(time_string, from_format, to_format='%Y.%m.%d %H:%M:%S'):
    time_struct = time.strptime(time_string,from_format)
    times = time.strftime(to_format, time_struct)
    return times

# 爬取第一頁的微網誌評論
def first_page_comment(weibo_id, url, headers):
    try:
        url = url + str(weibo_id) + '&mid=' + str(weibo_id) + '&max_id_type=0'
        web_data = requests.get(url, headers=headers,cookies = Cookie,timeout=20)
        js_con = web_data.json()
        # 擷取連接配接下一頁評論的max_id
        max_id = js_con['data']['max_id']
        max = js_con['data']['max']
        comments_list = js_con['data']['data']
        for commment_item in comments_list:
            Obj =  {
                'commentor_id':commment_item['user']['id'],
                'commentor_name':commment_item['user']['screen_name'],
                'commentor_blog_url':commment_item['user']['profile_url'],
                'comment_id':commment_item['id'],
                'comment_text':commment_item['text'],
                'create_time':formatTime(commment_item['created_at'],'%a %b %d %H:%M:%S +0800 %Y','%Y-%m-%d %H:%M:%S'),
                'like_count':commment_item['like_count'],
                'reply_number':commment_item['total_number'],
                'full_path':base_url+str(weibo_id),
                'max_id': max_id,
                'max':max
            }
            commentLists.append(Obj)
        print("已擷取第1頁的評論")
        return commentLists
    except Exception as e:
        print("遇到異常")
        return []

#運用遞歸思想,爬取剩餘頁面的評論。因為後面每一頁的url都有一個max_id,這隻有從前一個頁面傳回的資料中擷取。
def orther_page_comments(count,weibo_id, url, headers,max,max_id):
    if count<=max:
        try:
            if count<15:
                urlNew = url + str(weibo_id) + '&mid='+ str(weibo_id) + '&max_id=' + str(max_id) + '&max_id_type=0'
            else:
                urlNew = url + str(weibo_id) + '&mid=' + str(weibo_id) + '&max_id=' + str(max_id) + '&max_id_type=1'
            web_data = requests.get(url=urlNew, headers=headers,cookies = Cookie,timeout=10)
            #成功擷取資料了,才執行下一步操作
            if web_data.status_code == 200:
                js_con = web_data.json()
                # print('js_con:', js_con)
                #評論開啟了精選模式,傳回的資料為空
                if js_con['ok']!=0:
                    # 擷取連接配接下一頁評論的max_id
                    max_id = js_con['data']['max_id']
                    max = js_con['data']['max']
                    comments_list = js_con['data']['data']
                    # print('comments_list:',comments_list)
                    for commment_item in comments_list:
                        Obj =  {
                            'commentor_id':commment_item['user']['id'],
                            'commentor_name':commment_item['user']['screen_name'],
                            'commentor_blog_url':commment_item['user']['profile_url'],
                            'comment_id':commment_item['id'],
                            'comment_text':commment_item['text'],
                            'create_time':formatTime(commment_item['created_at'],'%a %b %d %H:%M:%S +0800 %Y','%Y-%m-%d %H:%M:%S'),
                            'like_count':commment_item['like_count'],
                            'reply_number':commment_item['total_number'],
                            'full_path':base_url+str(weibo_id),
                            'max_id': max_id,
                            'max':max
                        }
                        commentLists.append(Obj)
                    count += 1
                    print("已擷取第" + str(count+1) + "頁的評論。")
                    orther_page_comments(count,weibo_id,url,headers,max,max_id)#遞歸
                    return commentLists
                else:
                    return []
        except Exception as e:
            if count==1:
                print("遇到異常,爬蟲失敗") #假設連第一條資料都沒有爬到,我就認為是爬蟲失敗
    else:
        return

#将資料儲存到excel中的不同sheet中
def export_excel(exportArr,id,sheetName):
     #建立sheet
     # wb = openpyxl.load_workbook(excel_name)
     # wb.create_sheet(title=sheetName, index=0)
     # wb.save(excel_name)

     #将資料儲存到sheet中
     pf = pd.DataFrame(exportArr)     #将字典清單轉換為DataFrame
     order = ['comment_id','commentor_name','commentor_id','commentor_blog_url','comment_text','create_time','like_count','reply_number','full_path']     #指定字段順序
     pf = pf[order]
     #将列名替換為中文
     columns_map = {
          'comment_id':'comment_id',
          'commentor_name':'評論者名字',
          'commentor_id':'評論者id',
          'commentor_blog_url':'評論者的微網誌首頁',
          'comment_text':'評論内容',
          'create_time':'釋出時間',
          'like_count':'點贊數',
          'reply_number':'回複數',
          'full_path':'微網誌url',
     }
     pf.rename(columns=columns_map, inplace=True)
     pf.fillna(' ',inplace = True)     # 替換空單元格
     pf.to_excel(file_path,encoding = 'utf-8',index = False,sheet_name=sheetName)     #輸出

     print('----------第',id,'篇微網誌的評論已經儲存了---------------')
     return 'true'

#将資料儲存到txt檔案中
def export_txt(list,txtId):
    arr = [str(txtId),'   ',list['full_path'],'   ',list['commentor_name']]
    commentorNameMaxLen = 20 #假設最大的長度為20,不足20的以空格代替,確定長度一緻,避免參差不齊
    lenGap = commentorNameMaxLen - len(list['commentor_name'])
    for i in range(lenGap):
        arr.append('-')
    arr.append(list['comment_text'])
    arr.append('\n') #每一行結束要換行
    file_handle.writelines(arr)

if __name__ == "__main__":
    output = []
    commentLists = []  # 初始化存儲一個微網誌評論數組
    weibo_comment = weiboComment

    file_path = pd.ExcelWriter(excel_name)  # 指定生成的Excel表格名稱

    txt_id = 1  # 用于記錄txt資料的id
    file_handle = open(txt_name, mode='w',encoding='utf-8')  # 打開txt檔案
    file_handle.writelines(['id    ','微網誌連結                          ','評論者','                              ','評論内容\n']) #寫入頭部的字段名字

    #存儲每一篇微網誌的評論資料
    for ind,item in enumerate(weibo_comment):
        output = first_page_comment(item['weibo_id'], url, headers)
        if len(output)>0:
            maxPage = output[-1]['max']
            maxId =output[-1]['max_id']
            #如果結果不隻一頁,就繼續爬
            if(maxPage!=1):
                ans = orther_page_comments(0,item['weibo_id'], url, headers,maxPage,maxId)
                # 如果評論開啟了精選模式,最後一頁傳回的資料是為空的
                if ans!=[]:
                    bool = export_excel(ans,item['id'],item['sheet_name'])
                else:
                    bool = export_excel(commentLists,item['id'],item['sheet_name'])

                if bool=='true':
                    commentLists = [] #将存儲的資料置0
                    for list in ans:
                        txt_id = txt_id + 1  # 用于記錄txt資料的id
                        export_txt(list, txt_id)
            else:
                print('----------------該微網誌的評論隻有1頁-----------------')

    file_path.save()    #儲存到表格
    file_handle.close() #儲存到txt
           

config.py

base_url = 'https://m.weibo.cn/detail/'
url = 'https://m.weibo.cn/comments/hotflow?id='

excel_name = r'weibo_comments.xlsx'
txt_name = 'weibo_comments.txt'

# 參考代碼:https://www.cnblogs.com/pythonfm/p/9056461.html
ALF = 1583630252
MLOGIN = 1
M_WEIBOCN_PARAMS = 'oid%3D4469046194244186%26luicode%3D10000011%26lfid%3D102803%26uicode%3D10000011%26fid%3D102803'
SCF = 'AjheAPuZRqxmyLT-kTVnBXGduebXE6nZGT5fS8_VPbfADyWHQ_WyoRzZqAJNujugOFYP1tUivrlzK2TGTx83_Qo.'
SSOLoginState = 1581038313
SUB = '_2A25zOMq5DeRhGeNM6FUX8S_EzDqIHXVQwtbxrDV6PUJbktAKLVPhkW1NTjKs6wgXZoFv2vqllQWpcwE-e9-8LlMs'
SUBP = '0033WrSXqPxfM725Ws9jqgMF55529P9D9W58TWlXMj17lMMvjhSsjQ1p5JpX5K-hUgL.Fo-Ee0MceK2RS0q2dJLoIEXLxKqLBozL1h.LxKML1-BLBK2LxKML1-2L1hBLxK-LBKqL12BLxK-LBKqL12Bt'
SUHB = '0BLYTPzIKSGsDo'
WEIBOCN_FROM = 1110006030
XSRF_TOKEN = '5dcf70'
_T_WM = 64204543757
Cookie = {
    'Cookie': 'ALF={:d};MLOGIN={:d};M_WEIBOCN_PARAMS={};SCF={};SSOLoginState={:d};SUB={};SUBP={};SUHB={};WEIBOCN_FROM={:d};XSRF-TOKEN={};_T_WM={:d};'.format(
        ALF,
        MLOGIN,
        M_WEIBOCN_PARAMS,
        SCF,
        SSOLoginState,
        SUB,
        SUBP,
        SUHB,
        WEIBOCN_FROM,
        XSRF_TOKEN,
        _T_WM
    )
}

headers = {
    'Sec-Fetch-Mode': 'cors',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
    'X-Requested-With': 'XMLHttpRequest',  # 通過ajax請求形式擷取資料
    'X-XSRF-TOKEN': 'aa8bed',
    'Accept': 'application/json, text/plain, */*'
}


# 資料id号,要爬取的微網誌的id号,以及導出到excel對應的sheet名
weiboComment = [{
    'id':1,
    'weibo_id': 4349331148136901,
    'sheet_name': 'file_tab1',
},{
    'id':2,
    'weibo_id': 4349336798569857,
    'sheet_name': 'file_tab2',
},{
    'id':3,
    'weibo_id': 4349342632452485,
    'sheet_name': 'file_tab3',
},{
    'id':4,
    'weibo_id': 4349359489249263,
    'sheet_name': 'file_tab4',
},{
    'id':5,
    'weibo_id': 4349367202366649,
    'sheet_name': 'file_tab5',
},{
    'id':6,
    'weibo_id': 4349409263609558,
    'sheet_name': 'file_tab6',
},{
    'id':7,
    'weibo_id': 4349473562085041,
    'sheet_name': 'file_tab7',
},{
    'id':8,
    'weibo_id': 4349476527153453,
    'sheet_name': 'file_tab8',
},{
    'id':9,
    'weibo_id': 4349484396400084,
    'sheet_name': 'file_tab9',
},{
    'id':10,
    'weibo_id': 4349520848132903,
    'sheet_name': 'file_tab10',
},{
    'id':11,
    'weibo_id': 4349719763185960,
    'sheet_name': 'file_tab11',
},{
    'id':12,
    'weibo_id': 4349801526543328,
    'sheet_name': 'file_tab12',
},{
    'id':13,
    'weibo_id': 4350037775161542,
    'sheet_name': 'file_tab13',
},{
    'id':14,
    'weibo_id': 4350053403309300,
    'sheet_name': 'file_tab14',
},{
    'id':15,
    'weibo_id': 4350126740919864,
    'sheet_name': 'file_tab15',
},{
    'id':16,
    'weibo_id': 4350129907409012,
    'sheet_name': 'file_tab16',
},{
    'id':17,
    'weibo_id': 4350130469806786,
    'sheet_name': 'file_tab17',
},{
    'id':18,
    'weibo_id': 4350133967955764,
    'sheet_name': 'file_tab18',
},{
    'id':19,
    'weibo_id': 4350135909606542,
    'sheet_name': 'file_tab19',
},{
    'id':20,
    'weibo_id': 4350218999265612,
    'sheet_name': 'file_tab20',
},{
    'id':21,
    'weibo_id': 4350440310723864,
    'sheet_name': 'file_tab21',
},{
    'id':22,
    'weibo_id': 4350520937742523,
    'sheet_name': 'file_tab22',
},{
    'id':23,
    'weibo_id': 4350785468613341,
    'sheet_name': 'file_tab23',
},{
    'id':24,
    'weibo_id': 4350785615363253,
    'sheet_name': 'file_tab24',
},{
    'id':25,
    'weibo_id': 4350789927730012,
    'sheet_name': 'file_tab25',
},{
    'id':26,
    'weibo_id': 4350789751053448,
    'sheet_name': 'file_tab26',
},{
    'id':27,
    'weibo_id': 4350780188153079,
    'sheet_name': 'file_tab27',
},{
    'id':28,
    'weibo_id': 4350791797481716,
    'sheet_name': 'file_tab28',
},{
    'id':29,
    'weibo_id': 4350797737493161,
    'sheet_name': 'file_tab29',
},{
    'id':30,
    'weibo_id': 4350798441501055,
    'sheet_name': 'file_tab30',
},{
    'id':31,
    'weibo_id': 4350800991931397,
    'sheet_name': 'file_tab31',
},{
    'id':32,
    'weibo_id': 4350974611001741,
    'sheet_name': 'file_tab32',
},{
    'id':33,
    'weibo_id': 4351283193709752,
    'sheet_name': 'file_tab33',
}]
           

github項目連結