你们要的代码来了!爬了菊姐的两万条评论——详细代码解读篇
點(diǎn)擊上方“程序人生”,選擇“置頂公眾號(hào)”
第一時(shí)間關(guān)注程序猿(媛)身邊的故事
作者
張俊紅
如需轉(zhuǎn)載,請(qǐng)聯(lián)系原作者授權(quán)。
前言
上一篇文章爬了菊姐的兩萬(wàn)條評(píng)論,竟發(fā)現(xiàn)菊粉都是這樣的人!發(fā)出后,大家反響還不錯(cuò)。
在上一篇文章中之所以沒(méi)帶代碼主要是因?yàn)槲抑幌雽?xiě)一篇數(shù)據(jù)分析報(bào)告,咱們平常給領(lǐng)導(dǎo)看數(shù)據(jù)分析報(bào)告,肯定也不會(huì)把Sql代碼、Python代碼放在PPT中,給老板講述每一行Sql代碼是什么意思,所以就沒(méi)有放代碼。
但是大家都很愛(ài)學(xué)習(xí),都想要代碼學(xué)習(xí)學(xué)習(xí),所以今天就專門來(lái)一篇講講代碼。
在開(kāi)始具體的代碼講解之前,我需要說(shuō)明一下關(guān)于菊粉人數(shù)中摩羯座人數(shù)最多這個(gè)結(jié)論的一些爭(zhēng)議,有人評(píng)論說(shuō)微博用戶如果不設(shè)置年齡的話,默認(rèn)就是1月1,也就是摩羯座,所以摩羯座人數(shù)比較多。先來(lái)看兩張圖:
通過(guò)上面幾張截圖來(lái)看的話,如果未設(shè)置年齡時(shí),并不會(huì)默認(rèn)顯示成摩羯座,所以應(yīng)該就不存在大家說(shuō)的那種情況。
還有所在地和家鄉(xiāng)是可以選擇則其他的,性別、年齡、星座是不可以選擇其他。我們本次就是要獲取這幾個(gè)字段。
本篇主要分為三個(gè)部分:
數(shù)據(jù)獲取
數(shù)據(jù)預(yù)處理
可視化圖表制作
數(shù)據(jù)抓取
先講講數(shù)據(jù)抓取的邏輯,最終目的就是要找到pick王菊的人都是哪些人,剛開(kāi)始想的是直接抓取王菊的粉絲列表,但是后來(lái)發(fā)現(xiàn)微博數(shù)據(jù)有限制,只能抓取少量的粉絲列表,所以這個(gè)方案行不通,只能換下一個(gè)。
在小歪大佬的建議下,決定抓取王菊微博留言下面的用戶,因?yàn)檫@些用戶是和王菊有過(guò)互動(dòng)的,要比那些只關(guān)注沒(méi)有互動(dòng)(這里的互動(dòng)只指評(píng)論這一動(dòng)作)的用戶粉的程度要大,更有代表性。
所以最終的一個(gè)數(shù)據(jù)抓取思路就是:通過(guò)獲取微博評(píng)論下的用戶,然后進(jìn)而獲取用戶基本信息,具體實(shí)現(xiàn)代碼如下:
獲取每條微博評(píng)論url
我們先隨便點(diǎn)擊一條微博的評(píng)論進(jìn)去,看看我們要的字段都在哪里。
可以看到,有評(píng)論 text ,以及每一條 text 對(duì)應(yīng)的 user_id ,找到了字段位置,我們?cè)賮?lái)看看這些字段對(duì)應(yīng)url是什么,有什么規(guī)律。
微博評(píng)論url
通過(guò)查看這個(gè)url https://m.weibo.cn/api/comments/show?id=4248590911655823&page=1 ,我們大概可以猜出,id前面的部分 https://m.weibo.cn/api/comments/show? 應(yīng)該是所有微博評(píng)論都一樣的,id值是唯一的,每一個(gè)id對(duì)應(yīng)一條微博,而page是表示一條微博的評(píng)論存放在多頁(yè)里面,經(jīng)過(guò)驗(yàn)證確實(shí)如此,而且page最大值就是100,100以后就不返回?cái)?shù)據(jù)了。
所以接下來(lái)我們的目標(biāo)就是獲取每條微博對(duì)應(yīng)的唯一id值。回到用戶主頁(yè),
可以看到每條微博的發(fā)布時(shí)間,以及微博id,也就是只需要解析用戶主頁(yè)url就可以得到該用戶的每條微博對(duì)應(yīng)的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值以后,我們就可以獲取到每條微博評(píng)論的url,具體代碼如下:
#導(dǎo)入相關(guān)庫(kù)import requests
import json
comment_parameter = []#用來(lái)存放weibo_id值
comment_url = []#用來(lái)存放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)
#獲取每條微博評(píng)論url
c_url_base = 'https://m.weibo.cn/api/comments/show?id='
for parameter in comment_parameter:
? ?for page in range(1,101):#提前知道每條微博只可抓取前100頁(yè)評(píng)論
? ? ? ?c_url = c_url_base + str(parameter) + "&page=" + str(page)
? ? ? ?comment_url.append(c_url)
獲取每個(gè)user_id和comment
上面獲取到每條微博評(píng)論的url以后,我們就可以直接請(qǐng)求對(duì)應(yīng)的url,然后把user_id和text解析出來(lái)即可,實(shí)現(xiàn)代碼如下:
user_id = []#用來(lái)存放user_idcomment = []#用來(lái)存放comment
for url in comment_url:
? ?u_c_r = requests.get(url)
? ?try:
? ? ? ?for m in range(0,9):#提前知道每個(gè)url會(huì)包含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以后,我們?cè)賮?lái)看看我們想要獲取的字段在哪,如下圖,
知道了我們想要獲取的字段在哪以后,再看看這些字段對(duì)應(yīng)的url是什么?
用戶信息對(duì)應(yīng)url
看到這個(gè)url以后我們又可以猜測(cè),每個(gè)用戶信息對(duì)應(yīng)的url應(yīng)該只有 value&containerid 這兩個(gè)值是不一樣的,其他都是一樣的,經(jīng)驗(yàn)證,缺失如此,且 value 值就是 user_id , containerid 是另外一個(gè)唯一值,所以我們接下來(lái)的目標(biāo)是獲取每個(gè)用戶對(duì)應(yīng)的 containerid 。具體實(shí)現(xiàn)代碼如下: containerid = []
user_base_url = "https://m.weibo.cn/api/container/getIndex?type=uid&value="
for id in set(user_id):#需要對(duì)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,我們就可以唯一確定一個(gè)用戶的基本信息,具體實(shí)現(xiàn)代碼如下:
#這里需要設(shè)置headers以及cookie模擬登陸feature = []#存放用戶基本信息
id_lose = []#存放請(qǐng)求失敗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)#設(shè)置睡眠一秒鐘,防止被封
? ?except:
? ? ? ?id_lose.append(num[0])
#將featrue建立成DataFrame結(jié)構(gòu)便于后續(xù)分析
user_info = pd.DataFrame(feature,columns = ["性別","年齡","星座","國(guó)家城市"])
最后的結(jié)果如下表:
可以看到,年齡和星座為空,并不是摩羯座,且當(dāng)年齡和星座為空時(shí),所在地就會(huì)錯(cuò)位到年齡列,接下來(lái)就做一些數(shù)據(jù)預(yù)處理。
數(shù)據(jù)清洗
數(shù)據(jù)清洗邏輯如下:
對(duì)于國(guó)家列為空,星座列不空且不包含座字,則認(rèn)為是國(guó)家城市名,則把星座列賦值給國(guó)家城市列
對(duì)于國(guó)家列為空,星座列也為空,年齡列不為空且不包含歲或座字,則把年齡列賦值給國(guó)家城市列
對(duì)于星座列為空,但是年齡列包含座字,則把年齡列賦值給星座列
對(duì)于星座列不包含座的,全部賦值為“未知”
對(duì)于年齡列不包含歲的,全部賦值為“999歲”(為便于后續(xù)好篩選)
對(duì)于國(guó)家列為空的,全部賦值為“其他”
具體代碼如下:
user_info1 = user_info[(user_info["性別"] == "男") | (user_info["性別"] == "女")]#去除掉性別不為男女的部分
user_info1 = user_info1.reindex(range(0,5212))#重置索引
user_index1 = user_info1[(user_info1["國(guó)家城市"].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["國(guó)家城市"].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歲"#便于后續(xù)統(tǒng)一處理
user_index6 = user_info1[(user_info1["國(guó)家城市"].isnull() == True)].index
for index in user_index6:
? ?user_info1.iloc[index,3] = "其他"
圖表制作
主要講講這篇報(bào)告中涉及到的圖表的制作,上一篇文章中的圖表我是用的BDP做的,因?yàn)锽DP做出來(lái)的要比python做出來(lái)的美觀,而且方便,所以我就用了BDP,這篇主要是講代碼,所以就給大家用python實(shí)現(xiàn)一遍。
詞云圖制作
詞云圖制作是先把一大段話進(jìn)行分詞,分成若干個(gè)詞語(yǔ),然后對(duì)詞語(yǔ)進(jìn)行計(jì)數(shù),最后挑選出出現(xiàn)次數(shù)比較大的那些詞,繪制在同一張圖上,且出現(xiàn)次數(shù)越多,字體顯示越大,最終效果圖如下:
當(dāng)然了,最后結(jié)果只是右半部分,左半部分是為了對(duì)比后期PS加上去的。具體實(shí)現(xiàn)代碼如下: import fool
from collections import Counter
from PIL import Image,ImageSequence ?
from wordcloud import WordCloud,ImageColorGenerator
#因留言結(jié)構(gòu)比較亂,所以先保存到本地做進(jìn)一步處理
#刪除掉一些html元素
pd.DataFrame(comment).to_csv(r"C:\Users\zhangjunhong\Desktop\comment.csv")
#處理完以后再次載入進(jìn)來(lái)
comment_data = pd.read_excel(r"C:\Users\zhangjunhong\Desktop\comment.xlsx")
#將數(shù)據(jù)轉(zhuǎn)換成字符串
text = (",").join(comment_data[0])
#進(jìn)行分詞
cut_text = ' '.join(fool.cut(text))
#將分詞結(jié)果進(jìn)行計(jì)數(shù)
c = Counter(cut_text)
c.most_common(500)#挑選出詞頻最高的500詞
#將結(jié)果導(dǎo)出到本地進(jìn)行再一次清洗,刪除無(wú)意義的符號(hào)詞
pd.DataFrame(c.most_common(500)).to_excel(r"C:\Users\zhangjunhong\Desktop\fenci.xlsx")
#導(dǎo)入背景圖,這里選擇菊姐頭像
image = Image.open('C:/Users/zhangjunhong/Desktop/圖片1.png')
#將圖片信息轉(zhuǎn)換成數(shù)組形式
graph = np.array(image)
#設(shè)置詞云參數(shù)
#參數(shù)分別是指定字體、背景顏色、最大的詞的大小、使用給定圖作為背景形狀 ?
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))#詞以及詞頻以字典形式存儲(chǔ) ?
#根據(jù)給定詞頻生成詞云
wc.generate_from_frequencies(dic)
image_color = ImageColorGenerator(graph) ?
plt.imshow(wc) ?
plt.axis("off")#不顯示坐標(biāo)軸 ?
plt.show()
#保存結(jié)果到本地
wc.to_file('C:/Users/zhangjunhong/Desktop/wordcloud.jpg')
這里分詞沒(méi)有用jieba分詞,而是用了fool,據(jù)稱是最準(zhǔn)確的中文分詞包,github地址:https://github.com/rockyzhengwu/FoolNLTK。
餅圖繪制
餅圖就很簡(jiǎn)單了,代碼如下:
繪制男女比例的餅圖user_info1["性別"].value_counts(normalize = True).plot.pie(title = "菊粉男女分布",autopct='%.2f')
菊粉男女分布
柱狀圖繪制
先對(duì)年齡進(jìn)行分區(qū)間,然后再進(jìn)行統(tǒng)計(jì)繪制,代碼如下
#將把年齡從字符串變成數(shù)字user_info1["age_1"] = [int(age[:-1]) for age in user_info1["年齡"]]
#對(duì)年齡進(jìn)行分組
bins = (0,10,20,25,30,100,1000)#將年齡進(jìn)行區(qū)間切分
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歲的過(guò)濾掉
ax.set_xticklabels(["0-10歲","10-20歲","20-25歲","25-30歲","30+"],rotation = 0)
菊粉年齡分布
地圖繪制
#導(dǎo)入相關(guān)庫(kù)import matplotlib.pyplot as plt
import matplotlib
from matplotlib.patches import Polygon
from mpl_toolkits.basemap import Basemap
from matplotlib.collections import PatchCollection
#將省份和城市進(jìn)行分列
country_data = pd.DataFrame([country.split(" ") for country in user_info1["國(guó)家城市"]],columns = ["省份","城市"])
#將國(guó)家和城市與user表合并
user_data = pd.merge(user_info1,country_data,left_index = True,right_index = True,how = "left")
#按省份進(jìn)行分組計(jì)數(shù)
shengfen_data = user_data.groupby("省份")["性別"].count().reset_index().rename(columns = {"性別":"人次"})
#需要先對(duì)各省份地址進(jìn)行經(jīng)緯度解析
#導(dǎo)入解析好的省份經(jīng)緯度信息
location = pd.read_table(r"C:\Users\zhangjunhong\Desktop\latlon_106318.txt",sep = ",")
#將省份數(shù)據(jù)和經(jīng)緯度進(jìn)行匹配
location_data = pd.merge(shengfen_data,location[["關(guān)鍵詞","地址","谷歌地圖緯度","谷歌地圖經(jīng)度"]],
? ? ? ? ? ? ? ? ? ?left_on = "省份",right_on = "關(guān)鍵詞",how = "left")
#進(jìn)行地圖可視化
#創(chuàng)建坐標(biāo)軸
fig = plt.figure(figsize=(16,12))
ax ?= fig.add_subplot(111)
#需要提前下載中國(guó)省份地圖的.shp
#指明.shp所在路徑進(jìn)行導(dǎo)入
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")
#定義繪圖函數(shù)
def create_great_points(data):
? ?lon ? = np.array(data["谷歌地圖經(jīng)度"])
? ?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上調(diào)用繪圖函數(shù)
create_great_points(location_data)
plt.axis("off")#關(guān)閉坐標(biāo)軸
plt.savefig("C:/Users/zhangjunhong/Desktop/itwechat.png")#保存圖表到本地
plt.show()#顯示圖表
菊粉全國(guó)分布
上面地圖繪制主要是用的Python中的Basemap庫(kù),解析地理位置用的XGeocoding_v2。
Top省份和Top城市就是兩個(gè)柱狀圖,制作方式和上面的年齡分布類似。
樹(shù)地圖繪制
星座顯示的這種可視化形式叫做樹(shù)地圖,主要用的squarify庫(kù),實(shí)現(xiàn)如下:
import squarify# 創(chuàng)建數(shù)據(jù)
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, # 指定繪圖數(shù)據(jù)
? ? ? ? ? ? ? ? ? ? label = xingzuo, # 指定標(biāo)簽
? ? ? ? ? ? ? ? ? ? color = colors, # 指定自定義顏色
? ? ? ? ? ? ? ? ? ? alpha = 0.6, # 指定透明度
? ? ? ? ? ? ? ? ? ? value = rate, # 添加數(shù)值標(biāo)簽
? ? ? ? ? ? ? ? ? ? edgecolor = 'white', # 設(shè)置邊界框?yàn)榘咨?/span>
? ? ? ? ? ? ? ? ? ? linewidth =3 # 設(shè)置邊框?qū)挾葹?
? ? ? ? ? ? ? ? ? ?)
# 設(shè)置標(biāo)簽大小
plt.rc('font', size=10)
# 設(shè)置標(biāo)題大小
plt.title('菊粉星座分布',fontdict = {'fontsize':12})
# 去除坐標(biāo)軸
plt.axis('off')
# 去除上邊框和右邊框刻度
plt.tick_params(top = 'off', right = 'off')
菊粉星座分布
自定義詞云圖
上面從各個(gè)字段介紹了菊粉的特質(zhì),最后該來(lái)個(gè)總結(jié)了,總結(jié)的形式很多,還是選擇詞云圖的形式,只不過(guò)這里不需要進(jìn)行分詞,直接手動(dòng)輸入你要顯示的詞,以及詞的權(quán)重(頻次)即可,具體代碼如下:
image = Image.open('C:/Users/zhangjunhong/Desktop/圖片1.png')#作為背景形狀的圖 ?graph = np.array(image) ?
#參數(shù)分別是指定字體、背景顏色、最大的詞的大小、使用給定圖作為背景形狀 ?
wc = WordCloud(font_path = "C:\\Windows\\Fonts\\simkai.ttf", background_color = 'White', max_words = 150, mask = graph) ?
name = ["女性","摩羯座","20歲","21歲","22歲","23歲","24歲","25歲","廣州","杭州","成都","武漢","長(zhǎng)沙","上海","北京","海外","美國(guó)","深圳"]
value = [20,20,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10]#詞的頻率
dic = dict(zip(name, value))#詞頻以字典形式存儲(chǔ) ?
wc.generate_from_frequencies(dic)#根據(jù)給定詞頻生成詞云
image_color = ImageColorGenerator(graph) ?
plt.imshow(wc) ?
plt.axis("off")#不顯示坐標(biāo)軸 ?
plt.show()
wc.to_file('C:/Users/zhangjunhong/Desktop/wordcloud.jpg')
菊粉畫(huà)像 可點(diǎn)擊文末「 閱讀原文 」查看GitHub地址。
- The End -
「若你有原創(chuàng)文章想與大家分享,歡迎投稿。」
加編輯微信ID,備注#投稿#:
程序 丨 druidlost??
小七 丨 duoshangshuang
總結(jié)
以上是生活随笔為你收集整理的你们要的代码来了!爬了菊姐的两万条评论——详细代码解读篇的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Nautilus Chain测试网迎阶段
- 下一篇: CTF练习题[MISC]-0和1的故事(