Python抓取分析「創造101 」菊姐微博

前一段時間,創造101很火,這個火是能夠理解的,畢竟中國首部女團節目。可是還有一我的不知道爲啥忽然也火了,那就是咱們的菊姐。關於菊姐爲何火,網上已經有不少發文了,這裏就再也不贅述了。
咱們抓取了菊姐的最新微博評論,將評論分詞之後製做成以下詞雲圖。html

image

image

image

這裏的海外指大陸+港澳臺之外的其餘全部地方。
除海外用戶之外就北上廣的用戶最多了,這些地方的互聯網用戶基數原本就大。python

image

由於北京上海比較特殊,北京上海的一些區至關於北京上海這兩個省下面的市區,因此你會看到一些北京上海的區域也進入了榜單,好比說朝陽羣衆。json

image

可是你們都很愛學習,都想要代碼學習學習,因此今天就專門來一篇講講代碼。api

在開始具體的代碼講解以前,我須要說明一下關於菊粉人數中摩羯座人數最多這個結論的一些爭議,有人評論說微博用戶若是不設置年齡的話,默認就是1月1,也就是摩羯座,因此摩羯座人數比較多。先來看兩張圖:數組

image

經過上面幾張截圖來看的話,若是未設置年齡時,並不會默認顯示成摩羯座,因此應該就不存在你們說的那種狀況。bash

還有所在地和家鄉是能夠選擇則其餘的,性別、年齡、星座是不能夠選擇其餘。咱們本次就是要獲取這幾個字段。cookie

本篇主要分爲三個部分:
數據獲取
數據預處理
可視化圖表製做
數據抓取app

先講講數據抓取的邏輯,最終目的就是要找到pick王菊的人都是哪些人,剛開始想的是直接抓取王菊的粉絲列表,可是後來發現微博數據有限制,只能抓取少許的粉絲列表,因此這個方案行不通,只能換下一個。函數

在小歪大佬的建議下,決定抓取王菊微博留言下面的用戶,由於這些用戶是和王菊有過互動的,要比那些只關注沒有互動(這裏的互動只指評論這一動做)的用戶粉的程度要大,更有表明性。學習

因此最終的一個數據抓取思路就是:經過獲取微博評論下的用戶,而後進而獲取用戶基本信息,具體實現代碼以下:
獲取每條微博評論url
咱們先隨便點擊一條微博的評論進去,看看咱們要的字段都在哪裏。

image

能夠看到,有評論text,以及每一條text對應的user_id,找到了字段位置,咱們再來看看這些字段對應url是什麼,有什麼規律。

image

經過查看這個url`js
m.weibo.cn/api/comment…
`
咱們大概能夠猜出,id前面的部分`js
m.weibo.cn/api/comment…
`
該是全部微博評論都同樣的,id值是惟一的,每個id對應一條微博,而page是表示一條微博的評論存放在多頁裏面,通過驗證確實如此,並且page最大值就是100,100之後就不返回數據了。
因此接下來咱們的目標就是獲取每條微博對應的惟一id值。回到用戶主頁,

image

能夠看到每條微博的發佈時間,以及微博id,也就是隻須要解析用戶主頁url就能夠獲得該用戶的每條微博對應的id值。

url = https://m.weibo.cn/api/container/getIndex?uid=1773294041&luicode=10000011&lfid=100103type%3D1%26q%3D%E7%8E%8B%E8%8F%8A&featurecode=20000320&containerid=1076031773294041
複製代碼

獲取到每條微博的id值之後,咱們就能夠獲取到每條微博評論的url,具體代碼以下:

#導入相關庫
import requests
import json

comment_parameter = []#用來存放weibo_id值
comment_url = []#用來存放weibo_url

#獲取每條微博的id值
url = 'https://m.weibo.cn/api/container/getIndex?uid=1773294041&luicode=10000011&lfid=100103type%3D1%26q%3D%E7%8E%8B%E8%8F%8A&\featurecode=20000320&type=uid&value=1773294041&containerid=1076031773294041'

c_r = requests.get(url)
for i in range(2,11):
    c_parameter = (json.loads(c_r.text)["data"]["cards"][i]["mblog"]["id"])
    comment_parameter.append(c_parameter)

#獲取每條微博評論url
c_url_base = 'https://m.weibo.cn/api/comments/show?id='
for parameter in comment_parameter:
    for page in range(1,101):#提早知道每條微博只可抓取前100頁評論
        c_url = c_url_base + str(parameter) + "&page=" + str(page)
        comment_url.append(c_url)
複製代碼

獲取每一個user_id和comment
上面獲取到每條微博評論的url之後,咱們就能夠直接請求對應的url,而後把user_id和text解析出來便可,實現代碼以下:

