从携程爬取的杭州酒店数据中提取价格、评分与评论的关键信息

一、理解数据源:结构化的混乱

在开始编写提取代码之前,我们必须理解携程页面数据的构成。现代网页大量使用JavaScript动态渲染,数据通常以JSON格式直接嵌入在HTML中,而非简单的静态文本。

  1. 价格信息:通常不在静态HTML里,而是通过AJAX请求从后端API获取的一个JSON数据包。这个数据包结构复杂,包含原价、现价、折扣、房型、是否含早餐等大量信息。
  2. 评分与评论摘要 :如总评分、分项评分(位置、卫生、服务、设施)、评论总数等,常以嵌套的JSON对象形式存在于页面的 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);"><script></font> 标签内。
  3. 评论内容:与价格类似,评论详情也是通过单独的API接口异步加载的,返回标准的JSON格式,其中包含了用户昵称、评论内容、评分、入住时间、有用数等字段。

因此,我们的提取策略主要分为两种:

  • 方案A:解析内嵌JSON - 从初始的HTML页面中,通过正则表达式或HTML解析器找到包含酒店概要信息的JSON字符串。
  • 方案B:直接请求API接口 - 通过浏览器开发者工具(F12)找到获取价格和评论数据的真实API地址,用爬虫直接模拟这些请求。

本文将重点介绍方案A,因为它更通用,且在一次请求中能获取到除详细评论外的大部分关键信息。

二、技术栈与准备工作

我们将使用以下Python库:

  • <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">requests</font>:用于发送HTTP请求,获取网页内容。
  • <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">json</font>:用于解析JSON字符串。
  • <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">re</font>:正则表达式库,用于精准匹配和提取文本中的JSON块。
  • <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">pandas</font>:用于最终数据的整理和存储。
三、实战代码:三步提取关键信息

假设我们已经通过爬虫获取到了一个杭州酒店列表页或详情页的HTML内容,存储于变量 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">html_content</font> 中。

步骤一:定位并提取核心JSON数据块

经过对携程页面的分析,我们发现酒店的核心数据被包含在一个特定的 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);"><script></font> 标签中,其类型为 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">application/json</font>,并且有一个特定的ID。我们可以使用正则表达式来精准抓取它。

plain 复制代码
import re
import json
import pandas as pd
import requests
from typing import Dict, Any

# 代理配置
proxyHost = "www.16yun.cn"
proxyPort = "5445"
proxyUser = "16QMSOML"
proxyPass = "280651"

# 构建代理字典
proxies = {
    "http": f"http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}",
    "https": f"https://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}"
}

# 请求头配置
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
    "Accept-Encoding": "gzip, deflate, br",
    "Connection": "keep-alive",
    "Upgrade-Insecure-Requests": "1"
}

def get_html_with_proxy(url: str) -> str:
    """
    使用代理发送请求获取HTML内容
    
    参数:
        url: 目标URL
        
    返回:
        HTML内容字符串
    """
    try:
        response = requests.get(
            url,
            proxies=proxies,
            headers=headers,
            timeout=10,
            verify=False  # 注意:在生产环境中建议设置为True
        )
        response.raise_for_status()  # 如果状态码不是200,抛出异常
        return response.text
    except requests.exceptions.RequestException as e:
        print(f"请求失败: {e}")
        return ""

def extract_hotel_json(html_content: str) -> Dict[str, Any]:
    """
    从携程酒店HTML页面中提取包含酒店关键信息的JSON数据。
    
    参数:
        html_content: 网页的HTML内容字符串
        
    返回:
        解析后的Python字典,如果提取失败则返回空字典。
    """
    # 使用正则表达式匹配特定的JSON数据块
    # 此模式是示例,实际模式需根据携程页面结构调整
    pattern = r'<script id="json-lg" type="application/json">(.*?)</script>'
    match = re.search(pattern, html_content, re.DOTALL)
    
    if match:
        json_str = match.group(1)
        try:
            # 解析JSON字符串
            hotel_data = json.loads(json_str)
            return hotel_data
        except json.JSONDecodeError as e:
            print(f"JSON解析错误: {e}")
            return {}
    else:
        print("未找到匹配的JSON数据块。")
        return {}

