1. 项目概述与核心价值

最近在做一个数据分析项目,需要大量演唱会、话剧、展览的门票销售数据,第一反应就是去大麦网找。但手动一个个页面去翻、去复制粘贴,效率实在太低,而且数据格式也不统一,后续处理起来非常麻烦。于是,一个基于 Selenium 和 Pandas 的自动化爬虫项目就提上了日程。这个项目的核心目标很明确: 自动化、结构化地获取大麦网上的公开演出信息 ,比如演出名称、城市、场馆、时间、票价等,并将这些数据清洗整理成规整的表格,方便后续进行市场分析、价格监控或者个人兴趣追踪。

为什么选择 Selenium 和 Pandas 这对组合?这背后有很实际的考量。大麦网这类现代电商网站,大量内容是通过 JavaScript 动态加载的,直接用传统的 requests 库去请求网页,拿回来的很可能是一个没有数据的空壳。Selenium 的优势就在于它能模拟一个真实的浏览器,等页面完全加载、JS 执行完毕后再去抓取,数据“所见即所得”。而 Pandas 则是 Python 数据分析的“瑞士军刀”,它强大的 DataFrame 结构,天生就是为了处理表格数据而生,从数据清洗、转换到最终导出为 Excel 或 CSV,一条龙服务,极其高效。

这个项目适合谁呢?如果你是对 Python 有一定了解,想深入实战网络爬虫和数据分析的开发者;或者是市场、运营人员,需要定期采集竞品或行业数据来做报告;甚至是普通的演出爱好者,想做个自己的“演出日历”或“票价追踪器”,这个项目都能提供一个清晰的实现路径和可复用的代码框架。接下来,我就把自己从零搭建这个爬虫,到最终产出规整数据集的完整过程、踩过的坑以及一些优化心得,详细地分享出来。

2. 技术选型与核心思路拆解

2.1 为什么是 Selenium 而不是 Requests/Scrapy?

在决定技术栈时,首要问题是:如何应对动态加载。我用浏览器开发者工具(F12)观察了大麦网的列表页和详情页。当翻页或点击“加载更多”时,并没有看到典型的 XHR 或 Fetch 请求返回 JSON 数据,页面内容的变化更像是前端框架(如 React, Vue)驱动的组件更新。这意味着,单纯分析网络请求来构造 API 接口调用,难度很大,且接口可能不稳定。

Selenium 的方案是“以不变应万变”:我不关心网站用了什么技术,我只需要模拟人的操作,打开浏览器,等待页面渲染完成,然后像人眼一样去页面上找需要的信息(通过 CSS 选择器或 XPath)。这种方法虽然比直接调用 API 慢(需要启动浏览器、渲染页面),但 成功率极高,几乎能应对所有前端技术栈 ,对于目标明确、数据量不是海量级的项目来说,是性价比最高的选择。

当然,Scrapy 配合 Splash 也是一个选项,但 Splash 的维护和部署复杂度相对较高。而纯 Requests 方案,可能需要逆向解析复杂的 JS 逻辑,时间成本巨大。因此,对于大麦网这个具体目标,Selenium 的“笨办法”反而是最直接、最可靠的。

2.2 Pandas 在数据流程中的角色定位

Pandas 在这个项目里扮演的是“数据管家”和“清洁工”的角色。爬虫脚本抓取到的原始数据往往是杂乱无章的:日期可能是“2023年10月1日 周日 19:30”这样的字符串,票价可能是“380-1280元”这样的区间,城市和场馆信息可能混在一起。

Pandas 的 DataFrame 可以将这些数据先收纳进来,然后利用其丰富的字符串处理( .str 访问器)、时间序列处理( pd.to_datetime )和向量化运算功能,高效地进行清洗和转换。例如,将杂乱的日期字符串转换为标准的 datetime 类型,方便后续按月份、星期进行分析;将票价区间拆分成最低价和最高价两列。最后,一键导出为 data.csv data.xlsx ,无缝对接 Excel、BI 工具或进一步的 Python 分析脚本。

