NO.31——Python爬蟲分析馬蜂窩十一假期城市旅遊資料
十一假期開始,開啟朋友圈,看到小夥伴們紛紛晒出了自己的車票,不是出去玩就是回家。因為不可抗拒的因素,可憐的我只能堅守工作崗位,哪都去不了,心急難耐之餘,雖然自己去不了,那就看看全國各地的廣大旅友都喜歡去什麼地方吧。
這裡,資料來源是馬蜂窩http://www.mafengwo.cn/。首先,馬蜂窩對爬蟲相對友好,另外,使用馬蜂窩也是我和女友出遊的習慣,在計劃去某地前都會先在馬蜂窩上查查攻略,不得不佩服很多小夥伴寫的遊記真的超級棒,起到事半功倍的效果。
目標:
通過分析馬蜂窩中提及到某目的地的景點、餐飲、娛樂三個方面的遊記做定量分析,客觀程度上反映出某目的地的熱門程度。
工具:
selenium自動化測試工具
ChromeDriver
pandas
pyecharts
原理:
Selenium是一個自動化測試工具,利用它可以驅動瀏覽器執行特定的動作,如點選、下拉等操作,還可以獲取瀏覽器當前呈現的頁面的原始碼,做到可見即可爬。在這裡可以利用Selenium模擬點選,做一些翻頁操作。
步驟1:獲取城市編號
馬蜂窩中的所有城市或目的地都有一個專屬的五位數字編號,要想獲得該城市或目的地的具體資訊,首先要獲取該目的地(直轄市或地級市)的城市編號,然後進行後續的分析。
如上圖所示,在目的地欄進入某個省份,以雲南為例,總共有206個目的地。以上兩個頁面就是我們的城市編碼來源,首先在目的地頁面獲得各省編碼,之後進入各省的城市列表獲得城市編碼。這裡採用Selenium進行動態資料爬取,獲取城市編碼的程式碼如下:
# -*- coding: utf-8 -*- """ Created on Tue May 29 21:53:47 2018 @author: slash """ import os import time from urllib.request import urlopen from urllib import request from bs4 import BeautifulSoup import pandas as pd from selenium import webdriver os.chdir('/Users/Macx/Desktop/python_demo/mafengwo_data-master') ## 獲得地區url地址 def find_cat_url(url): headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0'} req=request.Request(url,headers=headers) html=urlopen(req) #指定使用html.parser解析器進行解析,目前支援lxml, html5lib, 和 html.parser bsObj=BeautifulSoup(html.read(),"html.parser") #按照屬性名和標籤名找到所有目的地,目的地名放在dt標籤裡 bs = bsObj.find('div',attrs={'class':'hot-list clearfix'}).find_all('dt') cat_url = [] cat_name = [] #遍歷所有目的地 for i in range(0,len(bs)): #遍歷某個目的地的所有地區名,地區名放在a標籤裡 for j in range(0,len(bs[i].find_all('a'))): #通過href屬性查詢地區網址進行新增 cat_url.append(bs[i].find_all('a')[j].attrs['href']) #通過a標籤查詢地區名進行新增 cat_name.append(bs[i].find_all('a')[j].text) cat_url = ['http://www.mafengwo.cn'+cat_url[i] for i in range(0,len(cat_url))] return cat_url ## 獲得城市url地址 def find_city_url(url_list): city_name_list = [] city_url_list = [] for i in range(0,len(url_list)): driver = webdriver.Chrome() driver.maximize_window() url = url_list[i].replace('travel-scenic-spot/mafengwo','mdd/citylist') driver.get(url) while True: try: time.sleep(2) bs = BeautifulSoup(driver.page_source,'html.parser') url_set = bs.find_all('a',attrs={'data-type':'目的地'}) city_name_list = city_name_list +[url_set[i].text.replace('\n','').split()[0] for i in range(0,len(url_set))] city_url_list = city_url_list+[url_set[i].attrs['data-id'] for i in range(0,len(url_set))] #模擬滾動條向下滾動800個畫素 js="var q=document.documentElement.scrollTop=1000" #呼叫JS指令碼 driver.execute_script(js) time.sleep(2) driver.find_element_by_class_name('pg-next').click() except: break driver.close() return city_name_list,city_url_list ## 執行程式碼 url = 'http://www.mafengwo.cn/mdd/' url_list = find_cat_url(url) city_name_list,city_url_list=find_city_url(url_list) #從字典構造DataFrame city = pd.DataFrame({'city_name':city_name_list,'city_code':city_url_list}) city.to_csv('city.csv')
最後,將爬取的城市編碼作為一個二維陣列放入一個表格裡。總共得到3281條資料。
步驟2:獲取城市具體資訊
這裡,主要獲取馬蜂窩中的城市印象標籤、景點、餐飲、娛樂四個板塊的資訊。
(1)城市印象標籤
(2)景點頁面
(2)餐飲頁面
(3)娛樂頁面
將每個城市獲取資料的過程封裝成函式,每次傳入之前先獲得城市編碼:
# -*- coding: utf-8 -*-
"""
Created on Sat Jun 2 16:46:19 2018
@author: slash
"""
import os
from urllib.request import urlopen
from urllib import request
from bs4 import BeautifulSoup
import pandas as pd
from pyecharts import Bar,Geo,Grid
os.chdir('/Users/Macx/Desktop/python_demo/mafengwo_data-master')
## 獲得城市url內容
def get_static_url_content(url):
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0'}
req=request.Request(url,headers=headers)
html=urlopen(req)
bsObj=BeautifulSoup(html.read(),"html.parser")
return bsObj
## 獲得城市資訊
def get_city_info(city_name,city_code):
this_city_base = get_city_base(city_name,city_code)
#景點
try:
this_city_jd = get_city_jd(city_name,city_code)
this_city_jd['city_name'] = city_name
this_city_jd['total_city_yj'] = this_city_base['total_city_yj']
except:
this_city_jd=pd.DataFrame()
#餐飲
try:
this_city_food = get_city_food(city_name,city_code)
this_city_food['city_name'] = city_name
this_city_food['total_city_yj'] = this_city_base['total_city_yj']
except:
this_city_food=pd.DataFrame()
#娛樂
try:
this_city_yl = get_city_yl(city_name,city_code)
this_city_yl['city_name'] = city_name
this_city_yl['total_city_yj'] = this_city_base['total_city_yj']
except:
this_city_yl=pd.DataFrame()
return this_city_base,this_city_jd,this_city_food,this_city_yl
#從這裡開始進入!!!!
## 獲得城市各類標籤資訊
def get_city_base(city_name,city_code):
url = 'http://www.mafengwo.cn/xc/'+str(city_code)+'/'
bsObj = get_static_url_content(url)
#<a href="/yl/10186/1487.html" target="_blank">
#酒吧<em> 4088</em> </a>
#在社群行程頁面尋找城市印象的標籤,如麗江印象
node = bsObj.find('div',{'class':'m-box m-tags'}).find('div',{'class':'bd'}).find_all('a')
#尋找印象提及次數的標籤
tag_node = bsObj.find('div',{'class':'m-box m-tags'}).find('div',{'class':'bd'}).find_all('em')
#將提及次數的text文字資訊轉化成整型
tag_count = [int(k.text) for k in tag_node]
#<a href="/yl/10186/1487.html" target="_blank">
#其中不同標籤有不同代號,看該標籤是屬於娛樂還是餐飲還是景點還是購物
#<a href="/yl/10186/1487.html" target="_blank">
par = [k.attrs['href'][1:3] for k in node]
#所有印象被提及次數的總和
tag_all_count = sum([int(tag_count[i]) for i in range(0,len(tag_count))])
#有多少人的遊記中提到該城市的景點
tag_jd_count = sum([int(tag_count[i]) for i in range(0,len(tag_count)) if par[i]=='jd'])
#有多少人的遊記中提到該城市的餐飲
tag_food_count = sum([int(tag_count[i]) for i in range(0,len(tag_count)) if par[i]=='cy'])
#有多少人的遊記中提到該城市的娛樂
tag_yl_count = sum([int(tag_count[i]) for i in range(0,len(tag_count)) if par[i] in ['gw','yl']])
#第一頁
url = 'http://www.mafengwo.cn/yj/'+str(city_code)+'/2-0-1.html '
bsObj = get_static_url_content(url)
#<span class="count">共<span>391</span>頁 / <span>5860</span>條</span>
#下滑後檢視頁碼和記錄條數,記錄總的遊記數量
total_city_yj = int(bsObj.find('span',{'class':'count'}).find_all('span')[1].text)
return {'city_name':city_name,'tag_all_count':tag_all_count,'tag_jd_count':tag_jd_count,
'tag_food_count':tag_food_count,'tag_yl_count':tag_yl_count,
'total_city_yj':total_city_yj}
## 獲得某個城市具體那些食物的資訊
def get_city_food(city_name,city_code):
#進到目的地餐飲頁面
#<ol class="list-rank">
# <li class="rank-item top1">
# <a href="/cy/10819/1664.html" title="牛肉麵">
# <span class="r-img"><img src="http://n2-q.mafengwo.net/s6/M00/9C/BC/wKgB4lMNs5GAAShMAAE0pvbJJvQ35.jpeg?imageMogr2%2Fthumbnail%2F%21135x100r%2Fgravity%2FCenter%2Fcrop%2F%21135x100%2Fquality%2F90" width="110" height="70"><em class="r-num">1</em></span>
# <h3>牛肉麵</h3>
# <span class="trend"><i></i>501</span> <p><span class="num-orange">501</span> 遊記提及</p>
# <p><span class="num-blue">4</span> 推薦美食</p>
# </a>
# </li>
url = 'http://www.mafengwo.cn/cy/'+str(city_code)+'/gonglve.html'
bsObj = get_static_url_content(url)
#餐飲名稱
food=[k.text for k in bsObj.find('ol',{'class':'list-rank'}).find_all('h3')]
#餐飲推薦次數
food_count=[int(k.text) for k in bsObj.find('ol',{'class':'list-rank'}).find_all('span',{'class':'trend'})]
return pd.DataFrame({'food':food[0:len(food_count)],'food_count':food_count})
## 獲得某個城市具體那些景點的資訊
def get_city_jd(city_name,city_code):
url = 'http://www.mafengwo.cn/jd/'+str(city_code)+'/gonglve.html'
bsObj = get_static_url_content(url)
#找到景點名稱標籤
node=bsObj.find('div',{'class':'row row-top5'}).find_all('h3')
jd = [k.text.split('\n')[2] for k in node]
#<span class="rev-total"><em>5833</em> 條點評</span>
node=bsObj.find_all('span',{'class':'rev-total'})
#將字串格式轉化成整型
jd_count=[int(k.text.replace(' 條點評','')) for k in node]
return pd.DataFrame({'jd':jd[0:len(jd_count)],'jd_count':jd_count})
## 獲得某個城市具體那些娛樂的資訊
def get_city_yl(city_name,city_code):
url = 'http://www.mafengwo.cn/yl/'+str(city_code)+'/gonglve.html'
bsObj = get_static_url_content(url)
#娛樂標籤名稱
yl=[k.text for k in bsObj.find('ol',{'class':'list-rank'}).find_all('h3')]
#娛樂推薦次數
yl_count=[int(k.text) for k in bsObj.find('ol',{'class':'list-rank'}).find_all('span',{'class':'trend'})]
return pd.DataFrame({'yl':yl[0:len(yl_count)],'yl_count':yl_count})
## 執行函式
city_list = pd.read_csv('city.csv')
#資料幀(DataFrame)是二維資料結構,即資料以行和列的表格方式排列
city_base = pd.DataFrame()
city_food = pd.DataFrame()
city_jd = pd.DataFrame()
city_yl = pd.DataFrame()
#讀取矩陣第一維度的長度
for i in range(0,city_list.shape[0]):
try:
#iloc是根據標籤所在的位置,從0開始計數
#loc根據列的具體名稱進行選取
k = city_list.iloc[i]
#city_name是str型別 city_code是64int型
this_city_base,this_city_jd,this_city_food,this_city_yl=get_city_info(k['city_name'],k['city_code'])
city_base=city_base.append(this_city_base,ignore_index=True)
#axis=0,按照行數首尾連結
city_food = pd.concat([city_food,this_city_food],axis=0)
city_jd = pd.concat([city_jd,this_city_jd],axis=0)
city_yl = pd.concat([city_yl,this_city_yl],axis=0)
print(i)
print('正確:'+k['city_name'])
except:
print(i)
print('錯誤:'+k['city_name'])
continue
## 繪製圖片
#######################################對城市作分析##########################################
#ascending=False 降序排列 ,ascending=True, 升序排列 inplace預設為True
city_base.sort_values('total_city_yj',ascending=False,inplace=True)
attr1 = city_base['city_name'][0:10]
#提到某城市的遊記總數量
v1 = city_base['total_city_yj'][0:10]
#提到某城市景點的遊記總數量
v2 = city_base['tag_jd_count'][0:15]
#提到某城市餐飲的遊記總數量
v3 = city_base['tag_food_count'][0:15]
#提到某城市娛樂的遊記總數量
v4 = city_base['tag_yl_count'][0:15]
bar1 = Bar("遊記TOP15")
#"遊記總數"為標題,attr為橫座標城市名稱,v1為縱座標遊記總數
bar1.add("遊記總數", attr1, v1, is_stack=True)
bar1.render('遊記總數量TOP10.html')
city_base.sort_values('tag_jd_count',ascending=False,inplace=True)
attr_jd = city_base['city_name'][0:15]
bar2 = Bar("景點類標籤排名")
bar2.add("景點類標籤分數", attr_jd, v2, is_splitline_show=False,xaxis_rotate=30)
city_base.sort_values('tag_food_count',ascending=False,inplace=True)
attr_food = city_base['city_name'][0:15]
bar3 = Bar("餐飲類標籤排名")
bar3.add("餐飲類標籤分數", attr_food, v3, legend_top="30",is_splitline_show=False,xaxis_rotate=30)
city_base.sort_values('tag_yl_count',ascending=False,inplace=True)
attr_yl = city_base['city_name'][0:15]
bar4 = Bar("休閒類標籤排名")
bar4.add("休閒類標籤分數", attr_yl, v4, legend_top="67.5",is_splitline_show=False,xaxis_rotate=30)
grid = Grid(height=800)
grid.add(bar2,grid_bottom="75%")
grid.add(bar3,grid_bottom="37.5%",grid_top="37.5%")
grid.add(bar4,grid_top="75%")
grid.render('城市分類標籤.html')
'''
#遍歷CSV中的每一行資料,城市名稱和每個城市提到的遊記數量
data=[(city_base['city_name'][i],city_base['total_city_yj'][i]) for i in range(0,
city_base.shape[0])]
#地理座標系Geo
geo = Geo('馬蜂窩全國城市旅遊熱力圖', title_color="#fff",
title_pos="center", width=1200,
height=600, background_color='#404a59')
attr, value = geo.cast(data)
geo.add("", attr, value, visual_range=[0, 30000], visual_text_color="#fff",
symbol_size=15, is_visualmap=True,is_roam=False)
geo.render('螞蜂窩全國城市旅遊熱力圖.html')
'''
#########################################對景點作分析#####################################
city_jd.sort_values('jd_count',ascending=False,inplace=True)
city_food.sort_values('food_count',ascending=False,inplace=True)
city_yl.sort_values('yl_count',ascending=False,inplace=True)
attr2 = city_jd['jd'][0:15]
attr3 = city_food['food'][0:15]
attr4 = city_yl['yl'][0:15]
v22 = city_jd['jd_count'][0:15]
v33 = city_food['food_count'][0:15]
v44 = city_yl['yl_count'][0:15]
bar11=Bar("景點人氣排名")
bar11.add("景點人氣分數", attr2, v22, is_splitline_show=False,xaxis_rotate=30)
bar22=Bar("餐飲人氣排名")
bar22.add("餐飲人氣分數", attr3, v33, legend_top="30",is_splitline_show=False,xaxis_rotate=30)
bar33 = Bar("休閒人氣排名")
bar33.add("休閒人氣分數", attr4, v44, legend_top="67.5",is_splitline_show=False,xaxis_rotate=30)
grid = Grid(height=800)
grid.add(bar11,grid_bottom="75%")
grid.add(bar22,grid_bottom="37.5%",grid_top="37.5%")
grid.add(bar33,grid_top="75%")
grid.render('人氣排名.html')
步驟3:資料視覺化分析
(1)熱門城市Top10
通過提煉提及到每個城市的遊記數量,排列出受歡迎程度前十名的城市如圖所示,不出意料,小清新的廈門果然受到廣大旅友的青睞。在年初三月份的時候和女友一同去了鼓浪嶼、曾厝垵等地方,印象真的很好。
(2)城市分類標籤
按提及到的景點、餐飲、娛樂對城市進行排名,果然,廈門又英勇奪魁。
(3)人氣排名
然後再分別看看景點、餐飲、休閒類的人氣排名,看看大家到底喜歡什麼地方。出乎意料的是,大家最喜歡逛的景點是第一市場,這個第一市場是什麼鬼,沒聽過!!!!不過後幾名的鼓浪嶼、錦裡、麗江古城、西湖還是在情理之中的。
因為去廈門的最多,自然而然排名前二的美食就是沙茶麵和海蠣煎嘍,本人也超級喜歡哈哈。
(4)馬蜂窩全國城市旅遊熱力圖。
在這裡,主要想看看大家的足跡都涉及到哪些城市。過程中遇到了個問題,首先從馬蜂窩提取出的目的地名稱是不包含“市”、“縣”、“區”這些字眼的,然而pyecharts帶的地圖資源包的json檔案中的鍵名包含了這些字眼,因此畫圖時總出現鍵名不匹配的bug。除了對json包進行修改,暫時沒想到其他方法,但3281條資料量比較大,就不修改了,這裡僅修改了雲南的50個目的地做演示。