【旅游景点项目日记 | 第二篇】基于Python中的Selenium爬取携程旅游网景点详细数据

在这里插入图片描述

Gitee仓库地址:travel-server:景点旅游项目服务端

3.基于Python中的Selenium爬取携程旅游网景点详细数据

3.1前提环境

  1. 确保安装python3.x环境
  2. 以管理员身份打开cmd,安装selenium、pymysql、datetime,默认安装最新版即可
pip install selenium
pip install pymysql
pip install datetime
  1. 确保chrome安装对应版本的驱动(将该驱动放在chrome安装路径下),用于控制chrome浏览器,并将路径添加到环境变量的Path变量中,如图所示!

    #安装chrome驱动教程链接:
    https://blog.csdn.net/linglong_L/article/details/136283810
    

    image-20240324223035454

3.2思路

  1. 搜索指定城市景点,网站通过分页进行展示
  2. 使用selenium每个景点的详细访问路径,并点击该路径获取详细景点信息,再通过正则表达式获取需要的内容

image-20240324220832823

  1. 如下图,景点的详细信息有:景点名称、景点等级(1-5A)、景点地址、开放时间(有两种,我们采用下面的)、联系电话、景点介绍、景点图片等内容

    image-20240324221249083

3.3代码详讲

3.3.1查询指定城市的所有景点

  • 控制打开chrome,并访问指定查询所有景点路径
    def __init__(self):
        options = Options()
        options.add_argument('--headless')
        service = Service()
        self.chrome = Chrome(service=service)
        self.chrome.get(
            'https://huodong.ctrip.com/things-to-do/list?pagetype=city&citytype=dt&keyword=%E6%A2%85%E5%B7%9E&id=523&name=%E6%A2%85%E5%B7%9E&pshowcode=Ticket2&kwdfrom=srch&bookingtransactionid=1711160613361_6064')
        time.sleep(3)
        self.page = 1
        self.headers = {
            'cookie': 'suid=lh/P1+4RKuhAYg684ErS+g==; suid=lh/P1+4RKuhAYg684ErS+g==',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36',
        }

3.3.2获取详细景点的访问路径

  • 使用selenium的根据class定位元素方法,找到详细景点的href属性,即为该景点的访问路径
  • 并通过page属性控制访问的页数
	#获取景点请求路径
    def get_url(self):
        while True:

            content = self.chrome.find_element(By.CLASS_NAME, "right-content-list").get_attribute('innerHTML')
            cons = re.findall(r'href="(.*?)" title="(.*?)"', content)
            for con in cons:
                self.detail_url = 'https:' + con[0]
                self.title = con[1]
                print(self.detail_url, self.title)
                self.get_detail()
            self.chrome.find_element(By.CLASS_NAME,'u_icon_enArrowforward').click()
            time.sleep(1)

            self.page += 1
            if self.page == 120:
                break

image-20240324222130900