这个流程可以概括为: Selenium 负责“拿进来”(获取原始HTML和数据),Pandas 负责“理清楚”(清洗、结构化数据)

2.3 项目整体架构设计

基于以上思路,我设计了如下工作流:

  1. 导航与列表爬取 :使用 Selenium 控制浏览器打开大麦网城市站点的演出列表页,通过模拟点击“下一页”或滚动,遍历多个列表页,获取所有演出的基本链接和概要信息。
  2. 详情页数据提取 :对于列表页获取到的每一个演出链接,用 Selenium 打开其详情页,提取更全面的信息,如详细时间、票价档位、演出介绍等。
  3. 数据暂存 :每爬取完一个演出的详情,立即将数据整理成一个字典(dict),添加到一个列表(list)中。这样做的好处是内存占用小,且即使中途出错,已爬取的数据也不会丢失(可以定期保存这个列表)。
  4. 数据清洗与存储 :所有页面爬取完毕后,将存储字典的列表直接转换为 Pandas DataFrame。接着,在 DataFrame 上进行一系列的数据清洗操作。清洗完成后,将最终结果保存到文件。
  5. 反爬虫策略与伦理考量 :必须设计合理的延迟(如 time.sleep(random.uniform(1, 3)) ),避免请求过快对服务器造成压力。严格遵守 robots.txt (虽然大麦网可能没有严格限制,但这是基本礼仪)。 绝对不尝试爬取用户隐私数据、不进行抢票等干扰正常业务的操作 ,本项目仅用于学习与研究公开信息。

3. 环境搭建与核心工具详解

3.1 Selenium 环境配置的坑与技巧

首先,你需要安装 Python 的 Selenium 库: pip install selenium 。但这只是第一步,最关键的是 浏览器驱动

驱动选择与下载 :Selenium 需要通过一个驱动(如 ChromeDriver)来操控浏览器。驱动版本必须与你的 Chrome 浏览器版本严格匹配。去 Chrome 的“关于”页面查看版本号,然后到 ChromeDriver官网 下载对应版本。将下载的 chromedriver.exe (Windows) 或 chromedriver (Mac/Linux) 放在一个固定路径,或者直接放到 Python 脚本目录下。

注意:网络上有些教程让你下载一个所谓的“万能驱动”或指定旧版本,这极可能导致 session not created cannot find Chrome binary 等错误。版本匹配是第一步,也是最容易踩的坑。

启动浏览器的参数化配置 :直接启动 Selenium 控制的浏览器,可能会遇到检测或看到烦人的“Chrome正受到自动测试软件控制”的提示。我们可以通过添加选项(Options)来优化:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time
import random

chrome_options = Options()
# 两个常用选项,提升稳定性和隐蔽性
chrome_options.add_argument('--disable-blink-features=AutomationControlled') # 禁用自动化控制标志
chrome_options.add_argument('--disable-gpu') # 某些环境下禁用GPU加速可避免崩溃
# 无头模式(Headless),不显示浏览器界面,节省资源,适合服务器运行
# chrome_options.add_argument('--headless')

# 初始化驱动,指定驱动路径
driver = webdriver.Chrome(executable_path='./chromedriver', options=chrome_options)

关于无头模式 --headless 参数可以让浏览器在后台运行,不弹出窗口。在调试阶段建议关闭它,这样你能直观地看到浏览器操作到哪一步,是否成功加载页面。等脚本稳定后,再开启无头模式用于定时任务。

3.2 Pandas 及辅助库安装

Pandas 的安装通常很简单: pip install pandas 。但如果你需要将数据导出为 Excel 文件( .xlsx 格式),还需要安装 openpyxl 引擎: pip install openpyxl

