python 爬蟲 一鍵爬取攜程旅遊團數據

python 爬蟲 一鍵爬取攜程旅遊團數據

前言

最近我的好朋友在做期末大作業,需要分析疫情前後對旅遊行業的影響。於是,就求助我,想讓我幫忙趴取一下攜程旅遊團的評價數據,包括評價時間、評價內容、評分這三條數據。

網站分析

第一步 查看首頁數據格式

首先我們打開協程網站,隨便搜一個地點,這裏我以重慶爲例:

地址:https://vacations.ctrip.com/list/whole/d-chongqing-158.html?from=do&startcity=2

在這裏插入圖片描述

大概就是這麼個頁面形式,爲了減少工作量的同時,獲取到更多的數據,我們採用銷量優先的篩選模式,對這些參團遊進行重新排序。

在這裏插入圖片描述

我們可以看到,排名前幾名的都有非常可觀的評價數,這裏我們就取排名前4的旅遊團

我們在當前頁面按F12 進入開發者模式,看看當前呈現在我們面前的數據的原貌是什麼

這裏需要注意一點,進入開發者模式後,我們需要先點擊另一個排序模式,再點回銷量優先:
在這裏插入圖片描述

這樣做的原因就是單純的刷新頁面,由於有些數據之前已經加載過了,就不會再進行請求了。

對了,在進行第二次點擊時,注意點擊clear (就在紅點旁邊),清除一下

這次,我們來看看加載了哪些數據:

在這裏插入圖片描述
這裏我們先選擇XHR進行篩選,過濾掉圖片、js、css等文件。

這麼多請求,哪個是我們需要的呢?最好的辦法呢,就是一個一個看!

這裏我就不給大家演示了,就直接用紅色框標出來我們需要的

在這裏插入圖片描述
我們將右邊的json數據的進行展開,會發現,在products這個裏面,就是我們需要的數據:
在這裏插入圖片描述

這裏我把products種的第一個數據賦值過來大家看看:

{
            "id":1020423035,
            "name":"重慶4日自由行·【爆紅名宿&網紅拍照聖地】精選民宿·套房任選丨全家出動·渝見你的溫馨時光丨走步道·坐索道·看輕軌·賞夜景·吃火鍋·不一樣的旅行",
            "type":1,
            "typeName":"自由行",
            "level":0,
            "price":0,
            "departureCityId":2,
            "saleMode":"S",
            "brandName":"攜程自營",
            "saleCount":0,
            "commentCount":44,
            "commentScore":4.6,
            "buType":"GT",
            "saleout":false,
            "hotelName":"濱之星商務度假公寓(重慶解放碑店) 重慶斯維登服務公寓(解放碑協信公館) 重慶兩江公館服務公寓(解放碑大唐諾亞店) 重慶渝佳酒店公寓解放碑日月光店 重慶斯維登服務公寓(解放碑大唐諾亞) 重慶大唐諾亞服務公寓 重慶雲尚輕奢酒店公寓 重慶解放碑日月光雲菲度假公寓 重慶一瓦澗精品民宿酒店 coco北歐小居住宿(重慶解放碑店) 久棲·重慶日月光酒店公寓解放碑店",
            "hotelStar":0,
            "hotelStarLicence":0,
            "hotelCustomEval":0,
            "correlationScore":0
        }

經過英文提示和數據的對比,我們獲取我們需要的信息

id:每個旅行項目的編號
saleMode:銷售模式,S 表示self  猜測是自營的意思

至於多少人購買,總和評分這些我們不需要,在這裏就不說了

找到我們要請求接口了,我們看看需要提交哪些數據吧:

在這裏插入圖片描述

{"contentType":"json","head":{"cid":"09031160411534832517","ctok":"","cver":"1.0","lang":"01","sid":"8888","syscode":"09","auth":"","extension":[]},"version":"80400","client":{"trace":"none","device":"PC","source":"NVacationSearchV2","variables":[{"key":"SHXVERSION","value":"B"}],"cid":"1590973576257.dfu8z"},"poiType":{"poid":158,"type":"D","keyword":"重慶"},"filtered":{"sort":2,"channel":"Online","tab":"A126","saleCity":2,"startCity":2,"pageSize":30,"pageIndex":1,"items":[]},"returnType":{"type":"all","filters":"ProductNewLine,ProductLine,HotDestination,HotScenicSpot,SaleDepartureStat,TravelDays,DepartureDate,Month,ProductPattern,ProductLevel,ADSuitPersons,ProductDistrict,ProviderBrand,PriceRange,Promotion,OnSale","recommendProduct":true}}

這就是一個json形式的數據,然後我們看一下請求地址:https://vacations.ctrip.com/list/restapi/gateway/13561/search?_fxpcqlniredt=09031160411534832517

