1. 選取主題
meituan是我常用的網頁,按照個人喜好確定了爬取meituan大盤雞的數據並進行統計分析
2. 分析網頁
翻頁可以看到,上方的URL並沒有發生改變,所以考慮是通過json翻頁。
右鍵檢查,network->刷新->XHR,找到uuid的文件,獲取URL,進行分析,一步步刪除多余部分,最后對比第一頁與第二頁的URL,發現僅offset發生變化,觀察網頁可知,美團頁面一頁有32個所需元素,第一頁的offset為0,第二頁的offset=32,也就是每翻頁一次,offset要增加32
GetHTMLText函數用於爬取網頁源代碼,頭部從上圖uuid文件中找到Request部 分,復制Accept、User-Agent、Cookie部分。翻頁在主函數實現
在爬取網頁時需要先登錄,搜索大盤雞,然后得到網頁的cookie
代碼部分
import requests
import re
from bs4 import BeautifulSoup
import json
from urllib.request import urlopen,quote
import pandas as pd
import numpy as np
from pylab import *
def getHTMLText(url):
headers={"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36",
"Cookie":"__mta=20871943.1625705574088.1625810520459.1625813097008.16; _lxsdk_cuid=17a839a3528c8-070f1da58fbd21-6373264-144000-17a839a3529c8; ci=1; rvct=1; mtcdn=K; uuid=5226c694e07949bdb828.1625725143.1.0.0; mt_c_token=xR1aknv_B8Yiio_ZWzUmVSFo2koAAAAACw4AAIkyH5SSt867G_XkxBIcCNINlemXinZVHTQDkWGAFQukS3HrBbqc_BktISzd7MhPvw; lsu=; iuuid=C72B4FEF3403F5A0924DEFB4DF9C9FFEC17E5FC35B2B4D6CF55632ABF4817904; isid=xR1aknv_B8Yiio_ZWzUmVSFo2koAAAAACw4AAIkyH5SSt867G_XkxBIcCNINlemXinZVHTQDkWGAFQukS3HrBbqc_BktISzd7MhPvw; logintype=normal; cityname=%E5%8C%97%E4%BA%AC; _lxsdk=C72B4FEF3403F5A0924DEFB4DF9C9FFEC17E5FC35B2B4D6CF55632ABF4817904; webp=1; i_extend=H__a100002__b1; latlng=39.830516,116.290895,1625725257685; __utma=74597006.798321478.1625725258.1625725258.1625725258.1; __utmz=74597006.1625725258.1.1.utmcsr=baidu|utmccn=(organic)|utmcmd=organic; _lx_utm=utm_source%3DBaidu%26utm_medium%3Dorganic; lt=fYaN6SmjIjuKuNSbDGgqCDbdjQgAAAAACw4AABKyZyfS3ZbSub8odXTK9CKxaHAOvtIkpz464sfHyJcIfPPYbpw2Vh_Rnd3Mr5B24A; u=583093909; n=%E9%A5%95%E9%A4%AEzzzzz; token2=fYaN6SmjIjuKuNSbDGgqCDbdjQgAAAAACw4AABKyZyfS3ZbSub8odXTK9CKxaHAOvtIkpz464sfHyJcIfPPYbpw2Vh_Rnd3Mr5B24A; unc=%E9%A5%95%E9%A4%AEzzzzz; __mta=20871943.1625705574088.1625810319319.1625810340857.13; firstTime=1625813095796; _lxsdk_s=17a89d8b4fc-08e-6c7-641%7C%7C48"}
try:
r = requests.get(url,headers=headers,timeout=100)
r.raise_for_status()
r.encoding = r.apparent_encoding#對文本中使用的編碼替換整體的編碼
return r.text
except:
return "失敗"
#將數據寫入csv文件中
def file_data(title,address,avgprice,avgscore,comment):
data={
'店鋪名稱':title,
'店鋪地址':address,
'平均消費價格':avgprice,
'店鋪評分':avgscore,
'評價人數':comment
}
with open('C:/Users/dell/Desktop/美團大盤雞店鋪數據4.csv','a',encoding='utf-8-sig') as fb:
fb.write(json.dumps(data,ensure_ascii=False)+'\n')
def main():
goods = '大盤雞'#檢索詞
depth = 49#設置爬取的深度,一共50頁
start_url = 'https://bj.meituan.com/s/' + goods
#以下采用for循環對每個頁面URL進行訪問
i=0
for i in range(depth):
try:
url =start_url + '&offset=' + str(32*i)
#url = start_url + str(32*i)
html = getHTMLText(url)
titles = re.findall('","title":"(.*?)","address":"',html)
addresses = re.findall(',"address":"(.*?)",',html)
avgprices = re.findall(',"avgprice":(.*?),', html)
avgscores = re.findall(',"avgscore":(.*?),',html)
comments = re.findall(',"comments":(.*?),',html)
print(titles)
print(addresses)
print(avgprices)
print(avgscores)
print(comments)
#輸出當前返回數據的長度
print(len(titles), len(addresses), len(avgprices), len(avgscores), len(comments))
#將每個店鋪的信息通過循環寫入文件
for j in range(len(titles)):
title=titles[j]
address=addresses[j]
avgprice=avgprices[j]
avgscore=avgscores[j]
comment=comments[j]
#file_data(title,address,avgprice,avgscore,comment)
except:
continue
#printGoodsList(infoList)
main()
其中的headers和cookie、accept后面的參數要替換成自己的,如果要存為txt文件,則把.csv改為.txt即可;一共爬取50頁,因為第一頁的offset為0,所以i最開始為0,之后每一次offset是32*i,實現網頁的翻頁功能;main()函數部分,因為json方法無法針對店鋪特有的title鍵值進行獲取所以使用正則表達式獲取當前相應內容中的數據;主函數通過循環將每次得到的店鋪信息傳入子函數;子函數也就是寫入csv文件的子函數,將主函數傳過來的數據寫入csv文件
數據展示:
在獲得的原始數據中,由於美團網頁的機制會存在大量的重復數據,同時也為了數據更直觀更方便使用,我們還需要對數據進行清洗。這一次我主要對數據進行了刪除重復記錄和數據類型轉換這兩項,將評分和人數以及平均消費價格這三列數據從object類型轉換為float以用於后續的運算。
#刪除重復記錄
import pandas as pd
df=pd.read_csv('C:/Users/dell/Desktop/Python/END/美團大盤雞店鋪數據.csv',encoding='GB18030')
df.head()
df.drop_duplicates(['店鋪名稱','店鋪地址'],inplace=True)
print("ok")
#數據類型轉換
df['平均消費價格(元)']=df['平均消費價格(元)'].astype('float')
df['評分']=df['評分'].astype('float')
df['評價人數(人)']=df['評價人數(人)'].astype('float')
print(df.dtypes)
之后我們可以將處理好的數據存入數據庫中,方便使用,這里我存的是mysql數據庫
load data infile 'D:\MySQL\mysql-8.0.23-winx64\mysql-8.0.23-winx64\data\meituan.csv'
into table mt_shop character set utf8
fields terminated by ',' optionally enclosed by '"' escaped by '"'
lines terminated by '\r\n';
在導入數據庫之前,需要先自行創建新的數據庫和表格,我這里的表格名稱是mt_shop,用的是cmd窗口導入數據庫
導入數據庫,要注意字符集的問題,我試了gb2312,gb18030,utf8,最后utf8能夠正確展示數據;還有一個是\和/以及\的問題,路徑最好不要有中文名,免得出現數據存入不成功的問題
3.Python數據分析及展示
詞雲:
用到jieba庫和worldcloud庫
import jieba
import wordcloud
fo=open("C:/Users/dell/Desktop/Python/END/詞雲文本.txt",encoding='UTF-8-sig')
txt=fo.read()
fo.close()
ls=jieba.lcut(txt)
txt=" ".join(ls)
from imageio import imread
mk=imread("C:/Users/dell/Desktop/Python/END/chicken.png")
w=wordcloud.WordCloud(width=1000,\
font_path="msyh.ttc",height=700,mask=mk,background_color="white")
w.generate(" ".join(jieba.lcut(txt)))
w.to_file("C:/Users/dell/Desktop/Python/END/pywcloud.png")
條形圖:
用數據聚合和分組運算統計評分前十、平均消費前十、評價人數前十的店鋪條形圖,先用groupby選出店鋪名稱和評分這兩列數據,然后排序,用numpy庫畫圖
#數據聚合:取出店鋪名稱和評分這兩列數據
a=df[['評分']].groupby(df['店鋪名稱']).sum()
a.index
#按照評分從高到低排序
a=a.sort_values(axis=0,ascending=False,by='評分')
a
#生成評分最高前十名的條形圖
import numpy as np
plt.rcParams['font.sans-serif']=['SimHei']
sns.barplot(x=a.head(10).index,y=a['評分'].head(10),color='skyblue')
i=0
for x,y in enumerate(a.評分):
if i<10:
plt.text(x,y+0.1,"%s"%round(y,1),ha='center')
i+=1
plt.title("評分前十店鋪")
plt.ylim(0,10)
plt.xticks(rotation=300)
plt.tight_layout() # 當有多個子圖時,可以使用該語句保證各子圖標題不會重疊
plt.savefig('C:/Users/dell/Desktop/Python/END/評分前十.jpg', dpi=700) # dpi 表示以高分辨率保存一個圖片文件,pdf為文件格式,輸出位圖文件
條形圖制作過程中,循環條件使用enumerate枚舉法對元素進行遍歷
在做評價人數前十店鋪時,遇到如下問題:
TypeError: can only concatenate tuple (not "float") to tuple
出錯行數:13
最開始評價人數那一列命名為:評價人數(人),含有中文括號,在使用循環遍歷元素時,無法識別或者說找到對應的那一列數據導致報錯
解決方法:更改列名,刪除中文括號或將中文括號替換為英文括號
折線圖:
沒有對應庫的先在cmd窗口安裝對應的庫,這里我在csv文件中獲取了各個區店鋪的數量用於畫折線圖
import matplotlib.pyplot as plt
from pylab import * #支持中文
mpl.rcParams['font.sans-serif'] = ['SimHei']
names = ['昌平區','朝陽區','東城區','房山區','豐台區','海淀區','密雲區','平谷區','石景山區','順義區','通州區','西城區']
x = range(len(names))
y = [6,49,13,9,9,100,4,1,2,4,9,3]
y1=[0,10,20,30,40,50,60,70,80,90,100,110]
plt.ylim(0, 110) # 限定縱軸的范圍
plt.plot(x, y, marker='o', mec='r', mfc='w',label=u'各城區大盤雞店鋪數量分布')
plt.legend() # 讓圖例生效
plt.xticks(x, names, rotation=45)
plt.margins(0)
plt.subplots_adjust(bottom=0.15)
plt.xlabel(u"time(s)鄰居") #X軸標簽
plt.ylabel("RMSE") #Y軸標簽
plt.title("各城區大盤雞店鋪數量分布圖") #標題
plt.savefig('C:/Users/dell/Desktop/Python/END/各城區大盤雞店鋪密度折線圖.jpg', dpi=700)
plt.show()
通過折線圖可以看出店鋪主要集中在海淀區、朝陽區這兩個區,分布很集中
餅狀圖:
選取店鋪數量最多的四個城區用matplotlib庫繪制餅狀圖,海淀區占比最大
import matplotlib.pyplot as plt
labels = ['東城區','朝陽區','海淀區','房山區']
x = [13,49,100,9]
colors='lightgreen','gold','lightskyblue','lightcoral'
#顯示百分比
#餅圖分離
explode = (0,0.1,0,0)
#設置陰影效果
#startangle,為起始角度,0表示從0開始逆時針旋轉,為第一塊。
plt.pie(x,labels=labels,autopct='%3.2f%%',explode=explode,shadow=True,startangle=90,colors=colors)
#設置x,y的刻度一樣,使其餅圖為正圓
plt.axis('equal')
plt.show()
plt.savefig('C:/Users/dell/Desktop/Python/END/各城區大盤雞店鋪密度.jpg', dpi=700)
地圖-熱力圖:
采用0.5版本的pyecharts庫進行畫圖,安裝:以管理員身份運行cmd窗口,輸入安裝命令:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyecharts==0.5.10
由於使用到了地圖,所以依然通過cmd命令來安裝對應的地圖,因為本次使用到的是北京市的地圖,所以安裝中國市級地圖:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple echarts-china-cities-pypkg
在繪制地圖過程中,由於echarts庫的1.0前的版本和1.0后的版本存在較大的差異,在引用庫上的格式存在着不同,最開始安裝了1.9版本的庫導致沒能成功繪制,后刪除1.9版本的echarts庫,安裝0.5版本的echarts的庫和地圖則可成功繪制。
#0.5版本
from pyecharts import Map
map2 = Map("北京地圖", '北京', width=1200, height=600)
#十二個城區
city = ['昌平區', '朝陽區', '東城區','房山區','豐台區','海淀區','密雲區','平谷區','石景山區','順義區','通州區','西城區']
values2 = [6,49,13,9,9,100,4,1,2,4,9,3]
map2.add('北京', city, values2, visual_range=[1, 100], maptype='北京', is_visualmap=True, visual_text_color='#000')
map2.render(path="北京地圖.html")
print("ok")
還有一個用IDEA做的圖,也就是后綴為.py的文件
import requests
import re
from bs4 import BeautifulSoup
import json
from urllib.request import urlopen,quote
import pandas as pd
import numpy as np
from pylab import *
def getHTMLText(url):
headers={"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36",
"Cookie":"__mta=20871943.1625705574088.1625810520459.1625813097008.16; _lxsdk_cuid=17a839a3528c8-070f1da58fbd21-6373264-144000-17a839a3529c8; ci=1; rvct=1; mtcdn=K; uuid=5226c694e07949bdb828.1625725143.1.0.0; mt_c_token=xR1aknv_B8Yiio_ZWzUmVSFo2koAAAAACw4AAIkyH5SSt867G_XkxBIcCNINlemXinZVHTQDkWGAFQukS3HrBbqc_BktISzd7MhPvw; lsu=; iuuid=C72B4FEF3403F5A0924DEFB4DF9C9FFEC17E5FC35B2B4D6CF55632ABF4817904; isid=xR1aknv_B8Yiio_ZWzUmVSFo2koAAAAACw4AAIkyH5SSt867G_XkxBIcCNINlemXinZVHTQDkWGAFQukS3HrBbqc_BktISzd7MhPvw; logintype=normal; cityname=%E5%8C%97%E4%BA%AC; _lxsdk=C72B4FEF3403F5A0924DEFB4DF9C9FFEC17E5FC35B2B4D6CF55632ABF4817904; webp=1; i_extend=H__a100002__b1; latlng=39.830516,116.290895,1625725257685; __utma=74597006.798321478.1625725258.1625725258.1625725258.1; __utmz=74597006.1625725258.1.1.utmcsr=baidu|utmccn=(organic)|utmcmd=organic; _lx_utm=utm_source%3DBaidu%26utm_medium%3Dorganic; lt=fYaN6SmjIjuKuNSbDGgqCDbdjQgAAAAACw4AABKyZyfS3ZbSub8odXTK9CKxaHAOvtIkpz464sfHyJcIfPPYbpw2Vh_Rnd3Mr5B24A; u=583093909; n=%E9%A5%95%E9%A4%AEzzzzz; token2=fYaN6SmjIjuKuNSbDGgqCDbdjQgAAAAACw4AABKyZyfS3ZbSub8odXTK9CKxaHAOvtIkpz464sfHyJcIfPPYbpw2Vh_Rnd3Mr5B24A; unc=%E9%A5%95%E9%A4%AEzzzzz; __mta=20871943.1625705574088.1625810319319.1625810340857.13; firstTime=1625813095796; _lxsdk_s=17a89d8b4fc-08e-6c7-641%7C%7C48"}
try:
r = requests.get(url,headers=headers,timeout=100)
r.raise_for_status()
r.encoding = r.apparent_encoding#對文本中使用的編碼替換整體的編碼
return r.text
except:
return "失敗"
#將數據寫入csv文件中
def file_data(title,address,avgprice,avgscore,comment):
data={
'店鋪名稱':title,
'店鋪地址':address,
'平均消費價格':avgprice,
'店鋪評分':avgscore,
'評價人數':comment
}
with open('C:/Users/dell/Desktop/美團大盤雞店鋪數據4.csv','a',encoding='utf-8-sig') as fb:
fb.write(json.dumps(data,ensure_ascii=False)+'\n')
#經緯度轉換
def addrlnglat(addr):
try:
url='http://api.map.baidu.com/geocoding/v3/'
addr=quote(addr)#quote編碼
output='json'
ak='zW9us6KSVv4HKwcvcAnTt3DYOzIKtu0L'#API密鑰
uri=url+'?address='+address+'&output='+output+'&ak='+ak
req=urlopen(uri)
res=req.read().decode()
temp=json.loads(res)
lng=temp['result']['location']['lng']
lat=temp['result']['location']['lat']
return lng,lat
except:
return 0,0
#將經緯度存為csv文件
def save(lng,lat):
data={
"經度":lng,
"緯度":lat
}
fpath='C:/Users/dell/Desktop/Python/END/經緯度.csv'
with open(fpath,'a',encoding='utf-8') as f:
f.write(json.dumps(data,ensure_ascii=False)+'\n')
def main():
goods = '大盤雞'#檢索詞
depth = 49#設置爬取的深度,一共50頁
start_url = 'https://bj.meituan.com/s/' + goods
#以下采用for循環對每個頁面URL進行訪問
i=0
for i in range(depth):
try:
url =start_url + '&offset=' + str(32*i)
#url = start_url + str(32*i)
html = getHTMLText(url)
titles = re.findall('","title":"(.*?)","address":"',html)
addresses = re.findall(',"address":"(.*?)",',html)
avgprices = re.findall(',"avgprice":(.*?),', html)
avgscores = re.findall(',"avgscore":(.*?),',html)
comments = re.findall(',"comments":(.*?),',html)
print(titles)
print(addresses)
print(avgprices)
print(avgscores)
print(comments)
#輸出當前返回數據的長度
print(len(titles), len(addresses), len(avgprices), len(avgscores), len(comments))
#將每個店鋪的信息通過循環寫入文件
for j in range(len(titles)):
title=titles[j]
address=addresses[j]
avgprice=avgprices[j]
avgscore=avgscores[j]
comment=comments[j]
#file_data(title,address,avgprice,avgscore,comment)
except:
continue
#printGoodsList(infoList)
main()
在最開始的代碼中我同時將得到的地址通過百度地圖API轉化為具體的經緯度並將經緯度存成csv文件
.py文件中畫圖代碼
從百度API轉換成地圖上的點:
在百度地圖API頁面找到地圖DEMO
選擇所需的示例;
Pycharm創建一個HTML文件,將csv文件中的點替換圖中的坐標點並添加自己的AK,創建點標記,並在地圖上添加點標記,
有空就去778