此外,为了进行更复杂的数据解析(比如从 HTML 中直接提取数据),我们可能还会用到 lxml BeautifulSoup4 。虽然 Selenium 可以定位元素,但有时用 BeautifulSoup 来解析一小段 HTML 会更方便。

pip install pandas openpyxl lxml beautifulsoup4

3.3 项目目录结构规划

一个清晰的项目结构能让代码更易维护。我建议这样组织:

damai_crawler/
├── chromedriver          # Chrome浏览器驱动(注意版本匹配)
├── config.py            # 配置文件,如请求头、基础URL、等待时间等
├── crawler.py           # 主爬虫脚本,包含核心爬取逻辑
├── data_processor.py    # 数据清洗处理脚本,使用Pandas
├── utils.py             # 工具函数,如随机延迟、日志记录
├── logs/                # 存放运行日志
│   └── crawl_20231027.log
└── output/              # 存放输出数据
    ├── raw_data_20231027.json  # 原始数据备份
    └── cleaned_concerts_20231027.xlsx  # 最终清洗后的数据

将配置、爬取、处理、工具分离,符合单一职责原则,后续要修改爬取规则或清洗逻辑时,互不影响。

4. 核心爬取策略与页面解析实战

4.1 列表页遍历与链接抓取

大麦网的演出列表页通常有分页或“加载更多”按钮。我们的目标是获取所有页面上每一个演出项的详情页链接(href)。

首先,分析列表页结构。打开大麦网某个城市(如北京)的演出列表,按 F12 检查元素。你会发现每个演出项通常包裹在一个类似 <div class="show-item"> 的容器里,里面的 <a> 标签的 href 属性就是详情页链接。

策略步骤

  1. 访问列表页起始URL(例如: https://www.damai.cn/bj/ )。
  2. 使用 driver.find_elements_by_css_selector(‘选择器’) 定位到所有演出项。
  3. 从每个演出项中提取 href 。注意,链接可能是相对路径,需要拼接上基础域名。
  4. 寻找“下一页”按钮,并模拟点击。直到没有下一页或达到预设的爬取页数上限。

关键代码示例与解析

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

base_url = "https://www.damai.cn"
list_url = f"{base_url}/bj/" # 以北京站为例

driver.get(list_url)
# 显式等待,确保列表内容已加载
wait = WebDriverWait(driver, 10)

show_links = []
page_num = 1
max_pages = 5 # 控制爬取页数,避免过多

while page_num <= max_pages:
    print(f"正在爬取第 {page_num} 页...")
    
    # 等待演出列表容器出现
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, “.show-list-container”))) # 此处需替换为实际选择器
    
    # 定位当前页所有演出项
    show_items = driver.find_elements(By.CSS_SELECTOR, “.show-item a.show-link”) # 选择器需根据实际页面调整
    
    for item in show_items:
        href = item.get_attribute(‘href’)
        if href and ‘/project/’ in href: # 过滤出详情页链接
            full_url = href if href.startswith(‘http’) else base_url + href
            show_links.append(full_url)
            print(f"找到演出链接: {full_url}")
    
    # 尝试查找并点击“下一页”按钮
    try:
        next_button = driver.find_element(By.CSS_SELECTOR, “.next-page”) # 选择器需调整
        # 检查按钮是否可点击(是否禁用)
        if ‘disabled’ not in next_button.get_attribute(‘class’):
            next_button.click()
            time.sleep(random.uniform(2, 4)) # 翻页后等待页面加载
            page_num += 1
        else:
            print(“已是最后一页。”)
            break
    except Exception as e:
        print(f“未找到下一页按钮或点击失败: {e}”)
        break

print(f“共收集到 {len(show_links)} 个演出链接。”)

实操心得 :页面元素的选择器(CSS Selector 或 XPath)是爬虫的“眼睛”,必须写准。大麦网的页面结构可能会改版,所以这个选择器不能写死。最好准备2-3个备选选择器,并在代码中加入 try...except ,当一个失败时尝试另一个。使用 WebDriverWait 进行显式等待比固定的 time.sleep 更高效、更稳定。