我們只需要構造請求數據,然後發送到請求地址就可以獲取我們想要的數據啦,接着往下看吧

第二步 查看詳細頁數據格式

前面我們獲取了首頁的數據,接下來面臨一個問題:我們如何通過主頁進入到詳情頁呢?

通常我們都是先在html數據頁面獲取每個旅行項目詳情頁的鏈接,但是,這個項目並不直接給你提供這樣的鏈接,而是通過js進行自行組裝(我猜測的)。先不管了,我們先點擊看看鏈接形式是什麼樣的

在這裏插入圖片描述
地址鏈接:https://vacations.ctrip.com/tour/detail/p1020423035s2.html

我們再點擊第二個旅遊項目的詳情頁:

鏈接地址:https://vacations.ctrip.com/tour/detail/p2563522s2.html

我們再拿出來前面我們獲取的第一個旅遊項目的id編號:1020423035

這下我們能夠發現,這兩個鏈接只有中間數字上的不同,而且,我們這中間的數字不是別的,就像該旅行項目的id編號。

我們有了詳情頁的地址,那麼獲取詳情頁的數據豈不是輕而易舉?

NO NO NO !!!

事情沒那麼簡單,這裏我不帶大家驗證了,直接告訴大家,用戶評價的數據是通過js後續請求加載進來的,直接通過請求htm地址頁面,無法獲取,除非你使用一些其他高級的手段,比如使用selenium工具包進行真實瀏覽器訪問。扯遠了 ,我們接下來還是通過F12控制檯尋找數據

在這裏插入圖片描述

老樣子,XHR 篩選數據,通過一個一個查找以及英文提示,我們很快的就能找到我們所需要的數據:

{
            "commentId":13705053,
            "originalId":0,
            "userId":"300****168",
            "userInfo":{
                "displayName":"300****168",
                "avatarUrl":"https://dimg04.c-ctrip.com/images/t1/headphoto/646/318/109/a865fe5f5465407d8befd5dc487a8554.jpg",
                "curLevelCode":"10",
                "curLevelName":"黃金貴賓"
            },
            "content":"難忘的一次旅行,走了幾個經典路線,喜歡上重慶這座城市!重慶獨特的城市文化,令人震撼的自然景觀,都給我留下了深刻的印象!感謝導遊小潘,幺妹兒熱情,民宿老闆娘熱情周到的服務!",
            "commentTime":1579774408793,
            "score":5,
            "essential":false,
......

這裏我們只是comments下的第一條數據,我們需要以下這幾個數據:

content:評論內容
commentTime: 評論時間  這裏是時間戳 需要轉化爲正常的時間格式
score:評分

老規矩,接下來看看請求地址和請求數據:

請求地址:https://vacations.ctrip.com/tour/restapi/online/15656/listProductComments.json?_fxpcqlniredt=09031160411534832517

請求數據(同樣是一段json數據):

{"ChannelCode":0,"Version":"810000","Locale":"zh-CN","head":{"cid":"09031160411534832517","ctok":"","cver":"1.0","lang":"01","sid":"8888","syscode":"09","auth":"","extension":[]},"productId":1020423035,"paging":{"pageSize":5,"pageNo":1},"sortType":2,"tagTerms":[],"PlatformId":4,"contentType":"json"}

這裏我們需要注意幾個數據:

pageNo:1 第幾頁
productId:1020423035 產品編號 也就是旅行項目編號

爲什麼只需要注意這幾個數據呢,是因爲不同旅行項目詳情頁productId 值不一樣,查看所有評論

好了 分析就到此結束了

代碼實操:

一、獲取旅行項目的名字和id

def test1(destination):
    headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
             'Cookie':'_abtest_userid=d778cf10-b380-4da2-977a-b3fd72a9934d; _RF1=120.216.170.214; _RSG=xQKVBUHW1u6FBqc9suTio8; _RDG=28014134c27fb82c9e1cd0973a907c8ce8; _RGUID=6e018117-8b7d-46bb-8cac-778d899b42cf; Session=smartlinkcode=U130026&smartlinklanguage=zh&SmartLinkKeyWord=&SmartLinkQuary=&SmartLinkHost=; Union=AllianceID=4897&SID=130026&OUID=&createtime=1590973579&Expires=1591578378915; MKT_CKID=1590973578972.3e60b.vtue; MKT_CKID_LMT=1590973578973; MKT_Pagesource=PC; _ga=GA1.2.776587014.1590973579; _gid=GA1.2.428582162.1590973579; StartCity_Pkg=PkgStartCity=2; GUID=09031160411534832517; appFloatCnt=1; manualclose=1; _jzqco=%7C%7C%7C%7C%7C1.1607786070.1590973578966.1590975682555.1590976896014.1590975682555.1590976896014.0.0.0.8.8; __zpspc=9.1.1590973578.1590976896.8%232%7Cwww.baidu.com%7C%7C%7C%7C%23; _bfa=1.1590973576257.dfu8z.1.1590973576257.1590973576257.1.31; _bfs=1.31; _bfi=p1%3D104317%26p2%3D104317%26v1%3D31%26v2%3D30; _gat=1',
             'origin':'https://vacations.ctrip.com',
             'x-req-src':'{"appId":"100020727","from":"vacations.ctrip.com/list/whole/d-chongqing-158.html","version":"8300.103","os":"PC","platform":"Online"}',
             'content-type':'application/json'}
    data={"contentType":"json","head":{"cid":"09031160411534832517","ctok":"","cver":"1.0","lang":"01","sid":"8888","syscode":"09","auth":"","extension":[]},"version":"80400","client":{"trace":"none","device":"PC","source":"NVacationSearchV2","variables":[{"key":"SHXVERSION","value":"B"}],"cid":"1590973576257.dfu8z"},"poiType":{"poid":158,"type":"D","keyword":destination},"filtered":{"sort":2,"channel":"Online","tab":"A126","saleCity":2,"startCity":2,"pageSize":30,"pageIndex":1,"items":[]},"returnType":{"type":"all","filters":"ProductNewLine,ProductLine,HotDestination,HotScenicSpot,SaleDepartureStat,TravelDays,DepartureDate,Month,ProductPattern,ProductLevel,ADSuitPersons,ProductDistrict,ProviderBrand,PriceRange,Promotion,OnSale","recommendProduct":True}}
    url="https://vacations.ctrip.com/list/restapi/gateway/13561/search?fxpcqlniredt=09031160411534832517"
    data = json.dumps(data)
    re = requests.post(url,headers=headers,data=data)
    jsondata = re.json()
    listdata=[]
    for i in range(4):
        listdata.append([jsondata['products'][i]['name'],jsondata['products'][i]['id']])
        #print(jsondata['products'][i]['name'],jsondata['products'][i]['id'],)
    return listdata
  • 這裏首先我們構造請求頭 其中就包括 Cookie、User-Agent、origin、x-req-src

  • 構造一個data數據,這個數據就是之前咱們提到的第一個json數據,這裏面有一個destination 參數,代表的是地名,比如重慶、廣州、山東這些等。

  • 我們通過json 進行將數據轉化爲json,即時有時不需要這樣轉化,但是還是建議大家去做,因爲有時候回報 無效json 的錯誤。

  • 通過requests.post 進行請求訪問,for循環4次獲取前四個旅遊項目的姓名和id,這裏我存到了list列表中。

