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

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

在开始编写提取代码之前,我们必须理解携程页面数据的构成。现代网页大量使用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)的数据处理。掌握了这项数据提炼技术,您就拥有了将互联网上海量、杂乱的公开信息,转化为高质量、可操作商业洞察的金钥匙。

相关推荐
笨鸟先飞,勤能补拙2 小时前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
长和信泰光伏储能2 小时前
京津冀光伏发电:绿色能源的未来之路
python·能源
浦信仿真大讲堂3 小时前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study3 小时前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境4 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境4 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd1234 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102165 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb005 小时前
一个项目带你入门AI应用开发01
python
0566465 小时前
Python康复训练——常用标准库
开发语言·python·学习