4.2 详情页关键信息提取

拿到详情页链接后,就需要深入每个页面抓取具体信息。详情页的信息更丰富,但结构也可能更复杂。

信息定位策略

  1. 演出标题 :通常在 <h1> 标签里。
  2. 时间 :可能在 class 包含 time date span div 里。
  3. 场馆 :查找 venue place 相关的元素。
  4. 票价 :这是难点。票价可能是一个列表,每个票价档位是一个独立的区块。需要定位到票价区域的父元素,然后遍历其子元素,提取价格和票档描述。

代码示例

def parse_detail_page(driver, url):
    """解析单个详情页,返回数据字典"""
    driver.get(url)
    data = {‘url’: url}
    
    # 使用显式等待确保关键元素加载
    wait = WebDriverWait(driver, 15)
    
    try:
        # 1. 提取标题
        title_elem = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, “h1.project-title”)))
        data[‘title’] = title_elem.text.strip()
        
        # 2. 提取时间 - 可能需要处理多个元素
        time_selector = “span.project-performance-time” # 示例选择器
        time_elem = driver.find_element(By.CSS_SELECTOR, time_selector)
        data[‘show_time_raw’] = time_elem.text.strip() # 先保存原始字符串
        
        # 3. 提取场馆
        venue_elem = driver.find_element(By.CSS_SELECTOR, “div.project-venue span”)
        data[‘venue’] = venue_elem.text.strip()
        
        # 4. 提取城市 - 有时从URL或页面特定位置获取
        # 假设从URL中提取:https://www.damai.cn/project/12345.html
        # 或者页面有城市元素
        city_elem = driver.find_element(By.CSS_SELECTOR, “.city-name”)
        data[‘city’] = city_elem.text.strip() if city_elem else “未知”
        
        # 5. 提取票价(复杂情况)
        price_list = []
        price_items = driver.find_elements(By.CSS_SELECTOR, “.price-item”) # 示例,定位每个票价档位
        for item in price_items:
            try:
                price_desc = item.find_element(By.CSS_SELECTOR, “.desc”).text # 票档描述,如“看台380元”
                price_value = item.find_element(By.CSS_SELECTOR, “.value”).text # 价格数字
                price_list.append(f“{price_desc}:{price_value}”)
            except:
                continue
        data[‘price_info_raw’] = ‘; ‘.join(price_list) # 用分号连接多个票价
        
        # 6. 其他可能的信息:演出状态(预售/售票中/已售罄)、艺人等
        status_elem = driver.find_element(By.CSS_SELECTOR, “.project-status”)
        data[‘status’] = status_elem.text.strip() if status_elem else “未知”
        
    except Exception as e:
        print(f“解析页面 {url} 时出错: {e}”)
        data[‘error’] = str(e)
    
    # 随机延迟,模拟人类操作,减轻服务器压力
    time.sleep(random.uniform(1.5, 3.5))
    return data

注意事项 :详情页的字段可能因演出类型(演唱会、话剧、展览)而异,有些信息可能缺失。你的解析代码必须有足够的容错性(大量的 try...except )。将原始文本先保存下来(如 show_time_raw ),后续再用 Pandas 做统一清洗,比在爬取时做复杂的字符串解析更稳妥。

4.3 应对页面动态加载与反爬机制

