前言
記得半個月之前的一晚,媳婦跟jb說,你看,蒼老師發了條微博,內容為69,後來微博官方關閉了該條微博的評論功能~
雖然不知道69是什麼意義,但是看評論,總感覺是要開車了~
聽說,蒼老師是90後啟蒙的一代,雖然沒有經歷過,但依然心存敬佩,於是乎,就像把蒼老師的微博內容都爬出來,看看老師都發了些什麼~PC找內容
開啟連結:
https://weibo.com/u/1739928273?refer_flag=1001030101_&is_all=1#_rnd1530254292380
開啟後就是蒼老師的微博連結,可以看到,下面就是蒼老師釋出的微博拉,而內容就是我們想要的東西~
像微博這種大廠,想都不用想就知道解析html獲取資料這條路是行不通的,那我們F12 重新整理下網頁,看看請求?
嗯,好多js跟css,那我們一個一個看,看看能不能找到有用的資訊;(5分鐘過去了)尼瑪,怎麼一條微博動態都沒看到,怎麼獲取?
嘗試多次,依然找不到解決方案,欲想放棄,此時,三三同學說,用wap版,微博有介面獲取!!!
轉戰手機版
就這樣,轉戰手機版,手機版蒼老師連結如下:https://m.weibo.cn/u/1739928273;
老規矩,F12重新整理網頁,然後把請求一條一條過,結果發現一個玩意:
比起PC版,手機版終於看到有點類似資料的東西了,那我們開啟第一條看看~
這裡面的text不就是跟蒼老師的第一條微博是一樣的嗎?get~這就是我們需要的東西啦~那我們點選headers,把request url拿出來分析下:
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,不信, 你隨便改下試試,可能會跳到其他老師那呢~
這不,簡單把最後2位73改成12,就變成另一位美女了~
貌似跑題了,咳咳,剛剛說到哪~
嗯,知道這幾個引數,沒啥特別的,那我們試試滑動下螢幕,往下拉,拉取更多的資料,最後使用上面的方式,獲取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,現在知道了資料在哪裡,翻頁怎麼弄,那我們就看看請求頭把~
咦,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??這是什麼??
此時,趕緊看看蒼老師的內容:
內容的對應欄位是text,那我們copy去到剛剛那個頁面搜尋下: 都變成了\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)
複製程式碼
執行後,得到的結果是這樣的:
核對了下,跟網頁訪問是一樣的~https://m.weibo.cn/api/container/getIndex?type=uid&value=1739928273&containerid=1076031739928273&page=1
複製程式碼
接下來可以幹嘛?就可以寫正則來匹配啦,先找data,然後找cards,然後再獲取每個cards下面的text;
如果真的如上面說的,馬上寫噼裡啪啦寫正則,就有點衝動了,看下返回結果的格式,感覺是不是像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項:
那我們看看cards裡面的內容,這是第一個:
這是第二個:嗯,有發現不同了嗎?對的,就是有多了個關注XXX的一項,但是這一項,但是這一項不是我們要的,那怎麼搞?
逐個逐個分析,會發現,正常的資料都有這麼一項:
for card in cards:
if card.get("card_type") == 9:
text = card.get("mblog").get("text")
print(text)
複製程式碼
輸出的結果如下:
嗯,內容都獲取到了,但是有奇怪的東西進來了~回頭看了下,並不是程式碼的錯,而且因為釋出的內容有帶圖片或者表情~
這種情況過濾掉就好了~只需要文字~
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>
複製程式碼
結果如下:
這裡有個不解之謎,就是會看到,會有換行,原因是這樣的:
這尼瑪,居然有個換行符??那我們把獲取到的text列印以下~ 我去,這個換行符已經換行了,沒辦法匹配啊~本來還把想把\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
複製程式碼
去掉之後,會發現字元的確不見了,而且的的確不會換行了,問題解決;
kw = urllib.request.quote(text)
old_kw = re.sub("%0A","",kw)
new_kw = urllib.request.unquote(old_kw)
複製程式碼
回到正題,按照上面的程式碼爬下來的東西,好像沒啥問題,但認真一看,咦~
這裡誰說老師下垂了?
這裡,很明顯是之前的正則有問題,那我們重新折騰下正則,此處過程就不說了,很痛苦。。最後改成這樣:
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引數就行了
另外還遇到一個問題:
處理方案,加多一個引數,統計上一次的長度,如果相同,則認為沒有新資料,暫停指令碼處理
資料這多了,還發現這種東西~當然,也是正則相容下就行了~
最終程式碼
結果上面的處理,縫縫補補,最終程式碼如下:
# -*- 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"<.*?>|轉發微博|檢視圖片|檢視動圖|>")
#這裡就是把<>符號內的都匹配出來,正則規則
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
複製程式碼
開啟她的手機版微博主頁
然後把瀏覽器的F12,重新重新整理下網頁,搜尋get關鍵詞,從而獲得value跟containerid,直接填寫到get_Data方法裡面即可~最後的輸出結果如下:
說明
該指令碼可能依賴於網頁結果,一旦網頁結構發生變化,該指令碼即不適用,請了解~
嘗試過20個左右的使用者,均可資料,如遇到問題,請留言告知,謝謝~
感謝
本文感謝三三同學的極力支援,否則如研究PC版,估計就涼了~
小結
本文主要解析怎麼爬取手機版的微博內容,主要原理是解析json,遇到有趣的問題有2個,第一是正則,想獲取什麼,把不需要的處理掉就好了,不然什麼都()去做,太麻煩了~第二,微博的換行符,一開始還想著\n匹配處理,結果發現不行,後來換個角度,弄成編碼的格式就發現問題了;
好了,本文到此,謝謝大家~