㊗️本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~
㊙️本期爬虫难度指数:⭐ (基础入门篇)
🉐福利: 一次订阅后,专栏内的所有文章可永久免费看,持续更新中,保底1000+(篇)硬核实战内容。

全文目录:
-
-
- [🌟 开篇语](#🌟 开篇语)
- [0️⃣ 前言(Preface)](#0️⃣ 前言(Preface))
- [1️⃣ 摘要(Abstract)](#1️⃣ 摘要(Abstract))
- [2️⃣ 背景与需求(Why)](#2️⃣ 背景与需求(Why))
- [3️⃣ 合规与注意事项(Mandatory Compliance)](#3️⃣ 合规与注意事项(Mandatory Compliance))
- [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爬虫实战》👈,一次订阅后,专栏内的所有文章可永久免费阅读,持续更新中。
💕订阅后更新会优先推送,按目录学习更高效💯~
0️⃣ 前言(Preface)
-
一句话说明: 本文将使用 Python 的
requests和BeautifulSoup4工具,从志愿服务公益网站抓取"岗位类别说明"规则文本,并将其转化为结构化的关系型数据,最终产出 CSV 文件与 SQLite 数据库。 -
读完能获得什么:
- 掌握将"非标准段落文本"通过**正则表达式(Regex)**提取为精准字段的核心技巧。
- 学会构建一套包含请求重试、异常处理、数据清洗的完整且稳健的微型爬虫脚手架。
- 获得一份干净、可直接用于公益平台匹配算法的底层元数据字典。
1️⃣ 摘要(Abstract)
志愿服务说明页通常由大段的富文本组成,缺乏统一的 API 接口。本文针对这一痛点,设计了一套基于静态 HTML 解析的自动化抽取方案。通过锁定特定 DOM 节点,辅以文本模式识别,精准提取出岗位类别、服务对象、服务场景及说明等核心要素。该方案极度适合新手作为"文本结构化"的入门基石。
2️⃣ 背景与需求(Why)
为什么要爬取志愿岗位规则?
在搭建公益服务平台或进行志愿资源调度时,最大的痛点就是"供需不匹配"。如果能将长篇大论的岗位说明书,拆解为清晰的标签(如:服务对象是"孤寡老人",场景是"社区活动室"),就能极大地提升志愿者的报名转化率与平台分发效率。
目标字段清单(Field Schema):
| 字段名 (Field) | 含义 (Meaning) | 示例值 (Example) |
|---|---|---|
post_category |
岗位类别 | 敬老助老 / 赛会展会 / 环保绿化 |
target_audience |
服务对象 | 社区独居老人、残障人士 |
service_scenario |
服务场景 | 社区日间照料中心、户外广场 |
description |
岗位说明 | 协助工作人员维持秩序,提供基础陪伴... |
3️⃣ 合规与注意事项(Mandatory Compliance)
在我们用技术传递爱心的同时,也要做一名守规矩的极客:
- 遵循 robots.txt: 公益网站通常对爬虫比较宽容,但我们依然要检查其爬虫协议,不访问后台接口。
- 温和的频率控制: 我们不攻击、不并发!设置合理的
time.sleep(),以普通人类浏览的速度进行抓取,保护公益网站的服务器资源。 - 不采集个人隐私: 本次实战仅针对**"岗位规则与分类说明"**这种公开的基础元数据,绝对不涉及任何志愿者或受助人的敏感个人信息(中立、合法、合规)。
4️⃣ 技术选型与整体流程(What/How)
- 技术路线: 静态 HTML 抓取。因为志愿服务说明通常是写死的 CMS 文章页,不需要处理复杂的 JavaScript 动态渲染。
- 为什么选
requests + bs4: 对于新手来说,这套组合直观、轻量、学习曲线极其平滑。正则表达式(re)则是我们从段落中"抠"出目标字段的终极武器。
流程图 (System Architecture):
Start URL
Fetcher: Requests with Headers
Parse HTML Content
Identify Target Category Sections
Regex Extraction for Fields
Data Cleaning & Deduplication
Storage: CSV & SQLite DB
Visualization: Category Distribution
(注:流程图默认采用英语标签,保持技术文档的专业与国际化)
5️⃣ 环境准备与依赖安装(可复现)
Python 版本: 3.9+ (推荐 3.10)
项目结构建议:
text
Volunteer_Scraper/
├── __init__.py
├── main.py # 主逻辑与运行入口
├── requirements.txt # 环境依赖
└── output/ # 生成的数据与图表
依赖安装(一键复制执行):
bash
pip install requests beautifulsoup4 pandas matplotlib loguru
6️⃣ 核心实现:请求层(Fetcher)------ 给自己加上一层伪装
在这一步,我们构建一个"绅士"的请求发送器。它会携带 User-Agent,并且遇到错误时会温柔地重试。
python
import requests
import time
from loguru import logger
import random
class GentleFetcher:
def __init__(self):
# 伪装自己是一个浏览器的常见配置
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/118.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5"
}
def fetch(self, url: str, retries: int = 3) -> str:
"""
带重试与异常处理的抓取函数
"""
for attempt in range(retries):
try:
# 随机休眠 1-3 秒,对公益网站极其友好 🌻
time.sleep(random.uniform(1, 3))
logger.info(f"🌐 Fetching {url} (Attempt {attempt + 1})...")
# Timeout 设置非常关键,防止请求卡死
response = requests.get(url, headers=self.headers, timeout=10)
# 如果是 403 或 404 等,会在这里抛出异常
response.raise_for_status()
# 确保中文不乱码,如果是 gbk 等可以自行修改
response.encoding = 'utf-8'
return response.text
except requests.exceptions.HTTPError as e:
logger.error(f"HTTP Error: {e}")
if response.status_code == 403:
logger.warning("Oops! 403 Forbidden. Your IP might be blocked or User-Agent is flagged.")
break # 遇到 403 就不要头铁了,通常是需要代理了
except Exception as e:
logger.error(f"Connection Error: {e}")
time.sleep(2 * (attempt + 1))
return ""
7️⃣ 核心实现:解析层(Parser)------ 在混沌中寻找规则
这是本次实战的精华!志愿者的说明页往往长这样:
html
<!-- 这是一个模拟的极简 HTML 结构 -->
<div class="volunteer-post">
<h3>一、敬老助老类</h3>
<p>服务对象:社区高龄老人及特困独居老人。</p>
<p>服务场景:街道日间照料中心及老人家中。</p>
<p>岗位说明:协助老人的日常生活起居,提供精神慰藉和读书看报等文化服务。要求有耐心、有爱心。</p>
</div>
针对这种极其典型的文本格式,我们要祭出正则表达式的大招:前瞻断言与懒惰匹配。
python
from bs4 import BeautifulSoup
import re
class RulesParser:
def __init__(self, html: str):
self.soup = BeautifulSoup(html, 'html.parser')
def extract_categories(self) -> list:
"""
核心方法:将结构松散的 HTML 转化为有组织的字典列表
"""
parsed_data = []
# 假设所有岗位说明都被包在 class="volunteer-post" 的 div 里
post_blocks = self.soup.find_all('div', class_='volunteer-post')
for block in post_blocks:
# 1. 抓取类别名称 (比如 h3 标签里的 "一、敬老助老类")
category_title = block.find('h3')
if not category_title:
continue
# 清洗标题,去掉类似 "一、", "二、" 这样的前缀
raw_title = category_title.get_text(strip=True)
clean_category = re.sub(r'^[一二三四五六七八九十]+[、\..\s]*', '', raw_title)
# 2. 抓取全文文本并尝试进行正则表达式抽取
# 因为 <p> 标签可能是乱七八糟的,直接拿 div 的纯文本更稳健
full_text = block.get_text(separator='\n', strip=True)
# 3. 开始用正则进行字段抽取 (容错模式:如果匹配不到就给个 "N/A" )
target_audience = self._extract_by_keyword(full_text, r'服务对象[::]\s*(.*?)(?=\n|服务场景|岗位说明|$)')
service_scenario = self._extract_by_keyword(full_text, r'服务场景[::]\s*(.*?)(?=\n|服务对象|岗位说明|$)')
description = self._extract_by_keyword(full_text, r'说明[::]\s*(.*?)$')
# 4. 组装数据
parsed_data.append({
"post_category": clean_category,
"target_audience": target_audience,
"service_scenario": service_scenario,
"description": description
})
return parsed_data
def _extract_by_keyword(self, text: str, pattern: str) -> str:
"""
利用正则从长文本中精准抠出字段值。
加入了容错处理,如果匹配失败返回默认值。
"""
match = re.search(pattern, text, re.IGNORECASE | re.DOTALL)
if match:
# 拿到结果后做一下简单的两端清洗
return match.group(1).strip()
return "Not Specified"
实战重点复盘:
看到 r'服务对象[::]\s*(.*?)(?=\n|服务场景|岗位说明|$)' 这个魔法句子了吗?它的意思是:"寻找『服务对象:』之后的内容,直到遇到换行符、或者『服务场景』四个字、或者结尾为止"。这解决了新手最怕的"怎么在没有标签包裹的文本中准确切分数据"的难题!💯
8️⃣ 数据存储与导出(Storage)------ 让劳动成果落袋为安
我们将数据同时存为轻量级数据库 SQLite 和通用的 CSV 格式。对于表头,我特别使用了英文,这极度方便你后续直接扔给大模型做分析,或者导入数据中台。
python
import pandas as pd
import sqlite3
import os
class DataExporter:
def __init__(self, output_dir: str = "output"):
self.output_dir = output_dir
if not os.path.exists(output_dir):
os.makedirs(output_dir)
def export(self, data: list):
if not data:
logger.warning("No data to export.")
return
# 使用 Pandas 处理并导出 CSV,指定英文文件名
df = pd.DataFrame(data)
csv_path = os.path.join(self.output_dir, "Volunteer_Service_Categories.csv")
df.to_csv(csv_path, index=False, encoding='utf-8-sig') # utf-8-sig 让 Excel 打开不乱码
logger.success(f"🎉 Successfully exported to CSV: {csv_path}")
# 存入 SQLite 数据库,方便日后复杂查询
db_path = os.path.join(self.output_dir, "Volunteer_Database.db")
conn = sqlite3.connect(db_path)
# 将 DataFrame 直接写入 SQL (append 模式)
df.to_sql('service_categories', conn, if_exists='replace', index=False)
conn.close()
logger.success(f"💾 Successfully saved to SQLite DB: {db_path}")
9️⃣ 运行方式与结果展示(必写)
最后,我们把上面的积木拼起来,写一个漂亮的启动脚本。
python
if __name__ == "__main__":
# 我们这里用一段模拟的 HTML 来演示,真实场景换成你想要抓取的 URL
mock_html = """
<html>
<body>
<div class="volunteer-post">
<h3>一、敬老助老类</h3>
<p>服务对象:社区高龄老人及特困独居老人。</p>
<p>服务场景:街道日间照料中心及老人家中。</p>
<p>岗位说明:协助老人的日常生活起居,提供精神慰藉和读书看报等文化服务。要求有耐心、有爱心。</p>
</div>
<div class="volunteer-post">
<h3>二、大型赛会类</h3>
<p>服务对象:参会嘉宾及普通观众。</p>
<p>服务场景:大型国际会议中心、体育馆。</p>
<p>岗位说明:负责场馆引导、语言翻译、秩序维护等综合性志愿服务。</p>
</div>
</body>
</html>
"""
logger.info("🚀 Starting Volunteer Categories Scraper...")
# 正常流程应该是:
# fetcher = GentleFetcher()
# html_content = fetcher.fetch("https://some-volunteer-site.org/rules")
# 这里用 mock 的数据进行解析测试
parser = RulesParser(mock_html)
result_data = parser.extract_categories()
# 展示抓到的成果
for item in result_data:
print(f"📌 岗位: {item['post_category']} | 👥 对象: {item['target_audience']}")
# 导出
exporter = DataExporter()
exporter.export(result_data)
运行结果预览:
bash
2023-11-01 10:00:00 | INFO | 🚀 Starting Volunteer Categories Scraper...
📌 岗位: 敬老助老类 | 👥 对象: 社区高龄老人及特困独居老人。
📌 岗位: 大型赛会类 | 👥 对象: 参会嘉宾及普通观众。
2023-11-01 10:00:01 | SUCCESS | 🎉 Successfully exported to CSV: output/Volunteer_Service_Categories.csv
2023-11-01 10:00:01 | SUCCESS | 💾 Successfully saved to SQLite DB: output/Volunteer_Database.db
🔟 常见问题与排错(强烈建议写)
在你把这套代码放到别的网站上测试时,你可能会遇到以下小烦恼,别怕,我教你怎么见招拆招!🛡️
-
返回状态码
403 Forbidden/ 遇到验证码怎么办?- 原因: 网站的反爬机制觉得你不像普通用户,或者是你的请求太快被频控限制(Rate Limiting)了。
- 排错: 检查你的
User-Agent是否正常;在每次请求间隙使用time.sleep(random.randint(3, 8))加大延迟;如果还不行,说明可能需要加代理 IP 池了。
-
成功返回状态码,但 HTML 里一片空白/抓取不到我要的内容?
- 原因: 这说明你要的数据是动态渲染的!也就是通过 AJAX 从接口异步加载,或者是被前端 JS 渲染出来的。
- 排错: 在浏览器的"开发者工具 (F12) -> Network -> Fetch/XHR"里寻找真实的 JSON 数据接口。或者直接放弃
requests,转投Playwright这种所见即所得的自动化工具怀抱。
-
解析出来的中文字符出现
\u4e00\u4e8c或者一堆乱码?- 原因: 编码错误(GBK/UTF-8 冲突)。老旧的政府或公益网站偶尔会用
gb2312。 - 排错: 在获取到 response 后,强制转码:
response.encoding = response.apparent_encoding。
- 原因: 编码错误(GBK/UTF-8 冲突)。老旧的政府或公益网站偶尔会用
-
正则匹配一直报
NoneType错误?- 原因: 网页结构的变动导致匹配失败,或者你用了
match.group(1)但根本没有match对象。 - 排错: 永远记得容错! 就像我们在代码中做的那样,提取前用
if match:判断一下,找不到就优雅地返回Not Specified(未说明)。
- 原因: 网页结构的变动导致匹配失败,或者你用了
1️⃣1️⃣ 进阶优化(可视化生成)
为了让你抓取的数据不再是一堆枯燥的代码,我们来加点魔法 !用 Python 的 matplotlib 给我们的成果画个美美的条形图。
(注意:所有的图表元素我们均已采用全英文,完美符合数据科学行业的标准规范。)
python
import matplotlib.pyplot as plt
import pandas as pd
import os
def generate_visualization(csv_filepath: str):
"""
读取抓取到的 CSV 文件,并生成岗位场景的类别统计图
"""
if not os.path.exists(csv_filepath):
return
df = pd.read_csv(csv_filepath)
# 计算每个岗位的数量 (假设后续抓了几百条具体的志愿项目)
# 这里用我们现有的 mock 数据简单展示
category_counts = df['post_category'].value_counts()
plt.figure(figsize=(10, 6))
# 绘制条形图
category_counts.plot(kind='bar', color='skyblue', edgecolor='black')
# 设置全英文的标题和坐标轴
plt.title('Distribution of Volunteer Service Categories', fontsize=16, fontweight='bold')
plt.xlabel('Post Category (Chinese Names)', fontsize=12)
plt.ylabel('Number of Available Scenarios', fontsize=12)
# 调整 x 轴标签的旋转角度
plt.xticks(rotation=45, ha='right')
plt.tight_layout()
chart_path = os.path.join(os.path.dirname(csv_filepath), 'Category_Distribution.png')
plt.savefig(chart_path, dpi=300)
print(f"📊 Visualization saved to: {chart_path}")
# 调用示例
# generate_visualization("output/Volunteer_Service_Categories.csv")
1️⃣2️⃣ 总结与延伸阅读
呼!太棒了!👏 走到这里,我们一起干脆利落地完成了这套关于志愿服务说明页的清洗系统!你是不是发现:原来把看似杂乱无章的规则文字,变成工工整整的数据库,竟然可以这么有成就感!✨
- 复盘我们的成果: 我们用极简的
requests构建了温柔的访问者,用bs4和正则写出了精准的提取器,最后甚至用pandas生成了可视化的雏形。这不仅是一次代码的实战,更是一次用技术赋能公益场景的尝试!❤️ - 延伸思考: 目前我们的正则是基于"关键字(如『服务对象:』)"来匹配的。如果某个网站的文本里连关键字都不写,而是纯粹的一段长描述,我们该怎么办呢?🤔 这时候,就可以引入**自然语言处理(NLP)**模型,或者是直接调用大模型的 API 接口进行信息抽取(Information Extraction,IE)啦!
🌟 文末
好啦~以上就是本期的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
✅ 专栏持续更新中|建议收藏 + 订阅
墙裂推荐订阅专栏 👉 《Python爬虫实战》,本专栏秉承着以"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一期内容都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴 :强烈建议先订阅专栏 《Python爬虫实战》,再按目录大纲顺序学习,效率十倍上升~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】等写成某期实战?
评论区留言告诉我你的需求,我会优先安排实现(更新)哒~
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)
✅ 免责声明
本文爬虫思路、相关技术和代码仅用于学习参考,对阅读本文后的进行爬虫行为的用户本作者不承担任何法律责任。
使用或者参考本项目即表示您已阅读并同意以下条款:
- 合法使用: 不得将本项目用于任何违法、违规或侵犯他人权益的行为,包括但不限于网络攻击、诈骗、绕过身份验证、未经授权的数据抓取等。
- 风险自负: 任何因使用本项目而产生的法律责任、技术风险或经济损失,由使用者自行承担,项目作者不承担任何形式的责任。
- 禁止滥用: 不得将本项目用于违法牟利、黑产活动或其他不当商业用途。
- 使用或者参考本项目即视为同意上述条款,即 "谁使用,谁负责" 。如不同意,请立即停止使用并删除本项目。!!!