现代网站会有意无意地设置一些障碍。除了之前提到的添加浏览器选项,还有几个常见问题:

  1. 元素加载过慢 :使用 WebDriverWait 配合 expected_conditions (如 presence_of_element_located , element_to_be_clickable )是标准做法。避免使用固定的、过长的 time.sleep
  2. iframe 嵌套 :有些信息(如选座)可能嵌在 iframe 里。你需要用 driver.switch_to.frame(frame_element) 切换到 iframe 内部才能操作,操作完再 driver.switch_to.default_content() 切回来。
  3. 智能检测 :虽然我们用了 --disable-blink-features=AutomationControlled ,但更高级的检测可能通过 WebDriver 属性、浏览器指纹等进行。一个更彻底的方法是使用 undetected-chromedriver 这样的第三方库,它能更好地隐藏自动化特征。但对于大麦网,常规的 Selenium 设置通常足够。
  4. IP 限制 :如果短时间内请求过多,可能会被暂时限制访问。解决方案是: 放慢速度 (增加随机延迟)、使用代理 IP 池(成本较高,对于学习项目通常不需要)。 务必遵守 robots.txt 和网站的服务条款,将请求频率控制在合理范围,这是合法合规爬取的前提。

5. 数据清洗与结构化处理实战

爬虫脚本跑完后,我们得到一个包含很多字典的列表 all_show_data 。现在是 Pandas 大显身手的时候了。

5.1 从原始数据到 DataFrame

第一步,将列表转换为 DataFrame,并立即保存一份原始数据作为备份。

import pandas as pd
import json

# 假设 all_show_data 是爬取得到的数据列表
df_raw = pd.DataFrame(all_show_data)

# 备份原始数据到JSON文件,防止后续清洗出错无法回溯
backup_file = f“./output/raw_data_{pd.Timestamp.now().strftime(‘%Y%m%d_%H%M%S’)}.json”
df_raw.to_json(backup_file, orient=‘records’, force_ascii=False, indent=2)
print(f“原始数据已备份至: {backup_file}”)

# 查看数据概览
print(df_raw.head())
print(df_raw.info())

df.info() 可以快速查看每列的数据类型和缺失值情况,这对制定清洗策略至关重要。

5.2 核心字段的清洗与转换

清洗工作通常针对每一列进行。

1. 时间字段处理 : 原始时间 show_time_raw 可能是 “2023.12.31 周日 19:30”。我们需要将其拆分为“日期”和“星期”两列,并将日期转换为 datetime 类型。

# 首先,确保列存在且非空
if ‘show_time_raw’ in df_raw.columns:
    # 使用正则表达式或字符串方法拆分
    # 示例:假设格式为“2023.12.31 周日 19:30”
    df_raw[‘show_date_str’] = df_raw[‘show_time_raw’].str.extract(r’(\d{4}\.\d{1,2}\.\d{1,2})’)
    df_raw[‘show_weekday’] = df_raw[‘show_time_raw’].str.extract(r’周([一二三四五六日])’)
    df_raw[‘show_time_str’] = df_raw[‘show_time_raw’].str.extract(r’(\d{1,2}:\d{2})’)
    
    # 将日期字符串转换为 datetime 类型
    df_raw[‘show_date’] = pd.to_datetime(df_raw[‘show_date_str’], format=‘%Y.%m.%d’, errors=‘coerce’)
    # 组合日期和时间(如果时间信息完整)
    # 这里简化处理,仅使用日期。更复杂的可以组合 datetime。

2. 票价字段处理 : 原始票价 price_info_raw 可能是 “看台380元; 内场880元; 套票1280元”。我们需要解析出最低价和最高价,或者将票价展开成多行。

def parse_price_range(price_str):
    """从票价字符串中解析出最低价和最高价"""
    if pd.isna(price_str):
        return None, None
    # 提取所有数字
    import re
    prices = re.findall(r’\d+’, price_str)
    if prices:
        prices_int = list(map(int, prices))
        return min(prices_int), max(prices_int)
    else:
        return None, None

# 应用函数,创建新列
df_raw[[‘price_min’, ‘price_max’]] = df_raw[‘price_info_raw’].apply(
    lambda x: pd.Series(parse_price_range(x))
)

3. 处理缺失值与异常值 : 检查关键字段(如标题、时间)的缺失情况,并决定是删除还是填充。