二、獲取詳情頁數據:

def test2(id):
    headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36',
             'Cookie':'_abtest_userid=d778cf10-b380-4da2-977a-b3fd72a9934d; _RF1=120.216.170.214; _RSG=xQKVBUHW1u6FBqc9suTio8; _RDG=28014134c27fb82c9e1cd0973a907c8ce8; _RGUID=6e018117-8b7d-46bb-8cac-778d899b42cf; Session=smartlinkcode=U130026&smartlinklanguage=zh&SmartLinkKeyWord=&SmartLinkQuary=&SmartLinkHost=; Union=AllianceID=4897&SID=130026&OUID=&createtime=1590973579&Expires=1591578378915; MKT_CKID=1590973578972.3e60b.vtue; MKT_CKID_LMT=1590973578973; MKT_Pagesource=PC; _ga=GA1.2.776587014.1590973579; _gid=GA1.2.428582162.1590973579; StartCity_Pkg=PkgStartCity=2; GUID=09031160411534832517; appFloatCnt=1; manualclose=1; _jzqco=%7C%7C%7C%7C%7C1.1607786070.1590973578966.1590975682555.1590976896014.1590975682555.1590976896014.0.0.0.8.8; __zpspc=9.1.1590973578.1590976896.8%232%7Cwww.baidu.com%7C%7C%7C%7C%23; _bfa=1.1590973576257.dfu8z.1.1590973576257.1590973576257.1.31; _bfs=1.31; _bfi=p1%3D104317%26p2%3D104317%26v1%3D31%26v2%3D30; _gat=1',
             'origin':'https://vacations.ctrip.com',
             'x-req-src':'{"appId":"100007656","from":"vacations.ctrip.com/tour/detail/p1020423035s2.html","version":"8300.103","os":"PC","platform":"Online"}',
             'content-type':'application/json'}
    for i in range(1,10):
        try:
            pagenum = i
            data={"ChannelCode":0,"Version":"810000","Locale":"zh-CN","head":{"cid":"09031160411534832517","ctok":"","cver":"1.0","lang":"01","sid":"8888","syscode":"09","auth":"","extension":[]},"productId":id,"paging":{"pageSize":5,"pageNo":pagenum},"sortType":2,"tagTerms":[],"PlatformId":4,"contentType":"json"}
            url="https://vacations.ctrip.com/tour/restapi/online/15656/listProductComments.json?_fxpcqlniredt=09031160411534832517"
            data = json.dumps(data)
            re = requests.post(url,headers=headers,data=data)
            jsondata = re.json()
            for i in range(5):
                print("\n-----------------------")
                print("評論內容:",jsondata['comments'][i]['content'])#獲取評論數據
                print("評分:",jsondata['comments'][i]['score'])#獲取評分
                datatime = str(jsondata['comments'][i]['commentTime'])[:-3]
                times = time.localtime(int(datatime))
                datatime = time.strftime("%Y-%m-%d %H:%M:%S",times)
                print("日期:",datatime)#獲取日期
                print("-----------------------\n")
            time.sleep(2)
        except:
            break
  • 還是構造headers 和data數據,這裏面data中有兩個參數,一個是id 項目編碼,另一個pagenum 是當前評價頁面。這裏我做了一個處理,只取前9也得評價,如果評價不夠9頁怎麼辦,沒有關係,會直接出發報錯,運行break 跳出循環。

  • 緊接着for循環5次,是因爲一個頁面有五條評論數據

  • 獲取到的時間數據是時間戳,這裏時間戳是13位,平常python中處理的時間戳是10位,是不包括毫秒的,這裏我們先轉化爲字符串,截取前10個字符,再轉化爲整型。

  • 通過time.localtime 和 time.strftime 方法將時間戳轉化爲年-月-日 時:分:秒的格式。

  • 爲了爬取太過於頻繁,我們設置一個2s的休息時間 time.sleep(2)

