由於本人十一國慶想去成都旅遊,所以這裏就以成都這個城市的所有攜程酒店爲抓取的目標城市。想要抓取其他城市或者多個城市的博友們,可以更改url爲其他城市拼音+城市id。或者直接將城市接口數據(js)爬取下來去遍歷城市列表在循環頁面。有興趣的朋友可以去試試爬取全國的數據。
一、開始分析攜程酒店頁面數據結構及其反爬的一些方式
經過嘗試一點下一頁,發現頁面url是沒有變化的,將源碼加載到本地,可以看到完整的url鏈接
所以直接在城市路由後邊接着跟頁碼的路由路徑就可以實現翻頁的效果了
而且頁面中的數據基本上可以拿到。除了酒店的價格信息是使用js動態加載的
在源碼中可以找到動態加載的價格數據(酒店id:價格)
到時候只需要在酒店div數據下將酒店的id爬取下來,然後再將該數據取出來,轉成字典。重構字典數據,格式爲:酒店id數字:價格數字
二、進行頁面請求,數據提取
這裏數據提取,就不用了之前的xpath進行提取了。因爲在網上看了一個新的解析庫pyquery,所以就以此工具搭配requests工具進行頁面請求和數據抓取。pyquery語法基於css語法。
數據提取代碼如下:
import re
import json
import requests
from pyquery import PyQuery as pq
url = 'https://hotels.ctrip.com/hotel/chengdu28/p1'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
}
res = requests.get(url,headers=headers).content.decode('utf-8')
# with open('xiecheng.html','w',encoding='utf-8')as fp:
# fp.write(res)
# print(res)
# 使用pyquery加載HTML頁面
html = pq(res)
# 抓取動態加載的價格數據
hllist = re.findall(r"htllist: '(.*?)',",res)[0]
# 將json格式字符串轉爲python類型 [{},{},...]
hllist = json.loads(hllist)
# 重新構建字典{’39341191‘:’299‘}
hotel_price = {}
for i in hllist:
hotel_id = i['hotelid']
amount = i['amount']
hotel_price[hotel_id] = amount
print(hotel_price)
# 總頁數
page_total = html('#txtpage').attr('data-pagecount')
print('總頁數:',page_total)
# 取出酒店塊列表,itmes方法返回的是一個生成器對象
items = html('#hotel_list > div > ul').items()
print(items)
i = 1
for ul in items:
# 酒店id,用去拿取動態渲染的數據,
hotel_id = ul.find('.hotel_pic a').attr('data-hotel')
print(hotel_id)
# 標題
print(i)
title = ul.find('.hotel_name > a').attr('title')
print(title)
# 地址
address = ul.find('.hotel_item_htladdress').text()
address = re.findall(r'(.*?)。 地圖',address)[0]
print(address)
# 文字評分
score_text = ul.find('.hotel_level').text()
# 評分
score = ul.find('.hotel_value').text()
print(score_text,score)
# 用戶推薦率
user_recommend = ul.find('.total_judgement_score').text().replace('用戶推薦','')
print(user_recommend)
# 用戶點評數
user_comment_num = ul.find('.hotel_judgement').text().replace('源自','').replace('位住客點評','')
print(user_comment_num)
# 用戶推薦點評語
recommend = ul.find('.recommend').text().replace('\n',' ')
print(recommend)
# 價格
price = '¥' + hotel_price[hotel_id] + '起'
print(price)
i += 1
print('=============================================================')
抓取結果如下:
三、MongoDB數據庫保存
這裏創建一個MongoDB數據創建,保存,查詢的類,用於數據保存及查詢
class MyMongoDB():
def __init__(self,database,sets):
# 1.創建客戶端
self.client = pymongo.MongoClient('localhost')
# 2.創建數據庫
self.db = self.client[database]
# 3.創建集合
self.collection = self.db[sets]
def insert_one(self,dic):
"""
一次插入一條數據
:param dic: 字典數據
:return:
"""
try:
self.collection.insert(dic)
print('數據保存成功!')
except:
print('數據保存失敗!')
def insert_many(self,lst):
"""
一次性插入多條數據
:param lst: 列表套字典
:return:
"""
try:
self.collection.insert_many(lst)
print('數據保存成功!')
except:
print('數據保存失敗!')
def find(self):
"""
查詢所有數據
:return:
"""
data = self.collection.find()
print('數據查詢如下:')
# users = self.collection.find({'age': {'$gt': 19}})
for i in data:
print(i)
然後將每個酒店的數據存儲到一個字典中,調用MongoDB類就可以進行保存了
進入MongoDB可視化軟件中查看結果:
四、代碼優化,使用多線程封裝爲類
這裏使用隊列進行多線程數據請求提取(先進先出)
import re
import time
import json
import pymongo
import requests
import threading
from queue import Queue
from pyquery import PyQuery as pq
from fake_useragent import UserAgent
import urllib3
urllib3.disable_warnings()
sess = requests.session()
headers = {'user-agent':UserAgent().random}
class MyMongoDB():
def __init__(self,database,sets):
# 1.創建客戶端
self.client = pymongo.MongoClient('localhost')
# 2.創建數據庫
self.db = self.client[database]
# 3.創建集合
self.collection = self.db[sets]
def insert_one(self,dic):
"""
一次插入一條數據
:param dic: 字典數據
:return:
"""
try:
self.collection.insert(dic)
print('數據保存成功!')
except:
print('數據保存失敗!')
def insert_many(self,lst):
"""
一次性插入多條數據
:param lst: 列表套字典
:return:
"""
try:
self.collection.insert_many(lst)
print('數據保存成功!')
except:
print('數據保存失敗!')
def find(self):
"""
查詢所有數據
:return:
"""
data = self.collection.find()
print('數據查詢如下:')
# users = self.collection.find({'age': {'$gt': 19}})
for i in data:
print(i)
class SpiderCrawl(threading.Thread):
def __init__(self,t_name,url_queue):
super(SpiderCrawl, self).__init__()
# 線程名
self.t_name = t_name
# url隊列
self.url_queue = url_queue
def run(self):
while True:
# 判斷隊列是否爲空,爲空則跳出循環,結束任務
if self.url_queue.empty():
break
url = self.url_queue.get()
print(f'\033[0;36m{self.t_name}線程開始抓取{url}\033[0m')
self.html_request(url)
# 睡眠2s,要不然服務器檢測阻斷鏈接
time.sleep(2)
print()
print('\033[0;31m++++++++++++++++++++++++++++++++++++++++++++\033[0m')
# 頁面請求
def html_request(self,url):
"""
頁面請求
:param url: 目標鏈接
:return: 響應的網頁內容
"""
ua = UserAgent()
headers = {
'User-Agent': ua.random,
# 'origin': 'https://hotels.ctrip.com',
# 'referer': 'https://hotels.ctrip.com/hotel/chengdu28/'
}
response = sess.get(url, headers=headers,verify=False).content.decode('utf-8')
# print(sess.cookies)
hotel_price = self.reset_price_data(response)
self.html_parse(response,hotel_price)
# 重構酒店價格數據(酒店價格爲動態渲染)
def reset_price_data(self,response):
"""
重構酒店價格數據
:param response: 響應的頁面內容
:return: 酒店id:價格 對應的字典數據
"""
hotel_price = {}
# 正則匹配js動態加載的價格數據json字符串
try:
hllist = re.findall(r"htllist: '(.*?)',", response)[0]
# 將json格式字符串轉爲python類型 [{},{},...]
hllist = json.loads(hllist)
# 重新構建字典{’39341191‘:’299‘}
# hotel_price = {}
for i in hllist:
# 取出酒店id
hotel_id = i['hotelid']
# 取出酒店價格
amount = i['amount']
# id:價格 鍵值對
hotel_price[hotel_id] = amount
except:
print('沒有抓取到酒店價格信息')
return hotel_price
# pyquery頁面解析
def html_parse(self,response,hotel_price):
"""
頁面解析
:param response: requests請求的響應內容
:param hotel_price: 重構的價格數據
:return: 總頁碼
"""
# 使用pyquery加載HTML頁面
html = pq(response)
# 總頁數
# page_total = html('#txtpage').attr('data-pagecount')
# print('總頁數:', page_total)
# 取出酒店塊列表元素,itmes方法返回的是一個生成器對象
items = html('#hotel_list > div > ul').items()
for ul in items:
data = {}
# 酒店id,用去拿取動態渲染的數據,
hotel_id = ul.find('.hotel_pic a').attr('data-hotel')
# print(hotel_id)
# 標題
title = ul.find('.hotel_name > a').attr('title')
# print(title)
# 地址
address = ul.find('.hotel_item_htladdress').text()
try:
address = re.findall(r'(.*?)。 地圖', address)[0]
except:
address = ''
# print(address)
# 文字評分
score_text = ul.find('.hotel_level').text()
# 評分
score = ul.find('.hotel_value').text()
# print(score_text, score)
# 用戶推薦率
user_recommend = ul.find('.total_judgement_score').text().replace('用戶推薦', '')
# print(user_recommend)
# 用戶點評數
user_comment_num = ul.find('.hotel_judgement').text().replace('源自', '').replace('位住客點評', '')
# print(user_comment_num)
# 用戶推薦點評語
recommend = ul.find('.recommend').text().replace('\n', ' ')
# print(recommend)
# 價格
if hotel_price:
price = '¥' + hotel_price[hotel_id]
else:
price = ''
# print(price)
data['hotel_id'] = hotel_id
data['title'] = title
data['price'] = price
data['score_text'] = score_text
data['score'] = score
data['user_recommend'] = user_recommend
data['user_comment_num'] = user_comment_num
data['recommend'] = recommend
data['address'] = address
# 實例化MongoDB
mongo = MyMongoDB('xiecheng','hotel')
mongo.insert_one(data)
print(data)
print('=============================================================')
# 創建隊列
url_queue = Queue()
flag = True
if __name__ == "__main__":
start_time = time.time()
base_url = 'https://hotels.ctrip.com/hotel/chengdu28'
base_res = sess.get(url=base_url,headers=headers).content.decode('utf-8')
try:
page = int(re.findall(r'到<input class="c_page_num" id="txtpage" type="text" value="1"data-pagecount=(.*?) name="" />頁',
base_res, re.S)[0])
except:
print('暫時獲取不到總頁數,指定page爲100')
page = 100
print(page)
for i in range(1,page+1):
url = f'https://hotels.ctrip.com/hotel/chengdu28/p{i}'
url_queue.put(url)
# 起線程,完成任務,指定5個任務完成所有數據的爬取
# 指定5個任務,存放到列表中,並沒有任何實際意義
thread_names = ['T1', 'T2', 'T3','T4','T5']
thread_list = []
for t_name in thread_names:
thread = SpiderCrawl(t_name, url_queue)
thread.start()
thread_list.append(thread)
for t in thread_list:
t.join()
end_time = time.time()
print(f"整個程序耗時:{end_time - start_time}")