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

全文目录:
-
-
- [🌟 开篇语](#🌟 开篇语)
- [0️⃣ 前言(Preface)](#0️⃣ 前言(Preface))
- [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爬虫实战》👈,一次订阅后,专栏内的所有文章可永久免费阅读,持续更新中。
💕订阅后更新会优先推送,按目录学习更高效💯~
0️⃣ 前言(Preface)
嘿,代码艺术家们!👋 今天我们要用 Python 写一个优雅的爬虫,把开源字体网站上的精美字体名录、风格数量和支持语言全部"打包"带走。我们将使用 requests + BeautifulSoup 的经典组合,最终产出一个可以用于数据分析和检索的 JSON/CSV 字库字典。
读完这篇,你能获得:
- 彻底掌握**"列表页提取链接 → 详情页深度抓取 → 数据合并"**的二段式爬虫设计模式。
- 学会在高频抓取中合理使用
Session来提升网络请求效率。 - 获得一个可以直接运行的、包含可视化图表(纯英文标签)的工业级爬虫脚本。
1️⃣ 摘要(Abstract)
本文将演示如何构建一个二段式静态网页爬虫,抓取公开字体目录。从列表页提取字体名称与详情页 URL,随后深入详情页挖掘字体家族、风格数量、分类及支持语言。全程包含异常重试与容错机制,最终将清洗后的元数据持久化为 CSV 与 JSON 格式,并生成英文标签的数据可视化图表。
2️⃣ 背景与需求(Why)
为什么要爬?
- 素材聚合:设计师找字体总是在不同网站间穿梭,不如自己建立一个带标签的本地检索库。
- 自动化资产管理:为前端团队整理一份清晰的可用开源字体清单。
- 技术进阶:练习处理多级页面数据传递和异常字段清洗的最佳实战。
目标字段清单(Target Fields):
font_name:字体名(如 Open Sans)font_family:字体家族styles_count:风格数量(如 Regular, Bold, Italic 共 3 种)supported_langs:支持语言(如 Latin, Cyrillic)category:分类(如 Sans Serif, Display)preview_text:预览文案(模拟展示句)detail_link:详情链接(溯源追踪)
3️⃣ 合规与注意事项(必写)
在构建咱们的"字体小库"时,规矩不能破:🛡️
- Robots.txt 规范 :大型公开目录通常允许抓取基础信息,但请确保不要触碰
/admin或用户私密目录。 - 频率控制(Rate Limit) :由于是一个列表页对应 N 个详情页,请求量会被放大。详情页循环中必须加入随机休眠(
time.sleep),切忌实施 DDoS 式的并发攻击。 - 版权隔离 :我们仅抓取公开开源的元数据(文本信息) ,不直接批量盗刷和下载
.ttf/.otf字体文件资源,保持技术中立与合规。
4️⃣ 技术选型与整体流程(What/How)
- 类型:静态 HTML 多级页面抓取。
- 技术栈 :
requests(带 Session 维持) +BeautifulSoup(容错率极高的 HTML 解析器) +Pandas(数据存储)。 - 整体流程 :
发起列表页请求➔解析基础字段与详情 URL➔(循环) 发起详情页请求➔解析高级字段➔合并字典➔异常清洗➔导出 JSON/CSV
5️⃣ 环境准备与依赖安装(可复现)
建议使用 Python 3.8+ 环境。准备好你的终端,敲下这行魔法咒语:
bash
pip install requests beautifulsoup4 pandas matplotlib
项目目录结构:
text
font_scraper/
├── data/
│ ├── open_fonts_dataset.csv # 最终输出的表格
│ └── open_fonts_dataset.json # 最终输出的 JSON
├── outputs/
│ └── font_styles_chart.png # 稍后生成的可视化图表
└── spider_fonts.py # 我们的主程序
6️⃣ 核心实现:请求层(Fetcher)
在二段式采集中,强烈建议使用 requests.Session()。它能在抓取大量详情页时复用底层 TCP 连接,速度飞起!🚀
python
import requests
import time
import random
from bs4 import BeautifulSoup
from requests.exceptions import RequestException
class FontRepoFetcher:
def __init__(self, base_url):
self.base_url = base_url
self.session = requests.Session()
# 伪装自己是一个热爱字体的 Mac 设计师
self.headers = {
'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',
'Accept-Language': 'en-US,en;q=0.9',
}
self.session.headers.update(self.headers)
def get_html(self, url, retries=3):
"""
稳健的 HTML 获取函数
"""
for i in range(retries):
try:
# 设置 timeout,防止爬虫在某个响应慢的详情页假死
response = self.session.get(url, timeout=10)
if response.status_code == 200:
return response.text
print(f"⚠️ 状态码异常: {response.status_code} - {url}")
except RequestException as e:
print(f"❌ 请求超时/失败 (尝试 {i+1}/{retries}): {url} - {e}")
time.sleep(random.uniform(1.5, 3))
return None
7️⃣ 核心实现:解析层(Parser)
这里是爬虫的心脏,我们需要处理两层解析。以典型的结构为例:
python
def parse_list_page(self, html):
"""
[第一段] 列表页解析:抓取基础数据和详情页门票 (URL)
"""
soup = BeautifulSoup(html, 'html.parser')
# 假设字体卡片包裹在 <div class="font-card"> 中
cards = soup.find_all('div', class_='font-card')
fonts_basic_info = []
for card in cards:
try:
# 提取名称和详情链接
name_tag = card.find('h2', class_='font-name').find('a')
font_name = name_tag.text.strip()
detail_url = name_tag['href']
# 处理相对路径
if detail_url.startswith('/'):
detail_url = self.base_url + detail_url
preview_text = card.find('p', class_='preview-text').text.strip() if card.find('p', class_='preview-text') else "The quick brown fox jumps over the lazy dog."
fonts_basic_info.append({
'font_name': font_name,
'preview_text': preview_text,
'detail_link': detail_url
})
except AttributeError as e:
print(f"⚠️ 列表页解析某个卡片出错跳过: {e}")
continue
return fonts_basic_info
def parse_detail_page(self, html):
"""
[第二段] 详情页解析:挖掘深入指标
"""
soup = BeautifulSoup(html, 'html.parser')
detail_data = {
'font_family': 'N/A',
'styles_count': 1,
'supported_langs': 'N/A',
'category': 'N/A'
}
try:
# 假设网页结构如此,实战中需根据实际 DOM 修改
# 提取分类
cat_tag = soup.find('span', class_='font-category')
if cat_tag: detail_data['category'] = cat_tag.text.strip()
# 提取风格数量 (如 "8 Styles")
style_tag = soup.find('div', class_='style-count')
if style_tag:
# 提取纯数字
styles_text = style_tag.text.strip()
detail_data['styles_count'] = int(''.join(filter(str.isdigit, styles_text)) or 1)
# 提取语言 (可能是一个 ul 列表)
lang_tags = soup.find_all('li', class_='supported-language')
if lang_tags:
detail_data['supported_langs'] = ", ".join([l.text.strip() for l in lang_tags])
# 提取家族名
family_tag = soup.find('h1', class_='font-family-name')
if family_tag: detail_data['font_family'] = family_tag.text.strip()
except Exception as e:
print(f"⚠️ 详情页解析局部缺失,已使用默认值: {e}")
return detail_data
8️⃣ 数据存储与导出(Storage)
我们要同时导出 CSV 和 JSON,并基于详情页 URL 进行去重。
python
import pandas as pd
import json
import os
def save_data(data_list, base_filename="open_fonts_dataset"):
if not data_list:
return
df = pd.DataFrame(data_list)
# 基于详情链接去重
df.drop_duplicates(subset=['detail_link'], inplace=True)
os.makedirs('data', exist_ok=True)
# 保存 CSV
csv_path = f"data/{base_filename}.csv"
df.to_csv(csv_path, index=False, encoding='utf-8-sig')
# 保存 JSON
json_path = f"data/{base_filename}.json"
df.to_json(json_path, orient='records', force_ascii=False, indent=4)
print(f"💾 完美!成功保存 {len(df)} 种字体数据。")
print(f"📁 文件路径: {csv_path} & {json_path}")
9️⃣ 运行方式与结果展示(必写)
这是将上下游串联起来的主调度逻辑:
python
def main():
print("🎨 开源字体库爬虫启动!")
# 这里以一个假想的标准化目录页为例
BASE_URL = "https://example-font-repo.com"
fetcher = FontRepoFetcher(BASE_URL)
all_fonts = []
# 假设我们只抓取第 1 页作为演示
list_url = f"{BASE_URL}/fonts?page=1"
print(f"🔍 正在获取列表页: {list_url}")
list_html = fetcher.get_html(list_url)
if not list_html:
return
basic_fonts = fetcher.parse_list_page(list_html)
print(f"✅ 列表页解析完成,找到 {len(basic_fonts)} 个字体,准备进入详情页深度挖掘...")
for idx, font in enumerate(basic_fonts):
print(f" [{idx+1}/{len(basic_fonts)}] 正在深入抓取: {font['font_name']}")
detail_html = fetcher.get_html(font['detail_link'])
if detail_html:
# 获取详情数据
detail_data = fetcher.parse_detail_page(detail_html)
# 字典合并:将基础数据和详情数据拼在一起
full_font_data = {**font, **detail_data}
all_fonts.append(full_font_data)
# ⚠️ 灵魂操作:休眠防封!
time.sleep(random.uniform(1.0, 2.5))
save_data(all_fonts)
if __name__ == "__main__":
main()
模拟运行输出结果:
text
🎨 开源字体库爬虫启动!
🔍 正在获取列表页: https://example-font-repo.com/fonts?page=1
✅ 列表页解析完成,找到 20 个字体,准备进入详情页深度挖掘...
[1/20] 正在深入抓取: Open Sans
[2/20] 正在深入抓取: Roboto
...
💾 完美!成功保存 20 种字体数据。
📁 文件路径: data/open_fonts_dataset.csv & data/open_fonts_dataset.json
🔟 常见问题与排错(强烈建议写)
-
抓取到的内容是空壳子(只有 JavaScript)怎么办?
- 排查:这说明目标网站可能像 Google Fonts 一样使用了动态渲染。
- 解法 :去浏览器 Network 面板抓包,找
XHR/Fetch下的 JSON 接口;或者把requests换成Playwright渲染后再解析。
-
提取的
styles_count报错ValueError: invalid literal for int()?- 原因:详情页可能写的是 "No styles available" 而不是数字。
- 解法 :使用正则表达式
re.search(r'\d+', text)提取,如果提取不到,提供默认值1。
-
连接频繁断开(ConnectionResetError)?
- 解法 :这就是为什么我们在代码里强制要求使用
requests.Session(),并加上随机time.sleep的原因。必要时可引入代理 IP 池。
- 解法 :这就是为什么我们在代码里强制要求使用
1️⃣1️⃣ 进阶优化:数据探索可视化(加分项)
既然有了数据,我们就来用 matplotlib 做一个"不同字体分类下的平均风格数量"柱状图吧!(纯英文标注,专业感拉满 📈)
python
import matplotlib.pyplot as plt
import os
def generate_visualization(csv_path="data/open_fonts_dataset.csv"):
if not os.path.exists(csv_path):
return
df = pd.read_csv(csv_path)
# 按分类计算平均风格数量
avg_styles = df.groupby('category')['styles_count'].mean().sort_values()
plt.figure(figsize=(10, 6))
# 绘制横向柱状图
avg_styles.plot(kind='barh', color='#4CAF50', edgecolor='black')
# 强制英文标签 (English labels applied)
plt.title("Average Number of Styles by Font Category", fontsize=14, pad=15)
plt.xlabel("Average Styles Count", fontsize=12)
plt.ylabel("Font Category", fontsize=12)
plt.grid(axis='x', linestyle='--', alpha=0.7)
os.makedirs('outputs', exist_ok=True)
out_path = 'outputs/font_styles_chart.png' # English filename
plt.tight_layout()
plt.savefig(out_path, dpi=300)
print(f"📊 图表已生成至: {out_path}")
# 可在 main() 的结尾调用:generate_visualization()
1️⃣2️⃣ 总结与延伸阅读
复盘总结:
我们成功通关了"二段式采集"这个经典的爬虫关卡!先在列表页撒网捞取 URL 门票,再逐个进入详情页剥洋葱般提取深度数据,最后将两者完美缝合拼接。这个架构可以无缝套用到电商商品抓取、人才简历抓取、甚至是新闻资讯抓取中!
下一步的魔法:
既然已经有了一个完整的 .json 字体数据库,你可以尝试用 Python 的 Streamlit 或 Flask 框架,花 50 行代码写一个 Web 前端界面,把这份数据变成一个真正属于你的"在线字体检索库"!
🌟 文末
好啦~以上就是本期的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
✅ 专栏持续更新中|建议收藏 + 订阅
墙裂推荐订阅专栏 👉 《Python爬虫实战》,本专栏秉承着以"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一期内容都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴 :强烈建议先订阅专栏 《Python爬虫实战》,再按目录大纲顺序学习,效率十倍上升~

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