3.3.3获取景点的详细信息

  • 景点的详细信息有:景点名称、景点等级(1-5A)、景点地址、开放时间(有两种,我们采用下面的)、联系电话、景点介绍、景点图片等内容
  • 通过正则表达式获取,详细代码如下:
  • 并每次获取详细信息之后,将信息保存到mysql数据库中
    def get_detail(self):
        detail_con = requests.get(self.detail_url, verify=False, headers=self.headers).text
        # time.sleep(2)
        '''使用正则获取信息'''
        self.title = ''.join(re.findall(r'<div class="title"><h1>(.*?)<', detail_con, re.DOTALL))
        print('景点名称:'+self.title)

        #self.rank = ''.join(re.findall(r'rankText">(.*?)<', detail_con, re.DOTALL))

        self.address = ''.join(re.findall(r'地址</p><p class="baseInfoText">(.*?)<', detail_con, re.DOTALL))
        self.mobile = ''.join(re.findall(r'官方电话</p><p class="baseInfoText">(.*?)<', detail_con, re.DOTALL))
        self.quality_grade= ''.join(re.findall(r'<div class="titleTips"><span>(.*?)<!--', detail_con, re.DOTALL))
        #self.openTime = ''.join(re.findall(r'开放时间</div><div class="moduleContent">(.*?)<', detail_con, re.DOTALL))

        first_three_characters = self.address[:3]
        print('所在省份城市:'+'广东省'+first_three_characters)

        print('详细地址:'+self.address)
        #print('开放时间:'+self.openTime)
        print('电话:'+self.mobile)
        print('等级:'+self.quality_grade)
        if self.quality_grade=='':
            self.quality_grade=0

        '''使用xpath获取信息'''
        ret = etree.HTML(detail_con)
        desc_cons = ret.xpath('//div[@class="detailModule normalModule"]//div[@class="moduleContent"]')
        desc_titles = ret.xpath('//div[@class="detailModule normalModule"]//div[@class="moduleTitle"]')
        desc_list = []
        desc_title_list = []
        for d in desc_cons:
            des = ''.join(d.xpath('.//text()'))
            desc_list.append(des)
        for d in desc_titles:
            des = ''.join(d.xpath('.//text()'))
            desc_title_list.append(des)
        desc_dict = dict(zip(desc_title_list, desc_list))
        #print(desc_dict)

        first_value = list(desc_dict.values())[:2]  # 获取前两个值
        if len(first_value) >= 1:
            introduction = first_value[0]
        else:
            introduction = ''

        if len(first_value) >= 2:
            opening_hours = first_value[1]
        else:
            opening_hours = ''

        print('介绍:'+introduction)
        print('开放时间:'+ opening_hours)

        '''获取图片链接'''
        img_list = []
        imgs = re.findall(r'background-image:url\((.*?)\)', detail_con, re.DOTALL)
        for img in imgs:
            '''匹配到的同一张图片会有两种尺寸,我们只要大图,所以把尺寸为521*391的匹配出来即可'''
            image = re.search(r'521_391', img)
            if image:
                img_list.append(img)
        print(",".join(img_list))

        conn = pymysql.connect(host='localhost', user='root', password='root',
                               database='travel_ams', charset='utf8mb4')
        cursor = conn.cursor()

        sql = "INSERT INTO ams_attraction (attraction_name, quality_grade, province_city, location, open_hour, phone, introduction, images, add_time) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)"
        values = (self.title, self.quality_grade, '广东省'+first_three_characters, self.address, opening_hours, self.mobile, introduction,",".join(img_list)  ,datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
        cursor.execute(sql, values)

        conn.commit()
        conn.close()

        #self.get_ticket()

3.4数据库设计

travel_ams数据库的ams_attraction表

字段名字段类型是否为主键是否有唯一约束是否有非空约束注释
attraction_idint景点id,自增
attraction_namevarchar(20)景点名称
resource_type_idint景点资源类型id
quality_gradeint景点等级
province_cityvarchar(20)景点所在省份城市
locationvarchar(1000)详细位置
open_hourvarchar(1000)开放时间
phonevarchar(1000)电话
introductionvarchar(10000)景点介绍
imagesvarchar(1000)景点图片列表
stausint状态【1为显示,0为不显示】
add_timedatetime添加时间
update_timedatetime修改时间

创建表语句如下:

CREATE TABLE ams_attraction (
    attraction_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '景点id,自增',
    attraction_name VARCHAR(20) COMMENT '景点名称',
    resource_type_id INT COMMENT '景点资源类型id',
    province_city VARCHAR(20) COMMENT '景点所在省份城市',
    location VARCHAR(1000) COMMENT '详细位置',
    open_hour VARCHAR(1000) COMMENT '开放时间',
    phone VARCHAR(1000) COMMENT '电话',
    introduction VARCHAR(10000) COMMENT '景点介绍',
    images VARCHAR(1000) COMMENT '景点图片列表',
    status INT COMMENT '状态【1为显示,0为不显示】',
    add_time DATETIME COMMENT '添加时间',
    update_time DATETIME COMMENT '修改时间'
);

3.5全部代码

  • 执行该main方法,即可完成导入指定访问路径的景点数据
  • 可以在控制台查询是否导入成功
import pandas
import re
import time
import requests
import urllib3
from lxml import etree
from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
import pymysql
import datetime



urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class Jy_jd(object):
    def __init__(self):
        options = Options()
        options.add_argument('--headless')
        service = Service()
        self.chrome = Chrome(service=service)
        self.chrome.get(
            'https://huodong.ctrip.com/things-to-do/list?pagetype=city&citytype=dt&keyword=%E6%A2%85%E5%B7%9E&id=523&name=%E6%A2%85%E5%B7%9E&pshowcode=Ticket2&kwdfrom=srch&bookingtransactionid=1711160613361_6064')
        time.sleep(3)
        self.page = 1
        self.headers = {
            'cookie': 'suid=lh/P1+4RKuhAYg684ErS+g==; suid=lh/P1+4RKuhAYg684ErS+g==',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36',
        }

    #获取景点请求路径
    def get_url(self):
        while True:

            content = self.chrome.find_element(By.CLASS_NAME, "right-content-list").get_attribute('innerHTML')
            cons = re.findall(r'href="(.*?)" title="(.*?)"', content)
            for con in cons:
                self.detail_url = 'https:' + con[0]
                self.title = con[1]
                print(self.detail_url, self.title)
                self.get_detail()
            self.chrome.find_element(By.CLASS_NAME,'u_icon_enArrowforward').click()
            time.sleep(1)

            self.page += 1
            if self.page == 120:
                break

    def get_detail(self):
        detail_con = requests.get(self.detail_url, verify=False, headers=self.headers).text
        # time.sleep(2)
        '''使用正则获取信息'''
        self.title = ''.join(re.findall(r'<div class="title"><h1>(.*?)<', detail_con, re.DOTALL))
        print('景点名称:'+self.title)

        #self.rank = ''.join(re.findall(r'rankText">(.*?)<', detail_con, re.DOTALL))

        self.address = ''.join(re.findall(r'地址</p><p class="baseInfoText">(.*?)<', detail_con, re.DOTALL))
        self.mobile = ''.join(re.findall(r'官方电话</p><p class="baseInfoText">(.*?)<', detail_con, re.DOTALL))
        self.quality_grade= ''.join(re.findall(r'<div class="titleTips"><span>(.*?)<!--', detail_con, re.DOTALL))
        #self.openTime = ''.join(re.findall(r'开放时间</div><div class="moduleContent">(.*?)<', detail_con, re.DOTALL))

        first_three_characters = self.address[:3]
        print('所在省份城市:'+'广东省'+first_three_characters)

        print('详细地址:'+self.address)
        #print('开放时间:'+self.openTime)
        print('电话:'+self.mobile)
        print('等级:'+self.quality_grade)
        if self.quality_grade=='':
            self.quality_grade=0

        '''使用xpath获取信息'''
        ret = etree.HTML(detail_con)
        desc_cons = ret.xpath('//div[@class="detailModule normalModule"]//div[@class="moduleContent"]')
        desc_titles = ret.xpath('//div[@class="detailModule normalModule"]//div[@class="moduleTitle"]')
        desc_list = []
        desc_title_list = []
        for d in desc_cons:
            des = ''.join(d.xpath('.//text()'))
            desc_list.append(des)
        for d in desc_titles:
            des = ''.join(d.xpath('.//text()'))
            desc_title_list.append(des)
        desc_dict = dict(zip(desc_title_list, desc_list))
        #print(desc_dict)

        first_value = list(desc_dict.values())[:2]  # 获取前两个值
        if len(first_value) >= 1:
            introduction = first_value[0]
        else:
            introduction = ''

        if len(first_value) >= 2:
            opening_hours = first_value[1]
        else:
            opening_hours = ''

        print('介绍:'+introduction)
        print('开放时间:'+ opening_hours)

        '''获取图片链接'''
        img_list = []
        imgs = re.findall(r'background-image:url\((.*?)\)', detail_con, re.DOTALL)
        for img in imgs:
            '''匹配到的同一张图片会有两种尺寸,我们只要大图,所以把尺寸为521*391的匹配出来即可'''
            image = re.search(r'521_391', img)
            if image:
                img_list.append(img)
        print(",".join(img_list))

        conn = pymysql.connect(host='localhost', user='root', password='root',
                               database='travel_ams', charset='utf8mb4')
        cursor = conn.cursor()

        sql = "INSERT INTO ams_attraction (attraction_name, quality_grade, province_city, location, open_hour, phone, introduction, images, add_time) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)"
        values = (self.title, self.quality_grade, '广东省'+first_three_characters, self.address, opening_hours, self.mobile, introduction,",".join(img_list)  ,datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
        cursor.execute(sql, values)

        conn.commit()
        conn.close()

        #self.get_ticket()

    #获取门票
    def get_ticket(self):
        id = self.detail_url.split('/')[-1]
        print(id)
        ticket_url = f'https://piao.ctrip.com/ticket/dest/{id}?onlyContent=true&onlyShelf=true'
        ticket_res = requests.get(ticket_url, verify=False, headers=self.headers).text
        # time.sleep(1)
        ticket_ret = etree.HTML(ticket_res)
        ticket = ticket_ret.xpath('//table[@class="ticket-table"]//div[@class="ttd-fs-18"]/text()')
        price = ticket_ret.xpath(
            '//table[@class="ticket-table"]//td[@class="td-price"]//strong[@class="ttd-fs-24"]/text()')
        print(ticket)
        print(price)
        '''拿到的列表里可能存在不确定数量的空值,所以这里用while True把空值全部删除,这样才可以确保门票种类与价格正确对应上'''
        while True:
            try:
                ticket.remove(' ')
            except:
                break
        while True:
            try:
                price.remove(' ')
            except:
                break
        '''
            这里多一个if判断是因为我发现有些详情页即便拿到门票信息并剔除掉空值之后仍然存在无法对应的问题,原因是网页规则有变动,
            所以一旦出现这种情况需要使用新的匹配规则,否则会数据会出错(不会报错,但信息对应会错误)
        '''
        if len(ticket) != len(price):
            ticket = ticket_ret.xpath(
                '//table[@class="ticket-table"]/tbody[@class="tkt-bg-gray"]//a[@class="ticket-title "]/text()')
            price = ticket_ret.xpath('//table[@class="ticket-table"]//strong[@class="ttd-fs-24"]/text()')
            while True:
                try:
                    ticket.remove(' ')
                except:
                    break
            while True:
                try:
                    price.remove(' ')
                except:
                    break
            print(ticket)
            print(price)
        ticket_dict = dict(zip(ticket, price))
        print(ticket_dict)

if __name__ == '__main__':

    jy_jd = Jy_jd()
    jy_jd.get_url()


3.6效果图

image-20240324223358077
在这里插入图片描述

  • 65
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
【资源说明】 基于python实现爬取携程景点数据与评论数据源码+项目说明.zip 基于python实现爬取携程景点数据与评论数据源码+项目说明.zip 基于python实现爬取携程景点数据与评论数据源码+项目说明.zip 1、该资源内项目代码都是经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载使用,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能。 爬取结果有两部分:`data/poi.csv`为**景点数据**,`data/comment/{id}.csv`为对应ID的景点的**评论数据** 评论内容的爬取有两种方法: 将`config.ini`的`isCrawlComment`置为1,运行`poi_crawl.py`文件,在爬取 景点数据 的过程爬取 评论数据 将`config.ini`的`isCrawlComment`置为0,运行`poi_crawl.py`文件,在爬取 景点数据 结束后运行再运行`comment_crawl.py`文件,获取 景点数据 的所有景点的评论 每次运行前都会在同一文件夹下复制一份上一次爬取景点结果的备份,名为`back.csv` 数据 **价格**、**最低价格**为response数据,暂无参考价值 后面四种人群门票价格为**预估的销量加权平均价格**,如果有不同需求可以修改 `GetTicketPrice` 函数。(返回的数据为所有的门票价格) 景点数据的**开放时间**与**优惠政策** 数据的格式为json格式 爬取的 评论数据 格式为: **用户ID** **评论文本** **发送时间戳** **赞同数**

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

闲来踢桃

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

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

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

打赏作者

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

抵扣说明:

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

余额充值