JB的Python之旅-爬蟲篇-新浪微博內容爬取

jb發表於2018-06-30

前言

記得半個月之前的一晚,媳婦跟jb說,你看,蒼老師發了條微博,內容為69,後來微博官方關閉了該條微博的評論功能~

JB的Python之旅-爬蟲篇-新浪微博內容爬取

雖然不知道69是什麼意義,但是看評論,總感覺是要開車了~

JB的Python之旅-爬蟲篇-新浪微博內容爬取
聽說,蒼老師是90後啟蒙的一代,雖然沒有經歷過,但依然心存敬佩,於是乎,就像把蒼老師的微博內容都爬出來,看看老師都發了些什麼~

PC找內容

開啟連結:
https://weibo.com/u/1739928273?refer_flag=1001030101_&is_all=1#_rnd1530254292380
開啟後就是蒼老師的微博連結,可以看到,下面就是蒼老師釋出的微博拉,而內容就是我們想要的東西~

JB的Python之旅-爬蟲篇-新浪微博內容爬取

像微博這種大廠,想都不用想就知道解析html獲取資料這條路是行不通的,那我們F12 重新整理下網頁,看看請求?

JB的Python之旅-爬蟲篇-新浪微博內容爬取
嗯,好多js跟css,那我們一個一個看,看看能不能找到有用的資訊;

(5分鐘過去了)尼瑪,怎麼一條微博動態都沒看到,怎麼獲取?

嘗試多次,依然找不到解決方案,欲想放棄,此時,三三同學說,用wap版,微博有介面獲取!!!

轉戰手機版

就這樣,轉戰手機版,手機版蒼老師連結如下:https://m.weibo.cn/u/1739928273;
老規矩,F12重新整理網頁,然後把請求一條一條過,結果發現一個玩意:

JB的Python之旅-爬蟲篇-新浪微博內容爬取

比起PC版,手機版終於看到有點類似資料的東西了,那我們開啟第一條看看~

JB的Python之旅-爬蟲篇-新浪微博內容爬取
這裡面的text不就是跟蒼老師的第一條微博是一樣的嗎?get~這就是我們需要的東西啦~

那我們點選headers,把request url拿出來分析下:

JB的Python之旅-爬蟲篇-新浪微博內容爬取

https://m.weibo.cn/api/container/getIndex?type=uid&value=1739928273&containerid=1076031739928273
複製程式碼

解析這這個url,這url帶有3個引數:

type=uid
value=1739928273
containerid=1076031739928273
複製程式碼

這3個引數,唯一能確定的就是value,為什麼這麼說?回頭看看蒼老師手機版的連結:https://m.weibo.cn/u/1739928273,由此得知,1739928273就是蒼老師微博的ID,不信, 你隨便改下試試,可能會跳到其他老師那呢~

JB的Python之旅-爬蟲篇-新浪微博內容爬取

這不,簡單把最後2位73改成12,就變成另一位美女了~

JB的Python之旅-爬蟲篇-新浪微博內容爬取

貌似跑題了,咳咳,剛剛說到哪~

嗯,知道這幾個引數,沒啥特別的,那我們試試滑動下螢幕,往下拉,拉取更多的資料,最後使用上面的方式,獲取url:

https://m.weibo.cn/api/container/getIndex?type=uid&value=1739928273&containerid=1076031739928273&page=3
複製程式碼

與上面的url不同的是,這裡多了個引數page=3,不用想都知道,這是代表第三頁的意思了~
結果分析,如果是第二頁,page=2,第一頁的話,是不攜帶page引數,但是嘗試把page=0跟page=1兩個情況,返回的資料跟不攜帶page引數是一直的,所以後面就把首頁當做是page=1的情況處理~

ok,現在知道了資料在哪裡,翻頁怎麼弄,那我們就看看請求頭把~

JB的Python之旅-爬蟲篇-新浪微博內容爬取
咦,Provisional headers are shown這是什麼,其他請求內容沒看到?
網上找了說,是這麼解釋:請求的資源可能會被(擴充套件/或其他什麼機制)遮蔽掉。
更詳細的資訊的話,請看:https://segmentfault.com/q/1010000000364871/a-1020000000429614

這個東西對於我們有影響嗎?暫時看是沒有的,從上圖就能看到請求的內容跟攜帶的引數~

那我們開啟看看,下面這條連結是什麼內容?

