㊙️本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~持续更新中!
㊗️爬虫难度指数:⭐⭐⭐
🚫声明:本数据&代码仅供学习交流,严禁用于商业用途、倒卖数据或违反目标站点的服务条款等,一切后果皆由使用者本人承担。公开榜单数据一般允许访问,但请务必遵守"君子协议",技术无罪,责任在人。

全文目录:
-
-
- [🌟 开篇语](#🌟 开篇语)
- [1️⃣ 摘要(Abstract)](#1️⃣ 摘要(Abstract))
- [2️⃣ 背景与需求(Why)](#2️⃣ 背景与需求(Why))
- [3️⃣ 合规与注意事项(必写)](#3️⃣ 合规与注意事项(必写))
- [4️⃣ 技术选型与整体流程(What/How)](#4️⃣ 技术选型与整体流程(What/How))
- [5️⃣ 环境准备与依赖安装(可复现)](#5️⃣ 环境准备与依赖安装(可复现))
- [6️⃣ 核心实现:请求层(Fetcher)](#6️⃣ 核心实现:请求层(Fetcher))
- [7️⃣ 核心实现:解析层(Parser)------ 正则与启发式提取 🕵️](#7️⃣ 核心实现:解析层(Parser)—— 正则与启发式提取 🕵️)
- [8️⃣ 数据存储与导出(Storage)](#8️⃣ 数据存储与导出(Storage))
- [9️⃣ 运行方式与结果展示(必写)](#9️⃣ 运行方式与结果展示(必写))
- [🔟 常见问题与排错(必看经验)](#🔟 常见问题与排错(必看经验))
- [1️⃣1️⃣ 进阶优化(可选但加分)](#1️⃣1️⃣ 进阶优化(可选但加分))
- [1️⃣2️⃣ 总结与延伸阅读](#1️⃣2️⃣ 总结与延伸阅读)
- [🌟 文末](#🌟 文末)
-
- [📌 专栏持续更新中|建议收藏 + 订阅](#📌 专栏持续更新中|建议收藏 + 订阅)
- [✅ 互动征集](#✅ 互动征集)
-
🌟 开篇语
哈喽,各位小伙伴们你们好呀~我是【喵手】。
运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO
欢迎大家常来逛逛,一起学习,一起进步~🌟
我长期专注 Python 爬虫工程化实战 ,主理专栏 《Python爬虫实战》:从采集策略 到反爬对抗 ,从数据清洗 到分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上。
📌 专栏食用指南(建议收藏)
- ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
- ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
- ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
- ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用
📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅/关注专栏👉《Python爬虫实战》👈
💕订阅后更新会优先推送,按目录学习更高效💯~
1️⃣ 摘要(Abstract)
项目代号: Art_Radar
核心目标: 遍历目标博物馆官网的"展览预告/正在热展"栏目,抓取展览名称、精确展期、具体展厅位置以及票务/预约规则。
工具栈: Requests (翻页请求) + BeautifulSoup (DOM 解析) + Re (正则表达式提取非结构化信息)。
难点攻克:
- 非结构化文本挖掘 :门票价格通常写在"一段话"里(如"成人票60元,学生半价"),没有专门的
class="price"。我们需要写正则去匹配它。 - 深度遍历 :必须采用 List-to-Detail (列表-详情)模式,因为列表页信息严重不足。
最终产出: 一份museum_exhibitions.csv,让你一眼看清周末去哪看展。
💡 读完这篇你将获得:
- 正则实战能力:学会用正则表达式从长文本中提取"价格(数字+元)"和"日期范围"。
- 二级爬虫架构:掌握"先抓列表链接,再逐个请求详情"的标准爬虫设计模式。
- 文本清洗策略 :如何去除 HTML 中的
、多余空行,还原干净的介绍文本。
2️⃣ 背景与需求(Why)
-
为什么要爬: 很多好展览展期短,容易错过。官方公众号推送不及时,而官网通常是最全的。做一个聚合器,每周五自动发给自己,规划周末行程。
-
目标站点: 以一个典型的公立博物馆结构为例(通常有
/exhibitions/current或/news/exhibition页面)。 -
目标字段清单:
Exhibition_Title(展览名,如 "只此青绿------宋代山水画特展")Date_Range(展期,如 "2023.10.1 - 2024.1.15")Location(地点,如 "三楼书画馆")Ticket_Info(票务,如 "免费预约" 或 "¥60")Status(状态,正在热展/即将开幕 - 可选)
3️⃣ 合规与注意事项(必写)
文化机构的服务器通常预算有限,不仅要慢 ,还要轻。
- 深层抓取的风险: 我们采用"列表->详情"模式,请求量是
1(列表页) + N(详情页)。如果列表页有 20 个展,就要发 21 次请求。必须 在请求详情页时加入time.sleep(2),否则极易被封 IP 或导致对方服务器卡顿。 - 尊重版权: 展览介绍文案和海报图片版权归主办方所有。爬取内容仅供个人浏览索引,严禁直接复制图文搭建"山寨官网"。
- 预约通道: 我们的爬虫只读取 预约规则,绝不尝试自动化抢票或刷预约号,这是严重的违规行为。
4️⃣ 技术选型与整体流程(What/How)
技术痛点分析:
在详情页中,Html 可能是这样的:
html
<div class="content">
<p>本次展览将于10月1日在三号厅开幕。</p>
<p>票价:全价票60元,老人免票。</p>
</div>
这里没有 <span id="price">。
解决方案:
我们提取整个 .content 的纯文本,然后用 Python 的 if "票" in text 或正则表达式 \d+元 来"猜测"票价。
🛠️ 流程图:
Fetch 列表页 → Parse 展览链接与状态 → Loop 遍历链接 → Fetch 详情页 → 提取正文 → Regex 匹配日期/地点/票价 → Save CSV
5️⃣ 环境准备与依赖安装(可复现)
Python 版本: 3.9+
依赖安装:
bash
pip install requests beautifulsoup4 pandas
目录结构:
text
art_radar/
├── radar.py # 主程序
├── keywords_conf.py # (可选) 存放提取规则的关键词配置
└── results/ # 结果存放
6️⃣ 核心实现:请求层(Fetcher)
为了支持"列表"和"详情"两种请求,我们封装一个通用的 Fetcher,并加上重试机制,因为详情页有时候链接会坏死(404)。
python
import requests
import time
import random
# 全局 Session 对象,复用 TCP 连接,提高效率
session = requests.Session()
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Referer': 'https://www.google.com/'
})
def fetch_content(url):
"""
通用抓取函数:支持重试和自动编码处理
"""
if not url.startswith('http'):
print(f"⚠️ Invalid URL: {url}")
return None
for attempt in range(3):
try:
# 详情页请求必须有随机延时!
# 放在请求前,确保每次调用都会暂停
time.sleep(random.uniform(1.5, 3.0))
response = session.get(url, timeout=10)
response.raise_for_status()
# 处理中文乱码:如果 meta charset 是 gbk,requests 可能会猜错
# 这里用 apparent_encoding 比较稳妥
if response.encoding == 'ISO-8859-1':
response.encoding = response.apparent_encoding
return response.text
except requests.RequestException as e:
print(f"⚠️ Fetch Error (Try {attempt+1}): {url} - {e}")
return None
7️⃣ 核心实现:解析层(Parser)------ 正则与启发式提取 🕵️
这是最考验逻辑的地方。我们将编写两个解析器:一个处理列表,一个处理详情文本。
Helper Function: 文本嗅探器
python
import re
def heuristic_extract(text_block):
"""
从一大段文本中,利用关键词和正则 '猜测' 关键信息
"""
info = {
'price': '详情见公告',
'location': '馆内'
}
# 1. 提取票价 (Heuristic: 寻找包含 "元" 或 "票" 的句子)
# 匹配模式:数字 + "元" (e.g., "60元", "20元")
# 或者关键词 "免费", "Free"
if "免费" in text_block or "Free" in text_block:
info['price'] = "免费/Free"
else:
# 查找类似 "票价:30元" 或者 "60元/人"
prices = re.findall(r'(\d+)\s*元', text_block)
if prices:
# 取最大值通常是全价票,或者列出所有
info['price'] = f"{'/'.join(prices)} 元"
# 2. 提取地点 (Heuristic: 寻找 "厅", "馆", "层")
# 这比较难,通常寻找短句。此处仅做简单示例。
# 实际项目中可能需要定位到 "地点:" 关键词后面的文本
loc_match = re.search(r'地点[::]\s*([^\n,。]+)', text_block)
if loc_match:
info['location'] = loc_match.group(1).strip()
elif "三层" in text_block:
info['location'] = "三层展厅" # 示例硬编码规则
return info
主解析逻辑:
python
from bs4 import BeautifulSoup
from urllib.parse import urljoin
BASE_DOMAIN = "https://www.example-museum.com" # 假设的域名
def parse_exhibition_list(html):
"""
解析展览列表页,返回展览的基本信息和详情页链接
"""
soup = BeautifulSoup(html, 'html.parser')
exhibitions = []
# 假设列表结构:ul.exhibit-list > li > a (包含标题) + span (包含时间)
items = soup.find_all('div', class_='exhibition-item') # 需根据实际网站调整
for item in items:
try:
# 1. 提取标题和链接
title_tag = item.find('h3').find('a')
title = title_tag.text.strip()
link = urljoin(BASE_DOMAIN, title_tag['href'])
# 2. 提取列表页展示的日期 (如果有)
date_tag = item.find('span', class_='date')
date_str = date_tag.text.strip() if date_tag else "N/A"
exhibitions.append({
'title': title,
'url': link,
'list_date': date_str # 暂时存下来,详情页可能还有更详细的
})
except AttributeError:
continue
return exhibitions
def parse_detail_page(html, basic_info):
"""
进入详情页,挖掘更深度的信息
"""
soup = BeautifulSoup(html, 'html.parser')
# 1. 提取正文纯文本
# 通常在 div.article-content 或 div.description
content_div = soup.find('div', class_='article-content')
if not content_div:
return basic_info # 如果解析失败,至少返回列表页拿到的信息
# 获取纯文本,去除 HTML 标签
full_text = content_div.get_text(separator='\n', strip=True)
# 2. 调用启发式提取器
extra_info = heuristic_extract(full_text)
# 3. 合并数据
final_data = basic_info.copy()
final_data['location'] = extra_info['location']
final_data['ticket'] = extra_info['price']
# 4. (可选) 尝试在详情页找更精确的日期
# 有时候列表页写 "2023年",详情页写 "2023.10.1 - 10.7"
# 这里可以再加正则匹配日期的逻辑
return final_data
8️⃣ 数据存储与导出(Storage)
这次我们稍微高级一点,除了存 CSV,我们把包含"免费"字眼的展览标记出来(Highlighter),方便后续筛选。
python
import pandas as pd
import os
from datetime import datetime
def save_exhibitions(data_list):
if not data_list:
print("⚠️ No exhibitions found.")
return
df = pd.DataFrame(data_list)
# 数据清洗
df['title'] = df['title'].str.replace('\n', '')
# 增加一列:是否值得去(示例逻辑)
# 如果是免费的,或者标题里有 "特展" 两个字
df['Recommendation'] = df.apply(
lambda x: '⭐⭐⭐' if ('免费' in str(x['ticket']) or '特展' in x['title']) else '',
axis=1
)
# 调整列顺序
cols = ['title', 'list_date', 'location', 'ticket', 'Recommendation', 'url']
# 防止某些列不存在的报错
existing_cols = [c for c in cols if c in df.columns]
df = df[existing_cols]
# 文件名带时间
filename = f"exhibitions_{datetime.now().strftime('%Y%m%d')}.csv"
output_path = os.path.join("results", filename)
os.makedirs("results", exist_ok=True)
df.to_csv(output_path, index=False, encoding='utf-8-sig')
print(f"💾 Saved {len(df)} exhibitions to {output_path}")
print("\n🎫 Top Picks:")
print(df[['title', 'ticket', 'Recommendation']].head().to_markdown(index=False))
9️⃣ 运行方式与结果展示(必写)
这里我们模拟整个流程:先抓列表,再循环抓详情。
python
if __name__ == "__main__":
# 假设的目标 URL (需要替换为真实的博物馆展览列表页 URL)
# 例如:某美术馆的 "当前展览" 页面
target_list_url = "https://www.example-art-museum.org/exhibitions/current"
print("🎨 Art Radar Initiated...")
# Step 1: 获取列表
print(f"📡 Scanning List: {target_list_url}")
list_html = fetch_content(target_list_url)
if list_html:
exhibitions = parse_exhibition_list(list_html)
print(f"✅ Found {len(exhibitions)} exhibitions. Digging details...")
full_data = []
# Step 2: 深度遍历详情页
for i, expo in enumerate(exhibitions):
print(f" [{i+1}/{len(exhibitions)}] Analyzing: {expo['title']} ...")
detail_html = fetch_content(expo['url'])
if detail_html:
detailed_expo = parse_detail_page(detail_html, expo)
full_data.append(detailed_expo)
else:
# 如果详情页抓失败,至少保留基本信息
full_data.append(expo)
# Step 3: 保存
save_exhibitions(full_data)
else:
print("❌ Failed to fetch list page.")
📊 示例结果展示 (CSV):
| title | list_date | location | ticket | Recommendation |
|---|---|---|---|---|
| 达芬奇的手稿------文艺复兴特展 | 2023.10.01-12.31 | 二楼特展厅 | 120/80 元 | ⭐⭐⭐ |
| 馆藏明清瓷器常设展 | 常设展览 | 四楼瓷器馆 | 免费/Free | ⭐⭐⭐ |
| 现代水墨实验展 | 2023.11.15-11.20 | 一楼侧厅 | 详情见公告 |
🔟 常见问题与排错(必看经验)
-
正则提取不到价格?
- 原因: 网站没写"元",写的是"CNY 60"或者"票价:陆拾"。
- 解法: 正则表达式需要不断迭代。可以把没提取到的详情页文本保存下来,人工观察规律,优化 Regex。比如增加
r'CNY\s*(\d+)'的匹配。
-
详情页是 PDF?
- 现象: 点击展览链接,直接下载了一个
.pdf文件。 - 原因: 很多传统机构习惯把公告做成 PDF 上传。
- 解法: 这种
BeautifulSoup解析不了。需要检测Content-Type是否为application/pdf,如果是,需要用pdfplumber库来读取 PDF 文本,这属于进阶内容。
- 现象: 点击展览链接,直接下载了一个
-
日期格式千奇百怪?
- 现象: 有的写 "10.1-10.7",有的写 "Oct 1st to 7th",有的写 "每周一闭馆"。
- 解法: 不要试图把日期标准化成 Date 对象(太难了),直接作为字符串存下来。让人类自己去读,往往比写代码解析更高效。
1️⃣1️⃣ 进阶优化(可选但加分)
-
自然语言处理 (NLP) 介入:
- 使用
spacy或中文分词工具jieba,识别文本中的 Location (LOC) 和 Date (DATE) 实体,比正则匹配更智能,能识别出"故宫博物院午门展厅"这样的复杂地点。
- 使用
-
日历集成 (iCal):
- 使用
ics库,把抓取到的展览生成.ics日历文件。用户双击文件,就能把展览直接添加到手机日历里,这才是极致的用户体验!
- 使用
1️⃣2️⃣ 总结与延伸阅读
复盘:
本篇我们不仅是一个爬虫工程师,更是一个**"信息抽取工程师"。我们面对的不再是整齐的表格,而是杂乱的自然语言。通过 列表遍历和正则嗅探**,我们成功地将非结构化的文本转化为了决策数据。
下一步:
既然搞定了博物馆,为什么不试试**"Livehouse 演出信息"或者"电影节排片表"**?它们的逻辑是通用的。去发现城市里更多美好的角落吧!🌃
🌟 文末
好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
📌 专栏持续更新中|建议收藏 + 订阅
专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?
评论区留言告诉我你的需求,我会优先安排更新 ✅
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)
免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。