# 示例使用 - 更新后的版本
def main_example():
    """
    使用代理获取网页内容的示例
    """
    # 示例URL - 请替换为实际的携程酒店URL
    example_url = "https://hotels.ctrip.com/hotels/detail/123456"
    
    # 使用代理获取HTML内容
    html_content = get_html_with_proxy(example_url)
    
    if html_content:
        # 提取JSON数据
        hotel_json = extract_hotel_json(html_content)
        
        if hotel_json:
            print("成功提取酒店JSON数据")
            # 这里可以继续处理提取到的数据
            print(f"提取到数据键: {list(hotel_json.keys())}")
        else:
            print("未能提取到有效的JSON数据")
    else:
        print("未能获取网页内容")

# 批量处理多个URL的函数
def batch_process_hotels(url_list: list, delay: float = 2) -> list:
    """
    批量处理多个酒店URL
    
    参数:
        url_list: URL列表
        delay: 请求延迟(秒),避免请求过快
        
    返回:
        提取到的酒店数据列表
    """
    import time
    all_hotel_data = []
    
    for i, url in enumerate(url_list):
        print(f"正在处理第 {i+1}/{len(url_list)} 个URL: {url}")
        
        html_content = get_html_with_proxy(url)
        if html_content:
            hotel_data = extract_hotel_json(html_content)
            if hotel_data:
                hotel_data['source_url'] = url  # 添加来源URL
                all_hotel_data.append(hotel_data)
        
        # 添加延迟,避免请求过于频繁
        if i < len(url_list) - 1:  # 最后一个不需要延迟
            time.sleep(delay)
    
    return all_hotel_data

if __name__ == "__main__":
    # 运行示例
    main_example()
    
    # 批量处理示例
    # hotel_urls = [
    #     "https://hotels.ctrip.com/hotels/detail/123456",
    #     "https://hotels.ctrip.com/hotels/detail/789012",
    #     # 添加更多URL...
    # ]
    # results = batch_process_hotels(hotel_urls)
    # print(f"成功处理 {len(results)} 个酒店数据")

步骤二:从JSON中解析价格、评分与评论摘要

成功提取到顶层JSON后,我们需要像剥洋葱一样,一层层地找到目标数据。这需要对JSON结构有清晰的了解。

plain 复制代码
def parse_key_info(hotel_data: Dict[str, Any]) -> Dict[str, Any]:
    """
    从酒店JSON数据中解析出价格、评分和评论摘要等关键信息。
    
    参数:
        hotel_data: 从extract_hotel_json函数得到的字典
        
    返回:
        包含提取出的关键信息的新字典。
    """
    key_info = {}
    
    try:
        # 1. 提取酒店基本信息
        key_info['酒店名称'] = hotel_data.get('pageConfig', {}).get('pageData', {}).get('baseInfo', {}).get('hotelName')
        key_info['酒店ID'] = hotel_data.get('pageConfig', {}).get('pageData', {}).get('baseInfo', {}).get('hotelId')
        
        # 2. 提取价格信息 - 路径非常复杂,需要仔细分析
        # 注意:价格信息可能因房型而异,这里取第一个可用房型的价格
        room_types = hotel_data.get('pageConfig', {}).get('pageData', {}).get('roomTypes', [])
        if room_types:
            first_room = room_types[0]
            key_info['房型'] = first_room.get('roomName')
            # 价格可能在priceInfo或avgPrice等字段中
            price_info = first_room.get('priceInfo', {})
            key_info['最低价格'] = price_info.get('avgPrice', price_info.get('price'))
            key_info['货币'] = price_info.get('currencyCode', 'CNY')
        else:
            key_info['房型'] = None
            key_info['最低价格'] = None
            key_info['货币'] = 'CNY'
        
        # 3. 提取评分信息
        review_summary = hotel_data.get('pageConfig', {}).get('pageData', {}).get('reviewSummary', {})
        key_info['综合评分'] = review_summary.get('score')
        key_info['点评数量'] = review_summary.get('count')
        # 细分评分
        sub_scores = review_summary.get('subScores', {})
        key_info['位置评分'] = sub_scores.get('position')
        key_info['设施评分'] = sub_scores.get('facility')
        key_info['服务评分'] = sub_scores.get('service')
        key_info['卫生评分'] = sub_scores.get('sanitation')
        
        # 4. 提取评论标签摘要(例如: "好评" "环境优雅" 等)
        keyword_summary = review_summary.get('keywordSummary', [])
        key_info['评论标签'] = ', '.join([kw.get('keyword') for kw in keyword_summary[:3]]) # 取前三个标签
        
    except Exception as e:
        print(f"解析关键信息时出错: {e}")
    
    return key_info