https://m.weibo.cn/api/container/getIndex?type=uid&value=1739928273&containerid=1076031739928273
複製程式碼

嗯,開啟之後,是這樣的,wtf??這是什麼??

JB的Python之旅-爬蟲篇-新浪微博內容爬取

此時,趕緊看看蒼老師的內容:

JB的Python之旅-爬蟲篇-新浪微博內容爬取
內容的對應欄位是text,那我們copy去到剛剛那個頁面搜尋下:

JB的Python之旅-爬蟲篇-新浪微博內容爬取
都變成了\u6211\u81ea\u5df這種玩意了~

這個問題,之前在寫urllib的時候也說明過:

URL只允許部分ASCLL(數字字母和部分符號),其他的字元(包括漢字)是不符合URL的標準,
所以URL需要對這些字元進行URL編碼,URL編碼的方式是把需要編碼的字元轉化為 %xx 的形式。
通常 URL 編碼是基於 UTF-8 的,函式說明也提及到給予UTF-8進行encode~
複製程式碼

Ok,那就說,提取text的內容就好啦~那我們先寫個請求吧

# -*- coding:utf-8 -*-
import requests

url = "https://m.weibo.cn/api/container/getIndex"
#請求的url

headers = {
    "Host": "m.weibo.cn",
    "Referer": "https://m.weibo.cn/u/1739928273",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
    "Accept":'application/json, text/plain, */*',
    "X-Requested-With":"XMLHttpRequest",
}
#請求頭

params = {
          "type": "uid",
          "value": "1739928273",
          "containerid": "1076031739928273",
          "page": "1"}
#請求攜帶的引數


res = requests.get(url,headers=headers,params=params).content
print(res)
複製程式碼

執行後,得到的結果是這樣的:

JB的Python之旅-爬蟲篇-新浪微博內容爬取
核對了下,跟網頁訪問是一樣的~

https://m.weibo.cn/api/container/getIndex?type=uid&value=1739928273&containerid=1076031739928273&page=1
複製程式碼

接下來可以幹嘛?就可以寫正則來匹配啦,先找data,然後找cards,然後再獲取每個cards下面的text;

JB的Python之旅-爬蟲篇-新浪微博內容爬取

如果真的如上面說的,馬上寫噼裡啪啦寫正則,就有點衝動了,看下返回結果的格式,感覺是不是像json?