三、總方法

def  mainfun():
    traveldestination = ['重慶','廣州','鄭州','三亞','上海','杭州','北京','成都']#這裏是添加旅遊地點
    for i in range(len(traveldestination)):
        listdata = test1(traveldestination[i])
        print("地點:",traveldestination[i],end='\n')
        for data in listdata:
            print(data[0])
            test2(data[1])
  • 我們可以往列表 traveldestination中添加我們想爬取的地點
  • 通過循環不斷地獲取評價數據

四、可改進地方

  1. 使用多線程加快爬取速度,並且多線程有同步機制,輸出數據時也不會出現混亂
  2. 考慮地情況不足,比如旅行團有非自營的

五、執行結果

地點: 重慶
重慶4日自由行·【爆紅名宿&網紅拍照聖地】精選民宿·套房任選丨全家出動·渝見你的溫馨時光丨走步道·坐索道·看輕軌·賞夜景·吃火鍋·不一樣的旅行

-----------------------
評論內容: 難忘的一次旅行,走了幾個經典路線,喜歡上重慶這座城市!重慶獨特的城市文化,令人震撼的自然景觀,都給我留下了深刻的印象!感謝導遊小潘,幺妹兒熱情,民宿老闆娘熱情周到的服務!
評分: 5
日期: 2020-01-23 18:13:28
-----------------------


-----------------------
評論內容: 一瓦澗精品民宿
老闆非常熱情周到!有緣讓我們相識,感謝這幾天對我們的關照,給我們做早餐發紅包。實在不好意思[害羞]。真是倍感親切,有朋友來就讓她們直接去找你哦。期待下次我們再相聚[親親][好的]
當地一日遊任師傅服務熱情講解到位,引領出行深入重慶瞭解感受當地民風民俗。很滿意謝謝!
評分: 4
日期: 2020-01-27 11:24:14
-----------------------


-----------------------
評論內容: 在攜程上搜的自由行套餐,行程安排的很好,導遊也不錯,最棒的是一瓦澗民宿,老闆娘很熱情,服務很周到,房間有暖氣,老闆娘給放了很多水果和堅果,還邀請我們一起吃早飯,介紹好玩的好吃的地方,房間很舒服,環境也很棒,位置出行也很方便,非常讚的一次旅行
評分: 5
日期: 2020-01-24 15:45:09
-----------------------


-----------------------
評論內容: 很好的旅行體驗。
評分: 5
日期: 2020-03-17 01:38:15
-----------------------

上述時部分執行數據,有了這些數據,就可以做一些數據分析了

總結

原本以爲非常簡單的事情,還是花了一上午時間,所以,我們還是多去實際動手做一些東西,儘管有些東西非常簡單,但是中間可能也會出現一些動態性的錯誤。如果有需要完整代碼的話,可以在評論中留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章