你們要的代碼來了
來自專欄數據科學成長之路108 人贊了文章
前言
上一篇文章發出後,大家反響還不錯,文章的閱讀量也是我公眾號歷史閱讀量最高的一篇(截至目前閱讀已經1124啦),在其他平台發布以後閱讀量已經超過5w了,果真還是平台的影響力大。
上一篇文章中之所以沒帶代碼主要是因為我只想寫一篇數據分析報告,咱們平常給領導看數據分析報告,肯定也不會把Sql代碼、Python代碼放在PPT中,給老闆講述每一行Sql代碼是什麼意思,所以就沒有放代碼。
但是大家都很愛學習,都想要代碼學習學習,所以今天就專門來一篇講講代碼。
在開始具體的代碼講解之前,我需要說明一下關於菊粉人數中摩羯座人數最多這個結論的一些爭議,有人評論說微博用戶如果不設置年齡的話,默認就是1月1,也就是摩羯座,所以摩羯座人數比較多。先來看兩張圖:
未設置年齡前的信息
設置個人信息
設置年齡後的信息
通過上面幾張截圖來看的話,如果未設置年齡時,並不會默認顯示成摩羯座,所以應該就不存在大家說的那種情況。
還有所在地和家鄉是可以選擇則其他的,性別、年齡、星座是不可以選擇其他。我們本次就是要獲取這幾個欄位。
本篇主要分為三個部分:
- 數據獲取
- 數據預處理
- 可視化圖表製作
數據抓取
先講講數據抓取的邏輯,最終目的就是要找到pick王菊的人都是哪些人
,剛開始想的是直接抓取王菊的粉絲列表,但是後來發現微博數據有限制,只能抓取少量的粉絲列表,所以這個方案行不通,只能換下一個。
在小歪大佬的建議下,決定抓取王菊微博留言下面的用戶,因為這些用戶是和王菊有過互動的,要比那些只關注沒有互動(這裡的互動只指評論這一動作)的用戶粉的程度要大,更有代表性。
所以最終的一個數據抓取思路就是:通過獲取微博評論下的用戶,然後進而獲取用戶基本信息
,具體實現代碼如下:
獲取每條微博評論url
我們先隨便點擊一條微博的評論進去,看看我們要的欄位都在哪裡。
最近一條微博的評論
可以看到,有評論text
,以及每一條text
對應的user_id
,找到了欄位位置,我們再來看看這些欄位對應url是什麼,有什麼規律。微博評論url
通過查看這個urlhttps://m.weibo.cn/api/comments/show?id=4248590911655823&page=1
,我們大概可以猜出,id前面的部分https://m.weibo.cn/api/comments/show?
應該是所有微博評論都一樣的,id值是唯一的,每一個id對應一條微博,而page是表示一條微博的評論存放在多頁裡面,經過驗證確實如此,而且page最大值就是100,100以後就不返回數據了。所以接下來我們的目標就是獲取每條微博對應的唯一id值。回到用戶主頁,
微博id
可以看到每條微博的發布時間,以及微博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 requestsimport jsoncomment_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=1076031773294041c_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)#獲取每條微博評論urlc_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_idcomment = []#用來存放commentfor 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以後,我們再來看看我們想要獲取的欄位在哪,如下圖,
用戶信息界面
知道了我們想要獲取的欄位在哪以後,再看看這些欄位對應的url是什麼?用戶信息對應url
看到這個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 = []#存放請求失敗iduser_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 = 1for 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 = ["性別","年齡","星座","國家城市"])
用戶信息表
可以看到,年齡和星座為空,並不是摩羯座,且當年齡和星座為空時,所在地就會錯位到年齡列,接下來就做一些數據預處理。數據清洗
數據清洗邏輯如下:
- 對於國家列為空,星座列不空且不包含座字,則認為是國家城市名,則把星座列賦值給國家城市列
- 對於國家列為空,星座列也為空,年齡列不為空且不包含歲或座字,則把年齡列賦值給國家城市列
- 對於星座列為空,但是年齡列包含座字,則把年齡列賦值給星座列
- 對於星座列不包含座的,全部賦值為「未知」
- 對於年齡列不包含歲的,全部賦值為「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)].indexfor 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))].indexfor 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))].indexfor 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)].indexfor index in user_index4: user_info1.iloc[index,2] = "未知"user_index5 = user_info1[(user_info1["年齡"].map(lambda s:str(s).find("歲")) == -1)].indexfor index in user_index5: user_info1.iloc[index,1] = "999歲"#便於後續統一處理user_index6 = user_info1[(user_info1["國家城市"].isnull() == True)].indexfor index in user_index6: user_info1.iloc[index,3] = "其他"
圖表製作
主要講講這篇報告中涉及到的圖表的製作,上一篇文章中的圖表我是用的BDP做的,因為BDP做出來的要比python做出來的美觀,而且方便,所以我就用了BDP,這篇主要是講代碼,所以就給大家用python實現一遍。
詞雲圖製作
詞雲圖製作是先把一大段話進行分詞,分成若干個詞語,然後對詞語進行計數,最後挑選出出現次數比較大的那些詞,繪製在同一張圖上,且出現次數越多,字體顯示越大,最終效果圖如下:
菊粉留言
當然了,最後結果只是右半部分,左半部分是為了對比後期PS加上去的。具體實現代碼如下:import foolfrom collections import Counterfrom PIL import Image,ImageSequence from wordcloud import WordCloud,ImageColorGenerator#因留言結構比較亂,所以先保存到本地做進一步處理#刪除掉一些html元素pd.DataFrame(comment).to_csv(r"C:UserszhangjunhongDesktopcomment.csv")#處理完以後再次載入進來comment_data = pd.read_excel(r"C:UserszhangjunhongDesktopcomment.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:UserszhangjunhongDesktopfenci.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:UserszhangjunhongDesktopda200.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,據稱是最準確的中文分詞包,github地址:https://github.com/rockyzhengwu/FoolNLTK
。
餅圖繪製
餅圖就很簡單了,代碼如下:
繪製男女比例的餅圖user_info1["性別"].value_counts(normalize = True).plot.pie(title = "菊粉男女分布",autopct=%.2f)
菊粉男女分布
柱狀圖繪製
先對年齡進行分區間,然後再進行統計繪製,代碼如下
#將把年齡從字元串變成數字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)
菊粉年齡分布
地圖繪製
#導入相關庫import matplotlib.pyplot as pltimport matplotlibfrom matplotlib.patches import Polygonfrom mpl_toolkits.basemap import Basemapfrom 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:UserszhangjunhongDesktoplatlon_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()#顯示圖表
菊粉全國分布
上面地圖繪製主要是用的Python中的Basemap庫
,解析地理位置用的XGeocoding_v2
。
Top省份和Top城市就是兩個柱狀圖,製作方式和上面的年齡分布類似。
樹地圖繪製
星座顯示的這種可視化形式叫做樹地圖,主要用的squarify庫
,實現如下:
import squarify# 創建數據xingzuo = user_info1["星座"].value_counts(normalize = True).indexsize = user_info1["星座"].value_counts(normalize = True).valuesrate = 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.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)
推薦閱讀:
※正則表達式+CSV實戰
※致讀者歉意
※Python爬蟲基礎練習(七)貓貓壁紙酷壁紙爬取
※Matplotlib:入門一
※如何正確安裝pygame