"ok":1,"data":{"cardlistInfo":{"containerid":"1076031739928273","v_p":
複製程式碼

沒錯,這就是json,那我們就可以換一種方式處理~

res = requests.get(url,headers=headers,params=params)
cards  = res.json().get("data").get("cards")
#獲取carads下的所有項
複製程式碼

獲取的就是下面這個截圖的所有cards項:

JB的Python之旅-爬蟲篇-新浪微博內容爬取

那我們看看cards裡面的內容,這是第一個:

JB的Python之旅-爬蟲篇-新浪微博內容爬取
這是第二個:

JB的Python之旅-爬蟲篇-新浪微博內容爬取

嗯,有發現不同了嗎?對的,就是有多了個關注XXX的一項,但是這一項,但是這一項不是我們要的,那怎麼搞?
逐個逐個分析,會發現,正常的資料都有這麼一項:

JB的Python之旅-爬蟲篇-新浪微博內容爬取
那我們就拿這項做判斷吧,先判斷這項是否等於9,然後再獲取mblog,再獲取text裡面的內容,於是乎就有了下面的程式碼:

for card in cards:
    if card.get("card_type") == 9:
        text = card.get("mblog").get("text")
        print(text)
複製程式碼

輸出的結果如下:

JB的Python之旅-爬蟲篇-新浪微博內容爬取
嗯,內容都獲取到了,但是有奇怪的東西進來了~

回頭看了下,並不是程式碼的錯,而且因為釋出的內容有帶圖片或者表情~

JB的Python之旅-爬蟲篇-新浪微博內容爬取

這種情況過濾掉就好了~只需要文字~

pattern = re.compile(r"(.*?)<span.*>(.*?)")
text = re.sub(pattern,"",text)
複製程式碼

從上面可以看到問題例子如下,那我們只需要把裡面的內容都幹掉就好了~

啊啊啊啊啊啊啊啊啊
<span class = "url-icon"><img alt = [允悲] src = "https://user-gold-cdn.xitu.io/2018/6/29/1644b1bf47628785?w=32&h=32&f=png&s=2591" style = "width:1em; height:1em;"/></span> 
複製程式碼

結果如下:

JB的Python之旅-爬蟲篇-新浪微博內容爬取

這裡有個不解之謎,就是會看到,會有換行,原因是這樣的:

JB的Python之旅-爬蟲篇-新浪微博內容爬取
這尼瑪,居然有個換行符??那我們把獲取到的text列印以下~

JB的Python之旅-爬蟲篇-新浪微博內容爬取
我去,這個換行符已經換行了,沒辦法匹配啊~本來還把想把\n換行符先幹掉了,這個就是這個換行符的來源,怎麼辦?

之前在介紹urllib的時候提及有,urllib有一個quote的方法,函式說明提及到給予UTF-8進行encode;

import urllib
kw = urllib.request.quote("很緊張啊啊啊啊↵<s")
print(kw)
複製程式碼

輸出的內容長這樣的:

%E5%BE%88%E7%B4%A7%E5%BC%A0%E5%95%8A%E5%95%8A%E5%95%8A%E5%95%8A%E2%86%B5%3Cs
複製程式碼

那我們把中文都去掉,只留↵看看?

%E2%86%B5
複製程式碼

得到的結果就是這樣的,OK,那假如我們把上面這串結果匹配成空格,是不是就能解決問題?

但實際嘗試了下,是不行的,那我們就把資料打出來:

啊啊啊啊啊啊啊啊啊
<s
複製程式碼

最後會發現%0A才是那個回車符

%8A%0A%3Cspan
複製程式碼

去掉之後,會發現字元的確不見了,而且的的確不會換行了,問題解決;

JB的Python之旅-爬蟲篇-新浪微博內容爬取

    kw = urllib.request.quote(text)
    old_kw = re.sub("%0A","",kw)
    new_kw = urllib.request.unquote(old_kw)
複製程式碼

回到正題,按照上面的程式碼爬下來的東西,好像沒啥問題,但認真一看,咦~
這裡誰說老師下垂了?

JB的Python之旅-爬蟲篇-新浪微博內容爬取
對應的微博內容:

JB的Python之旅-爬蟲篇-新浪微博內容爬取

這裡,很明顯是之前的正則有問題,那我們重新折騰下正則,此處過程就不說了,很痛苦。。最後改成這樣:

pattern = re.compile(r"<.*?>")
複製程式碼

意思就是把<>符號內的內容都去掉,得出的結果:

//@李太白的表哥:老師…你好像下垂了……
檢視圖片
複製程式碼

這裡可以看到,檢視圖片也是多餘的,那我們也去掉,包括有一些是轉發微博的,也都幹掉吧,就成這樣了~

pattern = re.compile(r"<.*?>|轉發微博|檢視圖片")
複製程式碼

執行下,結果是這樣了,看上去很好:

//@李太白的表哥:老師…你好像下垂了……
複製程式碼

ok,這個是一個頁面的內容抓取,整體程式碼如下:

# -*- coding:utf-8 -*-
import requests
import re
import urllib


url = "https://m.weibo.cn/api/container/getIndex"
#請求的url

headers = {
    "Host": "m.weibo.cn",
    "Referer": "https://m.weibo.cn/u/1739928273",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
    "Accept":'application/json, text/plain, */*',
    "X-Requested-With":"XMLHttpRequest",
}
#請求頭

params = {
          "type": "uid",
          "value": "1739928273",
          "containerid": "1076031739928273",
          "page": "1"}
#請求攜帶的引數

res = requests.get(url,headers=headers,params=params)
cards  = res.json().get("data").get("cards")
#獲取carads下的所有項

for card in cards:
    if card.get("card_type") == 9:
        text = card.get("mblog").get("text")
        # kw = urllib.request.quote(text)
        # old_kw = re.sub("%0A","",kw)
        # new_kw = urllib.request.unquote(old_kw)
        # %0A  這串數字對應的就是這個回車字元
        pattern = re.compile(r"<.*?>|轉發微博|檢視圖片")
        #這裡就是把<>符號內的都匹配出來
        text = re.sub(pattern,"",text)
        print(text)
複製程式碼

其他優化

既然一頁搞定了,那我們要爬多頁,怎麼破?這個很簡單啦,直接改page引數就行了
另外還遇到一個問題:

JB的Python之旅-爬蟲篇-新浪微博內容爬取
假如我們設定爬取1000頁,但是實際上,使用者可能只有100頁的資料,那指令碼還是會一直爬取的~
處理方案,加多一個引數,統計上一次的長度,如果相同,則認為沒有新資料,暫停指令碼處理

資料這多了,還發現這種東西~當然,也是正則相容下就行了~

JB的Python之旅-爬蟲篇-新浪微博內容爬取

最終程式碼

結果上面的處理,縫縫補補,最終程式碼如下:

# -*- coding:utf-8 -*-
import requests
import re
import urllib
import codecs


url = "https://m.weibo.cn/api/container/getIndex"
#請求的url

headers = {
    "Host": "m.weibo.cn",
    "Referer": "https://m.weibo.cn/u/1761379670",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
}
#請求頭

params = {
          "type": "uid",
          "value": "{uid}",
          "containerid": "{containerid}",
          "page":"{page}"}
#請求攜帶的引數


def get_Data(uid="3303658163", containerid="1005053303658163"):
    total = 3000  #打算爬取的頁數,比如100頁
    content = []  #存放獲取到的微博正文內容
    page =1  #頁碼,從第一頁開始算
    last_length = 0 #上一個的內容長度,用於對比上一次的總體內容長度跟這次是否一致,如果一致,則認為沒有新資料,停止指令碼處理
    for i in range(total):
        params["page"] = str(page)
        params['uid'] = uid
        params['containerid'] = str(containerid)
        res = requests.get(url, headers=headers, params=params)
        print(res.json().get("data"))
        cards = res.json().get("data").get("cards")
        # 獲取carads下的所有項


        for card in cards:
            if card.get("card_type") == 9:
                text = card.get("mblog").get("text")
                kw = urllib.request.quote(text)
                old_kw = re.sub("%0A","",kw)
                new_text = urllib.request.unquote(old_kw)
                # %0A  這串數字對應的就是這個回車字元
                pattern = re.compile(r"<.*?>|轉發微博|檢視圖片|檢視動圖|&gt;")
                #這裡就是把<>符號內的都匹配出來,正則規則
                text = re.sub(pattern,"",new_text)
                content.append(text)
        page +=1
        if (len(content) == last_length):
            print("已經獲取不到更多內容,指令碼暫停處理")
            break
        else:
            last_length = len(content)
            print("抓取第{page}頁,目前總共抓取了 {count} 條微博".format(page=page, count=len(content)))
            with codecs.open('jb.txt', 'w', encoding='utf-8') as f:
                f.write("\n".join(content))


if __name__ == '__main__':
    get_Data("1761379670", "1005051761379670")
複製程式碼

功能介紹的話,一路看下來就很明朗了,一句話就是,解析json而已;

可能有同學問,上面的程式碼如何使用?直接copy出來執行即可,如果想爬某人的資訊,比如吉澤明步:

https://m.weibo.cn/u/2360092592?uid=2360092592&luicode=10000011&lfid=100103
type%3D1%26q%3D%E5%90%89%E6%B3%BD%E6%98%8E%E6%AD%A5
複製程式碼

開啟她的手機版微博主頁

JB的Python之旅-爬蟲篇-新浪微博內容爬取
然後把瀏覽器的F12,重新重新整理下網頁,搜尋get關鍵詞,從而獲得value跟containerid,直接填寫到get_Data方法裡面即可~

JB的Python之旅-爬蟲篇-新浪微博內容爬取

最後的輸出結果如下:

JB的Python之旅-爬蟲篇-新浪微博內容爬取

說明

該指令碼可能依賴於網頁結果,一旦網頁結構發生變化,該指令碼即不適用,請了解~
嘗試過20個左右的使用者,均可資料,如遇到問題,請留言告知,謝謝~

感謝

本文感謝三三同學的極力支援,否則如研究PC版,估計就涼了~

小結

本文主要解析怎麼爬取手機版的微博內容,主要原理是解析json,遇到有趣的問題有2個,第一是正則,想獲取什麼,把不需要的處理掉就好了,不然什麼都()去做,太麻煩了~第二,微博的換行符,一開始還想著\n匹配處理,結果發現不行,後來換個角度,弄成編碼的格式就發現問題了;

好了,本文到此,謝謝大家~

JB的Python之旅-爬蟲篇-新浪微博內容爬取

相關文章