# 删除标题完全为空的行
df_cleaned = df_raw.dropna(subset=[‘title’])
# 对于城市缺失的,尝试从URL或场馆信息中推断(这里简化处理,填充为‘未知’)
df_cleaned[‘city’] = df_cleaned[‘city’].fillna(‘未知’)
# 检查价格合理性,比如有异常高的价格(可能是错误数据)
df_cleaned = df_cleaned[(df_cleaned[‘price_max’] < 10000) | (df_cleaned[‘price_max’].isna())]

5.3 数据规整与导出

清洗完成后,我们可能只需要保留有用的列,并重命名它们以便理解。

# 选择最终要保留的列
final_columns = {
    ‘title’: ‘演出名称’,
    ‘city’: ‘城市’,
    ‘venue’: ‘场馆’,
    ‘show_date’: ‘演出日期’,
    ‘show_weekday’: ‘星期’,
    ‘price_min’: ‘最低票价(元)’,
    ‘price_max’: ‘最高票价(元)’,
    ‘status’: ‘销售状态’,
    ‘url’: ‘详情页链接’
}

df_final = df_cleaned[list(final_columns.keys())].copy()
df_final.rename(columns=final_columns, inplace=True)

# 按演出日期排序
df_final.sort_values(by=‘演出日期’, inplace=True, na_position=‘last’)

# 重置索引
df_final.reset_index(drop=True, inplace=True)

# 导出为Excel和CSV
output_excel = f“./output/cleaned_concerts_{pd.Timestamp.now().strftime(‘%Y%m%d’)}.xlsx”
output_csv = f“./output/cleaned_concerts_{pd.Timestamp.now().strftime(‘%Y%m%d’)}.csv”

df_final.to_excel(output_excel, index=False, engine=‘openpyxl’)
df_final.to_csv(output_csv, index=False, encoding=‘utf-8-sig’) # utf-8-sig 确保Excel打开中文不乱码

print(f“数据清洗完成!共处理 {len(df_final)} 条记录。”)
print(f“Excel文件已保存至: {output_excel}”)
print(f“CSV文件已保存至: {output_csv}”)

现在,你就得到了一个干净、结构化的数据集,可以直接用于分析了。

6. 常见问题排查与性能优化

6.1 爬虫运行中的典型错误与解决

  1. NoSuchElementException (找不到元素)

    • 原因 :页面尚未加载完成,或元素的选择器已过时/写错。
    • 解决
      • 增加等待 :使用 WebDriverWait 替代硬性 sleep
      • 优化选择器 :使用更稳定、唯一的属性(如 data-* 属性)。避免使用可能变化的类名或索引位置。
      • 多重选择器备用 :在 try...except 块中尝试多个可能的选择器。
      try:
          elem = driver.find_element(By.CSS_SELECTOR, “selector_a”)
      except NoSuchElementException:
          try:
              elem = driver.find_element(By.XPATH, “//div[@class=‘fallback-class’]”)
          except NoSuchElementException:
              print(“元素未找到,跳过此项”)
              elem = None
      
  2. ElementNotInteractableException (元素不可交互)

    • 原因 :元素被遮挡、不在可视区域或处于禁用状态。
    • 解决
      • 滚动到元素位置 driver.execute_script(“arguments[0].scrollIntoView(true);”, element)
      • 等待元素可点击 WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, ‘button.next-page’)))
  3. 浏览器崩溃或驱动断开 ( WebDriverException )

    • 原因 :页面资源消耗过大、运行时间过长或驱动与浏览器版本不匹配。
    • 解决
      • 添加浏览器选项 chrome_options.add_argument(‘--no-sandbox’) chrome_options.add_argument(‘--disable-dev-shm-usage’) 对 Linux 服务器环境尤其有用。
      • 实现断点续爬 :将已成功爬取的链接和结果定期保存到文件。当脚本重启时,先加载这个文件,跳过已爬取的内容。这是生产级爬虫的必备功能。
      • 使用 driver.quit() :确保在脚本结束或异常退出时,调用 driver.quit() 来关闭浏览器和驱动进程,释放资源。

