携程酒店爬取并保存到MongoDB数据库

由于本人十一国庆想去成都旅游,所以这里就以成都这个城市的所有携程酒店为抓取的目标城市。想要抓取其他城市或者多个城市的博友们,可以更改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}")

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孜孜孜孜不倦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值