1. 程式人生 > >用python爬蟲爬取去哪兒4500個熱門景點,看看國慶不能去哪兒

用python爬蟲爬取去哪兒4500個熱門景點,看看國慶不能去哪兒

前言:本文建議有一定Python基礎和前端(html,js)基礎的盆友閱讀。

金秋九月,丹桂飄香,在這秋高氣爽,陽光燦爛的收穫季節裡,我們送走了一個個暑假餘額耗盡哭著走向校園的孩籽們,又即將迎來一年一度偉大祖國母親的生日趴體(無心上班,迫不及待想為祖國母親慶生!)。

那麼問題來了,去哪兒玩呢?百度輸了個“國慶”,出來的第一條居然是“去哪裡旅遊人少”……emmmmmmm,因缺思廳。

於是我萌生了通過旅遊網站的景點銷量來判斷近期各景點流量情況的想法(這個想法很危險啊)。

所以這次的目標呢,是爬去哪兒網景點頁面,並得到景點的資訊,大家可以先思考下大概需要幾步。

1.百度的地圖API和echarts

這次正好爬的是資料,我決定用資料的好基友——圖表來輸出我爬取的資料,也就是說我要用爬取的景點銷量以及景點的具體位置來生成一些視覺化資料。

安利一下百度的地圖APIecharts,前者是專門提供地圖API的工具,聽說好多APP都在用它,後者是資料處理居家旅行的好夥伴,用了之後,它好,我也好(隱約覺得哪裡不對)。

API是什麼,API是應用程式的程式設計介面,就好像插頭與插座一樣,我們的程式需要電(這是什麼程式?),插座中提供了電,我們只需要在程式中寫一個與插座匹配的插頭介面,就可以使用電來做我們想做的事情,而不需要知道電是如何產生的。

引入資料後的百度熱力圖

再詳細一點講呢,就好比米醬的小說寫完啦!但她還想把小說出成書,可是怎麼出書捏?米醬不會呀,這時候米醬發現某出版社提供了出版服務,出版社表示只需要提供小說的正文、以及一個設計的封面就可以啦,於是米醬將小說儲存成了word格式,又畫了個封面jpg圖,發給了出版社,沒過多久米醬就拿到了一本裝訂好的書啦(此段純屬虛構,專業出版人士儘管打我,我不會承認的)。

在米醬出書的過程中,米醬並不需要知道出版社是怎麼印刷這個書的,也不需要知道是怎麼裝訂這個書的,米醬只需要提供出版社所要求的東西即可。

通過api對♂接的開發者與服務商

2.確定輸出檔案

有人可能說,我已經懂了api是啥意思了,可是咋個用呢。關於這一點,米醬很負責任的告訴你:我也不會

但是!

百度地圖提供了很多API使用示例,有html基礎,大致可以看懂,有js基礎就可以嘗試改函數了(不會jsの我默默地複製原始碼),仔細觀察原始碼,可以知道熱力圖的生成主要的資料都存放在points這個變數中。

這種[{x:x,x:x},{x:x,x:x}]格式的資料,是一種json格式的資料,由於具有自我描述性,所以比較通俗易懂,大概可以知道這裡的三個值,前倆個是經緯度,最後一個應該是權重(我猜的)。

也就是說,如果我希望將景點的熱門程度生成為熱力圖,我需要得到景點的經緯度,以及它的權重,景點的銷量可以作為權重,並且這個資料應該是json格式的呈現方式。

echarts也是一樣滴(*^__^*)。

3.爬取資料

其實這次的爬蟲部分是比較簡單的(如果你有跟著我的文爬過網站的話)。

分析網址(去哪兒景點)→爬取分頁中資訊(景點經緯度、銷量)→轉為json檔案。

這次沒有用正則來匹配內容,而使用了xpath匹配,肥腸好用。

def getList():
    place = raw_input('請輸入想搜尋的區域、型別(如北京、熱門景點等):')
    url = 'http://piao.qunar.com/ticket/list.htm?keyword='+ str(place) +'&region=&from=mpl_search_suggest&page={}'
    i = 1
    sightlist = []
    while i:
        page = getPage(url.format(i))
        selector = etree.HTML(page)
        print '正在爬取第' + str(i) + '頁景點資訊'
        i+=1
        informations = selector.xpath('//div[@class="result_list"]/div')
        for inf in informations: #獲取必要資訊
            sight_name = inf.xpath('./div/div/h3/a/text()')[0]
            sight_level = inf.xpath('.//span[@class="level"]/text()')
            if len(sight_level):
                sight_level = sight_level[0].replace('景區','')
            else:
                sight_level = 0
            sight_area = inf.xpath('.//span[@class="area"]/a/text()')[0]
            sight_hot = inf.xpath('.//span[@class="product_star_level"]//span/text()')[0].replace('熱度 ','')
            sight_add = inf.xpath('.//p[@class="address color999"]/span/text()')[0]
            sight_add = re.sub('地址:|(.*?)|\(.*?\)|,.*?$|\/.*?$','',str(sight_add))
            sight_slogen = inf.xpath('.//div[@class="intro color999"]/text()')[0]
            sight_price = inf.xpath('.//span[@class="sight_item_price"]/em/text()')
            if len(sight_price):
                sight_price = sight_price[0]
            else:
                i = 0
                break
            sight_soldnum = inf.xpath('.//span[@class="hot_num"]/text()')[0]
            sight_url = inf.xpath('.//h3/a[@class="name"]/@href')[0]
            sightlist.append([sight_name,sight_level,sight_area,float(sight_price),int(sight_soldnum),float(sight_hot),sight_add.replace('地址:',''),sight_slogen,sight_url])
        time.sleep(3)
    return sightlist,place