6.2 提升爬取效率与稳定性

  1. 减少不必要的页面加载 :在详情页爬取时,如果只需要部分信息,可以通过 driver.execute_script(“window.stop()”) 在主要内容加载后停止加载其他资源(如图片、广告),但这需要精确判断时机。

  2. 并发控制(高级) :对于大量详情页,单线程顺序爬取很慢。可以考虑使用线程池( concurrent.futures.ThreadPoolExecutor ),但 必须严格控制并发数 (建议2-3个线程),并为每个线程创建独立的 WebDriver 实例,避免状态冲突。同时,要确保目标服务器能承受这样的压力。

  3. 使用更轻量的 Headless 模式 :在无头模式下,可以禁用图片加载来加速。

    chrome_options.add_argument(‘--headless’)
    chrome_options.add_argument(‘--blink-settings=imagesEnabled=false’) # 禁用图片
    prefs = {“profile.managed_default_content_settings.images”: 2}
    chrome_options.add_experimental_option(“prefs”, prefs)
    
  4. 日志记录 :使用 Python 的 logging 模块记录运行日志,包括信息、警告和错误,方便后期排查问题。

    import logging
    logging.basicConfig(level=logging.INFO,
                        format=‘%(asctime)s - %(levelname)s - %(message)s’,
                        handlers=[logging.FileHandler(‘./logs/crawl.log’), logging.StreamHandler()])
    

6.3 数据清洗的边界情况处理

  1. 日期格式混乱 :原始数据中日期可能有“2023/12/31”、“12-31-2023”、“2023年12月31日”等多种格式。 pd.to_datetime() errors=‘coerce’ 参数会将解析失败的转为 NaT (Not a Time)。对于这些异常数据,可以单独提取出来人工核查,或者尝试多种解析格式。
  2. 票价文本复杂 :票价可能包含“早鸟票280元(售罄)”、“VIP 888元起”等。正则表达式 r’(\d+)\s*元’ 可能只匹配到数字。需要根据实际情况调整正则模式,或者接受部分信息丢失,优先保证数据的准确性而非完整性。
  3. 中文编码问题 :确保在读取、保存文件时指定正确的编码(如 utf-8-sig 对于 Windows Excel 兼容性更好)。

7. 项目扩展与进阶思考

一个基础的爬虫完成后,可以考虑从“能用”到“好用”、“耐用”的方向进行扩展。

1. 调度与自动化 :使用 schedule 库或操作系统的定时任务(如 Linux 的 crontab, Windows 的任务计划程序),让爬虫每天在凌晨低峰时段自动运行,更新数据。

2. 数据监控与可视化 :将爬取的数据存入数据库(如 SQLite, MySQL),然后利用 Pandas 的绘图功能或 matplotlib seaborn 库,生成图表。例如,监控某个艺人演唱会票价随时间的变化趋势,或统计不同城市每月演出数量的分布。

3. 构建简单的数据查询接口 :使用 Flask Streamlit 快速搭建一个本地 Web 应用,提供按城市、日期、价格范围筛选演出的功能,让不熟悉代码的同事也能使用这些数据。

4. 更健壮的架构 :将配置(URL、选择器)、爬取逻辑、数据清洗、存储完全模块化。引入任务队列(如 Redis),将 URL 发现、详情爬取、数据清洗等步骤解耦,提高系统的可维护性和扩展性。

最后,也是最重要的提醒 :爬虫技术是一把双刃剑。本项目所有讨论均基于 爬取公开、非隐私信息 ,且 严格遵守目标网站的 robots.txt 协议,并将请求频率控制在极低水平,模拟人类浏览速度 。在实际应用中,务必尊重网站的数据权益和服务条款,将技术用于正当的学习、研究和分析目的,避免对目标网站的正常运营造成任何干扰。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