user_id = []#用來存放user_id
comment = []#用來存放comment
for url in comment_url:
    u_c_r = requests.get(url)
    try:
        for m in range(0,9):#提早知道每一個url會包含9條用戶信息
            one_id = json.loads(u_c_r.text)["data"]["data"][m]["user"]["id"]
            user_id.append(one_id)
            one_comment = json.loads(u_c_r.text)["data"]["data"][m]["text"]
            comment.append(one_comment)
    except:
        pass
複製代碼

獲取containerid
獲取到了user_id之後,咱們再來看看咱們想要獲取的字段在哪,以下圖,

image

知道了咱們想要獲取的字段在哪之後,再看看這些字段對應的url是什麼?

image

看到這個url之後咱們又能夠猜想,每一個用戶信息對應的url應該只有value&containerid這兩個值是不同的,其餘都是同樣的,經驗證,缺失如此,且value值就是user_id,containerid是另一個惟一值,因此咱們接下來的目標是獲取每一個用戶對應的containerid。具體實現代碼以下:

containerid = []
user_base_url = "https://m.weibo.cn/api/container/getIndex?type=uid&value="
for id in set(user_id):#須要對user_id去重
    containerid_url = user_base_url + str(id)
    try:
        con_r = requests.get(containerid_url)
        one_containerid = json.loads(con_r.text)["data"]['tabsInfo']['tabs'][0]["containerid"]
        containerid.append(one_containerid)
    except:
        containerid.append(0)
複製代碼

獲取用戶基本信息
知道了user_id以及containerid,咱們就能夠惟一肯定一個用戶的基本信息,具體實現代碼以下:

#這裏須要設置headers以及cookie模擬登錄
feature = []#存放用戶基本信息
id_lose = []#存放請求失敗id
user_agent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
headers = {"User-Agent":user_agent}
m = 1
for num in zip(user_id,containerid):
    url = "https://m.weibo.cn/api/container/getIndex?uid="+str(num[0])+"&luicode=10000011&lfid=100103type%3D1%26q%3D&featurecode=20000320&type=uid&value="+str(num[0])+"&containerid="+str(num[1])
    try:
        r = requests.get(url,headers = headers,cookies = cookie)
        feature.append(json.loads(r.text)["data"]["cards"][1]["card_group"][1]["item_content"].split(" "))
        print("成功第{}條".format(m))
        m = m + 1
        time.sleep(1)#設置睡眠一秒鐘,防止被封
    except:
        id_lose.append(num[0])

#將featrue創建成DataFrame結構便於後續分析
user_info = pd.DataFrame(feature,columns = ["性別","年齡","星座","國家城市"])
複製代碼

最後的結果以下表:

image

能夠看到,年齡和星座爲空,並非摩羯座,且當年齡和星座爲空時,所在地就會錯位到年齡列,接下來就作一些數據預處理。

數據清洗
數據清洗邏輯以下:
對於國家列爲空,星座列不空且不包含座字,則認爲是國家城市名,則把星座列賦值給國家城市列
對於國家列爲空,星座列也爲空,年齡列不爲空且不包含歲或座字,則把年齡列賦值給國家城市列
對於星座列爲空,可是年齡列包含座字,則把年齡列賦值給星座列
對於星座列不包含座的,所有賦值爲「未知」
對於年齡列不包含歲的,所有賦值爲「999歲」(爲便於後續好篩選)
對於國家列爲空的,所有賦值爲「其餘」
具體代碼以下:

#數據清洗
user_info1 = user_info[(user_info["性別"] == "男") | (user_info["性別"] == "女")]#去除掉性別不爲男女的部分
user_info1 = user_info1.reindex(range(0,5212))#重置索引


user_index1 = user_info1[(user_info1["國家城市"].isnull() == True)&(user_info1["星座"].isnull() == False)
                         &(user_info1["星座"].map(lambda s:str(s).find("座")) == -1)].index
for index in user_index1:
    user_info1.iloc[index,3] = user_info1.iloc[index,2]

user_index2 = user_info1[((user_info1["國家城市"].isnull() == True)&(user_info1["星座"].isnull() == True)
                          &(user_info1["年齡"].isnull() == False)&(user_info1["年齡"].map(lambda s:str(s).find("歲")) == -1))].index
for index in user_index2:
    user_info1.iloc[index,3] = user_info1.iloc[index,1]

user_index3 = user_info1[((user_info1["星座"].map(lambda s:str(s).find("座")) == -1)&
                          (user_info1["年齡"].map(lambda s:str(s).find("座")) != -1))].index
for index in user_index3:
    user_info1.iloc[index,2] = user_info1.iloc[index,1]

user_index4 = user_info1[(user_info1["星座"].map(lambda s:str(s).find("座")) == -1)].index
for index in user_index4:
    user_info1.iloc[index,2] = "未知"