1.這裡把每個景點的所有資訊都爬下來了(其實是為了練習使用xpath……)。
2.使用了while迴圈,for迴圈的break的方式是發現無銷量時給i值賦零,這樣while迴圈也會同時結束。
3.地址的匹配使用re.sub()函式去除了n多複雜資訊,這點後面解釋。

4.輸出本地文字

為了防止程式碼執行錯誤,為了維護程式碼執行的和平,將輸出的資訊列表存入到excel檔案中了,方便日後查閱,很簡單的程式碼,需要了解pandas的用法。

def listToExcel(list,name):
    df = pd.DataFrame(list,columns=['景點名稱','級別','所在區域','起步價','銷售量','熱度','地址','標語','詳情網址'])
    df.to_excel(name + '景點資訊.xlsx')

5.百度經緯度api

肥腸悲傷的,(ಥ﹏ಥ)我沒找到去哪兒景點的經緯度,以為這次學(zhuang)習(bi)計劃要就此流產了。(如果有人知道景點經緯度在哪裡請告訴我)

但是,enhahhahahaha,我怎麼會放棄呢,我又找到了百度經緯度api,網址:http://api.map.baidu.com/geocoder/v2/?address=地址&output=json&ak=百度金鑰,修改網址裡的“地址”和“百度金鑰”,在瀏覽器開啟,就可以看到經緯度的json資訊。

#上海市東方明珠的經緯度資訊
{"status":0,"result":{"location":{"lng":121.5064701060957,"lat":31.245341811634675},"precise":1,"confidence":70,"level":"UNKNOWN"}}

這樣我就可以根據爬到的景點地址,查到對應的經緯度辣!python獲取經緯度json資料的程式碼如下。

def getBaiduGeo(sightlist,name):
    ak = '金鑰'
    headers = {
    'User-Agent' :'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'
    }
    address = 地址
    url = 'http://api.map.baidu.com/geocoder/v2/?address=' + address  + '&output=json&ak=' + ak
    json_data = requests.get(url = url).json()
    json_geo = json_data['result']['location']

觀察獲取的json檔案,location中的資料和百度api所需要的json格式基本是一樣,還需要將景點銷量加入到json檔案中,這裡可以瞭解一下json的淺拷貝和深拷貝知識,最後將整理好的json檔案輸出到本地檔案中。

def getBaiduGeo(sightlist,name):
    ak = '金鑰'
    headers = {
    'User-Agent' :'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'
    }
    list = sightlist
    bjsonlist = []
    ejsonlist1 = []
    ejsonlist2 = []
    num = 1
    for l in list:
        try:
            try:
                try:
                    address = l[6]
                    url = 'http://api.map.baidu.com/geocoder/v2/?address=' + address  + '&output=json&ak=' + ak
                    json_data = requests.get(url = url).json()
                    json_geo = json_data['result']['location']
                except KeyError,e:
                    address = l[0]
                    url = 'http://api.map.baidu.com/geocoder/v2/?address=' + address  + '&output=json&ak=' + ak
                    json_data = requests.get(url = url).json()
                    json_geo = json_data['result']['location']
            except KeyError,e:
                    address = l[2]
                    url = 'http://api.map.baidu.com/geocoder/v2/?address=' + address  + '&output=json&ak=' + ak
                    json_data = requests.get(url = url).json()
                    json_geo = json_data['result']['location']
        except KeyError,e:
            continue
        json_geo['count'] = l[4]/100
        bjsonlist.append(json_geo)
        ejson1 = {l[0] : [json_geo['lng'],json_geo['lat']]}
        ejsonlist1 = dict(ejsonlist1,**ejson1)
        ejson2 = {'name' : l[0],'value' : l[4]/100}
        ejsonlist2.append(ejson2)
        print '正在生成第' + str(num) + '個景點的經緯度'
        num +=1
    bjsonlist =json.dumps(bjsonlist)
    ejsonlist1 = json.dumps(ejsonlist1,ensure_ascii=False)
    ejsonlist2 = json.dumps(ejsonlist2,ensure_ascii=False)
    with open('./points.json',"w") as f:
        f.write(bjsonlist)
    with open('./geoCoordMap.json',"w") as f:
        f.write(ejsonlist1)
    with open('./data.json',"w") as f:
        f.write(ejsonlist2)