# 示例使用
# key_info = parse_key_info(hotel_json)
# print(key_info)

步骤三:整合与数据存储

现在,我们可以将上述功能整合到一个流程中,并处理多个酒店的数据,最后使用Pandas保存到CSV文件。

plain 复制代码
def main(hotel_urls):
    """
    主函数,处理多个酒店URL,提取信息并保存。
    """
    all_hotels_info = []
    
    for url in hotel_urls:
        print(f"正在处理: {url}")
        # 1. 获取页面HTML(此处需自行添加headers等反爬措施)
        # response = requests.get(url, headers=headers)
        # html_content = response.text
        
        # 为演示,我们假设html_content已获取
        html_content = "" # 这里应该是实际的HTML内容
        
        # 2. 提取JSON
        hotel_json = extract_hotel_json(html_content)
        if not hotel_json:
            continue
            
        # 3. 解析关键信息
        hotel_info = parse_key_info(hotel_json)
        hotel_info['数据来源URL'] = url # 记录来源
        all_hotels_info.append(hotel_info)
    
    # 4. 转换为DataFrame并保存
    df = pd.DataFrame(all_hotels_info)
    
    # 数据清洗:确保价格是数值型
    df['最低价格'] = pd.to_numeric(df['最低价格'], errors='coerce')
    
    # 保存到CSV文件
    output_filename = 'hangzhou_hotels_key_info.csv'
    df.to_csv(output_filename, index=False, encoding='utf-8-sig')
    print(f"数据已成功保存到 {output_filename}")
    print(f"共处理了 {len(df)} 家酒店的信息。")
    
    return df

# 假设的URL列表
# hotel_urls = [
#     'https://hotels.ctrip.com/hotels/detail/123456',
#     'https://hotels.ctrip.com/hotels/detail/789012',
#     # ... 更多杭州酒店详情页URL
# ]
# df_result = main(hotel_urls)
四、注意事项与进阶优化
  1. 反爬虫策略 :携程有严格的反爬机制。务必设置合理的请求头(User-Agent, Referer),使用代理IP池,并添加请求间隔(如 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">time.sleep</font>)。
  2. 代码健壮性 :网页结构可能随时变动。上述代码中的JSON路径是示例,实际应用中需要您使用浏览器开发者工具重新分析并调整路径。应添加大量的 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">try-except</font> 块来处理可能缺失的字段。
  3. 处理分页评论:若要获取全部评论,需要找到评论API,并构造分页参数进行循环请求。这通常涉及分析Network选项卡中的XHR请求。
  4. 法律与伦理:本文内容仅用于技术交流和学习。大规模爬取商业网站数据可能违反其服务条款,请务必控制爬取速度和数据量,尊重网站权益,并将数据用于合法合规的分析研究。
五、结语

通过本文介绍的技术流程,我们成功地将非结构化的网页HTML,转化为了包含价格、评分和评论摘要的结构化数据。这套"提取-解析-存储"的方法论,不仅适用于携程,经过调整后也可应用于其他OTA平台(如飞猪、Booking.com)的数据处理。掌握了这项数据提炼技术,您就拥有了将互联网上海量、杂乱的公开信息,转化为高质量、可操作商业洞察的金钥匙。

相关推荐
不剪发的Tony老师几秒前
PyScripter:一款免费开源、功能强大的Python开发工具
ide·python
FL171713145 小时前
Pytorch保存pt和pkl
人工智能·pytorch·python
爱学习的小道长7 小时前
进程、线程、协程三者的区别和联系
python·ubuntu
L-李俊漩7 小时前
MMN-MnnLlmChat 启动顺序解析
开发语言·python·mnn
云飞云共享云桌面8 小时前
如何降低非标自动化工厂的研发软件采购成本
运维·服务器·网络·数据库·性能优化·自动化
大雷神8 小时前
HarmonyOS 横竖屏切换与响应式布局实战指南
python·深度学习·harmonyos
钅日 勿 XiName8 小时前
一小时速通pytorch之训练分类器(四)(完结)
人工智能·pytorch·python
青瓷程序设计8 小时前
水果识别系统【最新版】Python+TensorFlow+Vue3+Django+人工智能+深度学习+卷积神经网络算法
人工智能·python·深度学习
*才华有限公司*9 小时前
基于BERT的文本分类模型训练全流程:从环境搭建到显存优化实战
python
超级战斗鸡9 小时前
React 性能优化教程:useMemo 和 useCallback 的正确使用方式
前端·react.js·性能优化