Python爬虫实战:手把手教你如何采集开源字体仓库目录页(Google Fonts / 其他公开字体目录)!

㊗️本期内容已收录至专栏《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 字库字典。

读完这篇,你能获得:

  1. 彻底掌握**"列表页提取链接 → 详情页深度抓取 → 数据合并"**的二段式爬虫设计模式。
  2. 学会在高频抓取中合理使用 Session 来提升网络请求效率。
  3. 获得一个可以直接运行的、包含可视化图表(纯英文标签)的工业级爬虫脚本。

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️⃣ 合规与注意事项(必写)

在构建咱们的"字体小库"时,规矩不能破:🛡️

  1. Robots.txt 规范 :大型公开目录通常允许抓取基础信息,但请确保不要触碰 /admin 或用户私密目录。
  2. 频率控制(Rate Limit) :由于是一个列表页对应 N 个详情页,请求量会被放大。详情页循环中必须加入随机休眠(time.sleep,切忌实施 DDoS 式的并发攻击。
  3. 版权隔离 :我们仅抓取公开开源的元数据(文本信息) ,不直接批量盗刷和下载 .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

🔟 常见问题与排错(强烈建议写)

  1. 抓取到的内容是空壳子(只有 JavaScript)怎么办?

    • 排查:这说明目标网站可能像 Google Fonts 一样使用了动态渲染。
    • 解法 :去浏览器 Network 面板抓包,找 XHR/Fetch 下的 JSON 接口;或者把 requests 换成 Playwright 渲染后再解析。
  2. 提取的 styles_count 报错 ValueError: invalid literal for int()

    • 原因:详情页可能写的是 "No styles available" 而不是数字。
    • 解法 :使用正则表达式 re.search(r'\d+', text) 提取,如果提取不到,提供默认值 1
  3. 连接频繁断开(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 的 StreamlitFlask 框架,花 50 行代码写一个 Web 前端界面,把这份数据变成一个真正属于你的"在线字体检索库"!

🌟 文末

好啦~以上就是本期的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥

✅ 专栏持续更新中|建议收藏 + 订阅

墙裂推荐订阅专栏 👉 《Python爬虫实战》,本专栏秉承着以"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一期内容都做到:

✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)

📣 想系统提升的小伙伴 :强烈建议先订阅专栏 《Python爬虫实战》,再按目录大纲顺序学习,效率十倍上升~

✅ 互动征集

想让我把【某站点/某反爬/某验证码/某分布式方案】等写成某期实战?

评论区留言告诉我你的需求,我会优先安排实现(更新)哒~


⭐️ 若喜欢我,就请关注我叭~(更新不迷路)

⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)

⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)


✅ 免责声明

本文爬虫思路、相关技术和代码仅用于学习参考,对阅读本文后的进行爬虫行为的用户本作者不承担任何法律责任。

使用或者参考本项目即表示您已阅读并同意以下条款:

  • 合法使用: 不得将本项目用于任何违法、违规或侵犯他人权益的行为,包括但不限于网络攻击、诈骗、绕过身份验证、未经授权的数据抓取等。
  • 风险自负: 任何因使用本项目而产生的法律责任、技术风险或经济损失,由使用者自行承担,项目作者不承担任何形式的责任。
  • 禁止滥用: 不得将本项目用于违法牟利、黑产活动或其他不当商业用途。
  • 使用或者参考本项目即视为同意上述条款,即 "谁使用,谁负责" 。如不同意,请立即停止使用并删除本项目。!!!
相关推荐
Chase_______2 小时前
【Python 基础】第2章:流程控制完全指南(if/match/while/for)
python
第一程序员2 小时前
Python高级特性详解:从基础到进阶
python·github
wzhidev3 小时前
04、Python核心数据类型详解:从一段诡异的调试说起
开发语言·python
wzhidev3 小时前
05、Python流程控制与函数定义:从调试现场到工程实践
linux·网络·python
Thomas.Sir3 小时前
第十一章:深入剖析 Prompt 提示工程
python·prompt
Fortune793 小时前
用Pandas处理时间序列数据(Time Series)
jvm·数据库·python
2401_878530213 小时前
高级爬虫技巧:处理JavaScript渲染(Selenium)
jvm·数据库·python
Agent产品评测局3 小时前
企业自动化项目,如何做好内部推广与员工培训?——企业级智能体落地与人才赋能实测指南
运维·人工智能·ai·chatgpt·自动化
2401_873544923 小时前
使用Black自动格式化你的Python代码
jvm·数据库·python