(╯’ - ‘)╯┻━┻

在設定獲取經緯度的地址時,為了匹配到更準確的經緯度,我選擇了匹配景點地址,然鵝,景點地址裡有各種神奇的地址,帶括號解釋在XX對面的,說一堆你應該左拐右拐各種拐就能到的,還有英文的……於是就有了第三章中複雜的去除資訊(我終於圓回來了!)。

然鵝,就算去掉了複雜資訊,還有一些匹配不到的景點地址,於是我使用了巢狀try,如果景點地址匹配不到;就匹配景點名稱,如果景點名稱匹配不到;就匹配景點所在區域,如果依然匹配不到,那我……那我就……那我就跳過ㄒ_ㄒ……身為一個景點,你怎麼能,這麼難找呢!不要你了!

這裡生成的三個json檔案,一個是給百度地圖api引入用的,另倆個是給echarts引入用的。

6.網頁讀取json檔案

將第二章中所述的百度地圖api示例中的原始碼複製到直譯器中,新增金鑰,儲存為html檔案,開啟就可以看到和官網上一樣的顯示效果。echarts需要在例項頁面,點選頁面右上角的EN切換到英文版,然後點選download demo下載完整原始碼。

根據html匯入json檔案修改網頁原始碼,匯入json檔案。

#百度地圖api示例程式碼中各位置修改部分
<head>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.js"></script>
</head>
<script type="text/javascript">
    $.getJSON("points.json", function(data){
        var points = data;
        script中原有函式;
        });
</script>       

這裡使用了jQuery之後,即使網頁除錯成功了,在本地開啟也無法顯示網頁了,在chrome中右鍵檢查,發現報錯提示是需要在伺服器上顯示,可是,伺服器是什麼呢?

百度了一下,可以在本地建立一個伺服器,在終端進入到html檔案所在資料夾,輸入python -m SimpleHTTPServer,再在瀏覽器中開啟http://127.0.0.1:8000/,記得要將html檔名設定成index.html哦~

7.後記

因為註冊但沒有認證開發者賬號,所以每天只能獲取6K個經緯度api(這是一個很好的偷懶理由),所以我選擇了熱門景點中前400頁(每頁15個)的景點,結果可想而知,(ಥ﹏ಥ)為了除錯因為資料增多出現的額外bug,最終的獲取的景點資料大概在4k5條左右(爬取時間為2017年09月10日,爬取關鍵詞:熱門景點,僅代表當時銷量)。

熱門景點熱力圖

熱門景點示意圖

這些地圖上很火爆的區域,我想在國慶大概是這樣的

這樣的

還有這樣的

將地圖上熱門景點的銷量top20提取出來,大多數都是耳熟能詳的地點,帝都的故宮排在了第一位,而大四川則佔據了top5中的三位,而排在top20中也四川省就佔了6位,如果不是因為地震,我想還會有更多的火爆的景點進入排行榜的~這樣看來如果你這次國慶打算去四川的話,可以腦補到的場景就是:人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人……

熱門景點銷量top20

於是我又做了一個各城市包含熱門景點數目的排行,沒想到在4千多個熱門景點中,數目最多的竟是我大浙江,是第二個城市的1.5倍,而北京作為首都也……可以說是景點數/總面積的第一位了。

主要城市熱門景點數.png

這些城市有辣麼多熱門景點,都是些什麼級別的景點呢?由下圖看來,各城市的各級別景點基本與城市總熱門景點呈正相關,而且主要由4A景區貢獻而來。

主要城市熱門景點級別

既然去哪些地方人多,去哪裡景多都已經知道了,那再看看去哪些地方燒得錢最多吧?下圖是由各城市景點銷售起步價的最大值-最小值扇形組成的圓,其中湖北以單景點銷售起步價600佔據首位,但也可以看到,湖北的景點銷售均價並不高(在紅色扇形中的藏藍色線條)。而如果國慶去香港玩,請做好錢包減肥的心理和生理準備(•̀ω•́)✧。

各省旅遊景點銷售起步價

好啦分析完啦,ヾ(*ΦωΦ)ツ大家可要好好玩呀。

PS:寫了個網頁,展示百度地圖的熱力圖效果和echarts的景點排行榜,方便大家檢視。http://easyinfo.online 原始碼已經上傳到gayhub啦~寫完這篇文的時候發現echarts有針對python的模組可以引入,所以打算去學一下Django、Flask之類的web框架,最近會更一些純理論的意識流文,大家一起進步吧~

建了一個qq群,歡迎各位來交♂流學♀習→python交友娛樂會所:613176398
為了營造良好的交流環境,入群需回答問題:匹配資訊用什麼表示式,請認真回答,否則會被拒絕的哦~