user_index5 = user_info1[(user_info1["年齡"].map(lambda s:str(s).find("歲")) == -1)].index
for index in user_index5:
    user_info1.iloc[index,1] = "999歲"#便於後續統一處理

user_index6 = user_info1[(user_info1["國家城市"].isnull() == True)].index
for index in user_index6:
    user_info1.iloc[index,3] = "其餘"
複製代碼

圖表製做

主要講講這篇報告中涉及到的圖表的製做,上一篇文章中的圖表我是用的BDP作的,由於BDP作出來的要比python作出來的美觀,並且方便,因此我就用了BDP,這篇主要是講代碼,因此就給你們用python實現一遍。
詞雲圖製做
詞雲圖製做是先把一大段話進行分詞,分紅若干個詞語,而後對詞語進行計數,最後挑選出出現次數比較大的那些詞,繪製在同一張圖上,且出現次數越多,字體顯示越大,最終效果圖以下:

image

固然了,最後結果只是右半部分,左半部分是爲了對比後期PS加上去的。具體實現代碼以下:

import fool
from collections import Counter
from PIL import Image,ImageSequence  
from wordcloud import WordCloud,ImageColorGenerator

#因留言結構比較亂,因此先保存到本地作進一步處理
#刪除掉一些html元素
pd.DataFrame(comment).to_csv(r"C:\Users\zhangjunhong\Desktop\comment.csv")

#處理完之後再次載入進來
comment_data = pd.read_excel(r"C:\Users\zhangjunhong\Desktop\comment.xlsx")

#將數據轉換成字符串
text = (",").join(comment_data[0])

#進行分詞
cut_text = ' '.join(fool.cut(text))

#將分詞結果進行計數
c = Counter(cut_text)
c.most_common(500)#挑選出詞頻最高的500詞

#將結果導出到本地進行再一次清洗,刪除無心義的符號詞
pd.DataFrame(c.most_common(500)).to_excel(r"C:\Users\zhangjunhong\Desktop\fenci.xlsx")

#導入背景圖,這裏選擇菊姐頭像
image = Image.open('C:/Users/zhangjunhong/Desktop/圖片1.png')

#將圖片信息轉換成數組形式
graph = np.array(image) 

#設置詞雲參數 
#參數分別是指定字體、背景顏色、最大的詞的大小、使用給定圖做爲背景形狀 
wc = WordCloud(font_path = "C:\\Windows\\Fonts\\simkai.ttf", background_color = 'White', max_words = 150, mask = graph)  

fp = pd.read_csv(r"C:\Users\zhangjunhong\Desktop\da200.csv",encoding = "gbk")#讀取詞頻文件 
name = list(fp.name)#詞 
value = fp.time#詞的頻率 
dic = dict(zip(name, value))#詞以及詞頻以字典形式存儲 

#根據給定詞頻生成詞雲
wc.generate_from_frequencies(dic)
image_color = ImageColorGenerator(graph)  

plt.imshow(wc)  
plt.axis("off")#不顯示座標軸 
plt.show()

#保存結果到本地
wc.to_file('C:/Users/zhangjunhong/Desktop/wordcloud.jpg')
複製代碼

這裏分詞沒有用jieba分詞,而是用了fool,據稱是最準確的中文分詞包。
餅圖繪製
餅圖就很簡單了,代碼以下:

繪製男女比例的餅圖
user_info1["性別"].value_counts(normalize = True).plot.pie(title = "菊粉男女分佈",autopct='%.2f')
複製代碼

image

柱狀圖繪製
先對年齡進行分區間,而後再進行統計繪製,代碼以下

#將把年齡從字符串變成數字
user_info1["age_1"] = [int(age[:-1]) for age in user_info1["年齡"]]

#對年齡進行分組
bins = (0,10,20,25,30,100,1000)#將年齡進行區間切分
cut_bins = pd.cut(user_info1["age_1"],bins = bins,labels = False)
ax = cut_bins[cut_bins < 5].value_counts(normalize =True).plot.bar(title = "菊粉年齡分佈")#將大於100歲的過濾掉
ax.set_xticklabels(["0-10歲","10-20歲","20-25歲","25-30歲","30+"],rotation = 0)
複製代碼

image

地圖繪製

#導入相關庫
import matplotlib.pyplot as plt
import matplotlib
from matplotlib.patches import Polygon
from mpl_toolkits.basemap import Basemap
from matplotlib.collections import PatchCollection

#將省份和城市進行分列
country_data = pd.DataFrame([country.split(" ") for country in user_info1["國家城市"]],columns = ["省份","城市"])

#將國家和城市與user表合併
user_data = pd.merge(user_info1,country_data,left_index = True,right_index = True,how = "left")

#按省份進行分組計數
shengfen_data = user_data.groupby("省份")["性別"].count().reset_index().rename(columns = {"性別":"人次"})

