攜程熱門景區評論爬取#
前言#
由於最近參加了一個比賽,需要爬取雲貴川幾個省會的所有城市的熱門景區的評論以及景點的信息,看了下網上的項目,基本上都試了試,操作太麻煩,有的還需要一個個的去找參數,都不太滿足自己的需求,於是還是自己寫一個吧,首先先來看一下效果吧。
爬取的數據以excel
保存
正在爬取中
通過一會的爬取,我也是成功的爬取了雲貴川三省所有城市的熱門景區,加起來有 28 萬的數據。不容易啊😭😭😭
下面就分享一下這次爬取的過程🚀🚀🚀
注意:本次分享的所有代碼都不是完整代碼,完整代碼見aglorice/CtripSpider: 攜程評論爬蟲,使用線程池來爬取熱門景區評論,簡單易用。一鍵爬取任意省的所有熱門景區。 (github.com)
1. 分析頁面#
首先先進入到攜程攻略.景點
頁面,將光標移動到國內(含港澳台)
就可以得到基本上所有省份的所有城市,這裡就是我們城市數據的來源。
打開控制台,很快就定位到了,如下。
有了這個直接上代碼,這裡我使用的是BeautifulSoup
來解析頁面。
def get_areas(self) -> list:
city_list = []
try:
res = self.sees.get(
url=GET_HOME,
headers={
"User-Agent": get_fake_user_agent("pc")
},
proxies=my_get_proxy(),
timeout=TIME_OUT
)
except Exception as e:
self.console.print(f"[red]獲取城市景區信息失敗,{e},你可以檢查你的網路或者代理。", style="bold red")
exit()
res_shop = BeautifulSoup(res.text, "lxml")
areas = res_shop.find_all("div", attrs={"class": "city-selector-tab-main-city"})
for area in areas:
area_title = area.find("div", attrs={"class": "city-selector-tab-main-city-title"}).string
if area_title is None:
continue
area_items = area.find_all("div", attrs={"class": "city-selector-tab-main-city-list"})
area_items_list = [{"name": item.string, "url": item["href"]} for item in area_items[0].find_all("a")]
city_list.append({
"name": area_title,
"city": area_items_list
})
return city_list
通過這樣的方式將指定的省份的所有的城市名稱和url
全部保存為city.json
,為什麼要先保存起來,主要是為了方便自定義,你可以根據你的需求隨意的增加你想要爬取的城市或者刪減。爬取的結果如下:
緊接著我們打開這些景區的url
,如下,我們可以看到首頁的就是熱門景區或者景點,如下:
前置的工作已經完了,我們現在就來爬取對應景區的評論吧。
2. 景區評論爬取#
隨便打開一個景區的評論,打開控制台查看請求。如下:
首先我們分析一下參數,經過多次嘗試,就可以知道那些是動態的。首先是_fxpcqlniredt
, 檢查一下 cookie 就可以很快找到。
其次就是x-traceID
, 通過對 js 的逆向,我直接找到了相關的代碼。如下:
知道了他是怎麼生成的,那就簡單了,直接上代碼。
def generate_scene_comments_params(self) -> dict:
"""
生成請求景區評論的 params參數
:return:
"""
random_number = random.randint(100000, 999999)
return {
"_fxpcqlniredt": self.sees.cookies.get("GUID"),
"x-traceID": self.sees.cookies.get("GUID") + "-" + str(int(time.time() * 1000000)) + "-" + str(
random_number)
}
其實到這裡也差不多了,現在只需要解決poild
問題,其實這個在每個頁面上都有,在每個景區的頁面下的script
標籤裡都有這個參數。但是這樣就多了一次請求太浪費時間了,如果能直接請求數據數據就好了,而不需要進入景點頁面。那么我們就來轉變一下思路。我們進入到攜程的h5
頁面。發現它的獲取評論接口和pc
端的不一樣,如下:
在手機端上,就不需要使用poild
這個參數了,其實到這裡就已經結束了,剩下的就是解決再爬取得過程中各種各樣問題,最重要得就是攜程得反爬,由於我為了更快使用了線程池,速度很快,為了解決這個問題,我使用了隨機ua
, 以及代理池,在加上各種容錯機制,使得爬取能夠穩定運行。如下就是接口頻繁訪問得結果:
3. 攜程反爬的解決辦法#
解決反爬得第一個就是隨機得ua
,在之前我使用得是fake-useragent
,但是由於後面我使用了h5
我的接口,於是ua
必須是移動端的,但是這個庫不支持,於是就自己手動完成了個,簡單但實用。
# -*- coding = utf-8 -*-
# @Time :2023/7/13 21:32
# @Author :小岳
# @Email :[email protected]
# @PROJECT_NAME :scenic_spots_comment
# @File : fake_user_agent.py
from fake_useragent import UserAgent
import random
from config import IS_FAKE_USER_AGENT
def get_fake_user_agent(ua: str, default=True) -> str:
match ua:
case "mobile":
if IS_FAKE_USER_AGENT and default:
ua = get_mobile_user_agent()
return ua
else:
return "Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Mobile Safari/537.36 Edg/114.0.0.0"
case "pc":
if IS_FAKE_USER_AGENT and default:
ua = UserAgent()
return ua.random
else:
return "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Mobile Safari/537.36 Edg/103.0.1264.49"
def get_mobile_user_agent() -> str:
platforms = [
'iPhone; CPU iPhone OS 14_6 like Mac OS X',
'Linux; Android 11.0.0; Pixel 5 Build/RD1A.201105.003',
'Linux; Android 8.0.0; Pixel 5 Build/RD1A.201105.003',
'iPad; CPU OS 14_6 like Mac OS X',
'iPad; CPU OS 15_6 like Mac OS X',
'Linux; U; Android 9; en-us; SM-G960U Build/PPR1.180610.011', # Samsung Galaxy S9
'Linux; U; Android 10; en-us; SM-G975U Build/QP1A.190711.020', # Samsung Galaxy S10
'Linux; U; Android 11; en-us; SM-G998U Build/RP1A.200720.012', # Samsung Galaxy S21 Ultra
'Linux; U; Android 9; en-us; Mi A3 Build/PKQ1.180904.001', # Xiaomi Mi A3
'Linux; U; Android 10; en-us; Mi 10T Pro Build/QKQ1.200419.002', # Xiaomi Mi 10T Pro
'Linux; U; Android 11; en-us; LG-MG870 Build/RQ1A.210205.004', # LG Velvet
'Linux; U; Android 11; en-us; ASUS_I003D Build/RKQ1.200826.002', # Asus ROG Phone 3
'Linux; U; Android 10; en-us; CLT-L29 Build/10.0.1.161', # Huawei P30 Pro
]
browsers = [
'Chrome',
'Firefox',
'Safari',
'Opera',
'Edge',
'UCBrowser',
'SamsungBrowser'
]
platform = random.choice(platforms)
browser = random.choice(browsers)
match browser:
case 'Chrome':
version = random.randint(70, 90)
return f'Mozilla/5.0 ({platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{version}.0.#{random.randint(1000, 9999)}.#{random.randint(10, 99)} Mobile Safari/537.36'
case 'Firefox':
version = random.randint(60, 80)
return f'Mozilla/5.0 ({platform}; rv:{version}.0) Gecko/20100101 Firefox/{version}.0'
case 'Safari':
version = random.randint(10, 14)
return f'Mozilla/5.0 ({platform}) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/{version}.0 Safari/605.1.15'
case 'Opera':
version = random.randint(60, 80)
return f'Mozilla/5.0 ({platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{version}.0.#{random.randint(1000, 9999)}.#{random.randint(10, 99)} Mobile Safari/537.36 OPR/{version}.0'
case 'Edge':
version = random.randint(80, 90)
return f'Mozilla/5.0 ({platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{version}.0.#{random.randint(1000, 9999)}.#{random.randint(10, 99)} Mobile Safari/537.36 Edg/{version}.0'
case 'UCBrowser':
version = random.randint(12, 15)
return f'Mozilla/5.0 ({platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 UBrowser/{version}.1.2.49 Mobile Safari/537.36'
case 'SamsungBrowser':
version = random.randint(10, 14)
return f'Mozilla/5.0 ({platform}) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/{version}.0 Chrome/63.0.3239.26 Mobile Safari/537.36'
剩下得就是線程池,這裡得使用得是一個開源項目jhao104/proxy_pool: Python 爬蟲代理 IP 池 (proxy pool) (github.com)
解決到這裡,其實已經差不多了。👀👀👀
4. 尾言#
通過這次對攜程的爬取,其實也可以總結一些經驗,在遇到問題的時候的可以多去拓寬思路,不行就去多嘗試。
項目地址 aglorice/CtripSpider: 攜程評論爬蟲,使用線程池來爬取熱門景區評論,簡單易用。一鍵爬取任意省的所有熱門景區。 (github.com)