#須要先對各省份地址進行經緯度解析
#導入解析好的省份經緯度信息
location = pd.read_table(r"C:\Users\zhangjunhong\Desktop\latlon_106318.txt",sep = ",")

#將省份數據和經緯度進行匹配
location_data = pd.merge(shengfen_data,location[["關鍵詞","地址","谷歌地圖緯度","谷歌地圖經度"]],
                    left_on = "省份",right_on = "關鍵詞",how = "left")

#進行地圖可視化
#建立座標軸
fig = plt.figure(figsize=(16,12))
ax  = fig.add_subplot(111)

#須要提早下載中國省份地圖的.shp
#指明.shp所在路徑進行導入
basemap = Basemap(llcrnrlon= 75,llcrnrlat=0,urcrnrlon=150,urcrnrlat=55,projection='poly',lon_0 = 116.65,lat_0 = 40.02,ax = ax)
basemap.readshapefile(shapefile = "C:/Users/zhangjunhong/Desktop/CHN_adm/CHN_adm1",name = "china")

#定義繪圖函數
def create_great_points(data):
    lon   = np.array(data["谷歌地圖經度"])
    lat   = np.array(data["谷歌地圖緯度"])
    pop   = np.array(data["人次"],dtype=float)
    name = np.array(data["地址"])
    x,y = basemap(lon,lat)
    for lon,lat,pop,name in zip(x,y,pop,name):
        basemap.scatter(lon,lat,c = "#778899",marker = "o",s = pop*10)
        plt.text(lon,lat,name,fontsize=10,color = "#DC143C")

#在location_data上調用繪圖函數
create_great_points(location_data)

plt.axis("off")#關閉座標軸
plt.savefig("C:/Users/zhangjunhong/Desktop/itwechat.png")#保存圖表到本地
plt.show()#顯示圖表
複製代碼

image

上面地圖繪製主要是用的Python中的Basemap庫,解析地理位置用的XGeocoding_v2。
Top省份和Top城市就是兩個柱狀圖,製做方式和上面的年齡分佈相似。

樹地圖繪製
星座顯示的這種可視化形式叫作樹地圖,主要用的squarify庫,實現以下:

import squarify
# 建立數據
xingzuo = user_info1["星座"].value_counts(normalize = True).index
size = user_info1["星座"].value_counts(normalize = True).values
rate = np.array(["34%","6.93%","5.85%","5.70%","5.62%","5.31%","5.30%","5.24%","5.01%","4.78%","4.68%","4.36%"])

# 繪圖
colors = ['steelblue','#9999ff','red','indianred',
          'green','yellow','orange']
plot = squarify.plot(sizes = size, # 指定繪圖數據
                     label = xingzuo, # 指定標籤
                     color = colors, # 指定自定義顏色
                     alpha = 0.6, # 指定透明度
                     value = rate, # 添加數值標籤
                     edgecolor = 'white', # 設置邊界框爲白色
                     linewidth =3 # 設置邊框寬度爲3
                    )
# 設置標籤大小
plt.rc('font', size=10)
# 設置標題大小
plt.title('菊粉星座分佈',fontdict = {'fontsize':12})

# 去除座標軸
plt.axis('off')
# 去除上邊框和右邊框刻度
plt.tick_params(top = 'off', right = 'off')
複製代碼

image

自定義詞雲圖
上面從各個字段介紹了菊粉的特質,最後該來個總結了,總結的形式不少,仍是選擇詞雲圖的形式,只不過這裏不須要進行分詞,直接手動輸入你要顯示的詞,以及詞的權重(頻次)便可,具體代碼以下:

image = Image.open('C:/Users/zhangjunhong/Desktop/圖片1.png')#做爲背景形狀的圖 
graph = np.array(image)  
#參數分別是指定字體、背景顏色、最大的詞的大小、使用給定圖做爲背景形狀 
wc = WordCloud(font_path = "C:\\Windows\\Fonts\\simkai.ttf", background_color = 'White', max_words = 150, mask = graph)  

name = ["女性","摩羯座","20歲","21歲","22歲","23歲","24歲","25歲","廣州","杭州","成都","武漢","長沙","上海","北京","海外","美國","深圳"]
value = [20,20,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10]#詞的頻率
dic = dict(zip(name, value))#詞頻以字典形式存儲 
wc.generate_from_frequencies(dic)#根據給定詞頻生成詞雲
image_color = ImageColorGenerator(graph)  
plt.imshow(wc)  
plt.axis("off")#不顯示座標軸 
plt.show()
wc.to_file('C:/Users/zhangjunhong/Desktop/wordcloud.jpg')
複製代碼

image

菊粉畫像


本文做者:張俊紅

來自阿里雲開發者社區

原文連接:developer.aliyun.com/article/608…

相關文章
相關標籤/搜索