Python爬虫实战:采集博客园 Cnblogs文章标题、发布日期、标签以及HTML正文等(附 Markdown 文档格式预览)!

㊙️本期内容已收录至专栏《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 - CSS Selector 专场)](#7️⃣ 核心实现:解析层(Parser - CSS Selector 专场))
      • [8️⃣ 数据存储与导出(Storage - 生成 Markdown)](#8️⃣ 数据存储与导出(Storage - 生成 Markdown))
      • [9️⃣ 运行方式与结果展示(必写)](#9️⃣ 运行方式与结果展示(必写))
      • [🔟 常见问题与排错(Expert Advice)](#🔟 常见问题与排错(Expert Advice))
      • [1️⃣1️⃣ 进阶优化(可选但加分)](#1️⃣1️⃣ 进阶优化(可选但加分))
      • [1️⃣2️⃣ 总结与延伸阅读](#1️⃣2️⃣ 总结与延伸阅读)
      • [🌟 文末](#🌟 文末)
        • [📌 专栏持续更新中|建议收藏 + 订阅](#📌 专栏持续更新中|建议收藏 + 订阅)
        • [✅ 互动征集](#✅ 互动征集)

🌟 开篇语

哈喽,各位小伙伴们你们好呀~我是【喵手】。

运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO

欢迎大家常来逛逛,一起学习,一起进步~🌟

我长期专注 Python 爬虫工程化实战 ,主理专栏 《Python爬虫实战》:从采集策略反爬对抗 ,从数据清洗分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上

📌 专栏食用指南(建议收藏)

  • ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
  • ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
  • ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
  • ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用

📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅/关注专栏👉《Python爬虫实战》👈

💕订阅后更新会优先推送,按目录学习更高效💯~

1️⃣ 摘要(Abstract)

本文将构建一个针对传统博客平台(以博客园 Cnblogs 个人主页为例)的整站备份工具。我们将使用 requests 处理翻页请求,利用 CSS 选择器 (soup.select) 高效提取文章的标题发布日期标签 以及HTML正文 。最核心的创新点在于:我们会使用 html2text 库将网页内容清洗为 Markdown 格式,并自动生成包含元数据的 YAML Front Matter ,最终产出可以直接导入 Hexo/Hugo 的 .md 文件。

读完这篇你能获得什么?

  1. 🎨 技能解锁:精通 CSS 选择器语法,体验比 XPath 更符合前端直觉的定位方式。
  2. 🔄 格式转换:学会将 HTML 自动化转换为 Markdown 的核心技术。
  3. 📂 工程实践:掌握如何为每一篇文章生成标准的文件名和元数据头。

2️⃣ 背景与需求(Why)

为什么要爬?

  1. 数据主权:平台可能会倒闭、封号或改版,只有保存在自己硬盘里的 Markdown 才是真正属于你的资产。
  2. 二次分发:想把旧文章同步到掘金、知乎或公众号?有了 Markdown 源码,复制粘贴只需一秒。
  3. 写作分析:通过抓取"发布日期"和"标签",你可以分析自己哪一年产出最高,或者最关注哪个技术领域。

目标站点 :博客园某博主主页(示例:https://www.cnblogs.com/[username]/default.html?page=1

目标字段清单

字段名 说明 示例值
title 文章标题 Python 异步编程实战指南
date 发布时间 2025-08-21 14:30
tags 标签列表 [Python, Asyncio, 爬虫]
content 正文内容 (转换后的 Markdown 文本)

3️⃣ 合规与注意事项(必写)

知识无价,尊重作者:

  1. 版权声明 :本爬虫仅限备份自己的博客经授权的迁移。严禁批量爬取他人文章进行洗稿、转载或商业获利。
  2. Robots.txt:博客园通常允许搜索引擎和个人爬虫访问公开页面,但禁止高频抓取。
  3. 频率控制:老规矩,单线程 + 随机休眠。博客园的反爬机制虽然不严,但触发频率限制后会封 IP 几小时,得不偿失。

4️⃣ 技术选型与整体流程(What/How)

技术选型:Requests + BeautifulSoup (CSS Selector) + html2text

  • CSS Selector :也就是 soup.select()。对于熟悉前端开发(jQuery/CSS)的同学来说,这是最直观的提取方式。比如提取标题就是 .postTitle a,简单粗暴。
  • html2text :这是一个非常有用的 Python 库,能把 HTML 标签(如 <h1>, <b>, <a>)"翻译"成 Markdown 语法(#, **, []())。

整体流程图

5️⃣ 环境准备与依赖安装(可复现)

Python 版本:3.8+

依赖安装

这次我们需要引入 html2text 这个转换神器。

bash 复制代码
pip install requests beautifulsoup4 html2text

项目目录结构

text 复制代码
blog_migrator/
├── posts/             # 结果存放(markdown 文件)
├── migrator.py        # 主程序
└── config.py          # 配置文件(用户名等)

6️⃣ 核心实现:请求层(Fetcher)

为了应对博客可能存在的反爬(如检测 Referer),我们将 headers 封装得更加像真实用户访问。

python 复制代码
import requests
import time
import random

class BlogFetcher:
    def __init__(self):
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
            # 加上 Referer 有时能避开防盗链
            'Referer': 'https://www.cnblogs.com/'
        }
        self.session = requests.Session()
        self.session.headers.update(self.headers)

    def get_page(self, url):
        """通用请求方法,带重试和状态检查"""
        try:
            # 随机休眠,模拟阅读间隔
            time.sleep(random.uniform(1, 2.5))
            
            print(f"📡 正在抓取: {url}")
            resp = self.session.get(url, timeout=15)
            
            if resp.status_code == 200:
                return resp.text
            elif resp.status_code == 404:
                print(f"⚠️ 页面不存在: {url}")
                return None
            else:
                print(f"❌ 请求异常,状态码: {resp.status_code}")
                return None
        except Exception as e:
            print(f"❌ 网络错误: {e}")
            return None

7️⃣ 核心实现:解析层(Parser - CSS Selector 专场)

这里我们将展示 CSS 选择器的优雅之处。

例如:

  • XPath : //div[@class='post']/h2/a/text()
  • CSS : .post h2 a (是不是简洁很多?)
python 复制代码
from bs4 import BeautifulSoup
import html2text

class BlogParser:
    def __init__(self):
        # 初始化 markdown 转换器
        self.converter = html2text.HTML2Text()
        self.converter.ignore_links = False # 保留链接
        self.converter.ignore_images = False # 保留图片
        self.converter.body_width = 0 # 不自动换行,这点很重要!

    def parse_list(self, html):
        """解析文章列表页"""
        soup = BeautifulSoup(html, 'html.parser')
        articles = []
        
        # 博客园标准结构:class="day" 表示一天的内容,里面可能有几篇 class="postTitle"
        # 使用 CSS 选择器定位每一篇文章块
        # 注意:不同主题模板结构可能不同,这里以官方默认主题为例
        post_items = soup.select('.day') 
        
        for item in post_items:
            # 提取日期(通常在 .dayTitle 中)
            date_raw = item.select_one('.dayTitle a').get_text(strip=True) if item.select_one('.dayTitle a') else ""
            
            # 一天可能发多篇,遍历下面的 .postTitle
            titles = item.select('.postTitle a')
            for link_tag in titles:
                title = link_tag.get_text(strip=True)
                url = link_tag['href']
                
                articles.append({
                    "title": title,
                    "date": date_raw, # 简单起见只取日期,不含时分秒
                    "url": url
                })
        return articles

    def parse_detail(self, html):
        """解析详情页,提取正文、标签并转换为 Markdown"""
        soup = BeautifulSoup(html, 'html.parser')
        
        # 1. 提取标签 (Tags)
        # CSS 选择器:查找 id="EntryTag" 下的所有链接文本
        tags = [tag.get_text(strip=True) for tag in soup.select('#EntryTag a')]
        
        # 2. 提取正文 HTML
        # 博客园正文通常在 id="cnblogs_post_body"
        content_div = soup.select_one('#cnblogs_post_body')
        
        if not content_div:
            return None, []

        # 3. 核心转换:HTML -> Markdown
        # str(content_div) 将 Beautifulsoup 对象转回 HTML 字符串
        markdown_content = self.converter.handle(str(content_div))
        
        return markdown_content, tags

8️⃣ 数据存储与导出(Storage - 生成 Markdown)

这里我们不做 CSV 了,我们要生成 带 YAML 头信息的 .md 文件。这是 Hugo/Hexo 等静态博客引擎的标准格式。

格式示例:

markdown 复制代码
---
title: Python 爬虫实战
date: 2026-01-27
tags: [Python, Spider]
---

# 正文内容...
python 复制代码
import os
import re

class MarkdownStorage:
    def __init__(self, save_dir="posts"):
        self.save_dir = save_dir
        os.makedirs(self.save_dir, exist_ok=True)

    def clean_filename(self, title):
        """清洗文件名,去掉 Windows/Linux 不允许的字符"""
        # 将 / \ : * ? " < > | 替换为下划线
        return re.sub(r'[\\/:*?"<>|]', '_', title)

    def save_post(self, meta_data, content):
        """
        meta_data: 包含 title, date, tags, url
        content: markdown 文本
        """
        title = meta_data['title']
        clean_title = self.clean_filename(title)
        
        # 构造 YAML Front Matter
        yaml_header = "---\n"
        yaml_header += f"title: \"{title}\"\n"
        yaml_header += f"date: {meta_data['date']}\n"
        # 将列表转为 [tag1, tag2] 格式字符串
        tags_str = ", ".join([f'"{t}"' for t in meta_data['tags']])
        yaml_header += f"tags: [{tags_str}]\n"
        yaml_header += f"origin_url: {meta_data['url']}\n"
        yaml_header += "---\n\n"
        
        full_text = yaml_header + content
        
        file_path = os.path.join(self.save_dir, f"{clean_title}.md")
        
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(full_text)
            
        print(f"💾 已保存: {file_path}")

9️⃣ 运行方式与结果展示(必写)

整合逻辑:我们先抓列表,拿到链接后,再进详情页抓正文和标签,最后存文件。

python 复制代码
# migrator.py
# 导入 Fetcher, Parser, MarkdownStorage

def main():
    # 请替换为你想爬取的博主 ID
    target_user = "vamei" # Python 圈知名博主 Vamei (致敬)
    base_url = f"https://www.cnblogs.com/{target_user}/default.html"
    
    print(f"🚀 开始备份博主 [{target_user}] 的文章...")
    
    fetcher = BlogFetcher()
    parser = BlogParser()
    storage = MarkdownStorage(save_dir=f"backup_{target_user}")
    
    # 演示:只爬取前 2 页
    for page in range(1, 3):
        url = f"{base_url}?page={page}"
        print(f"\n📄 正在处理第 {page} 页列表...")
        
        list_html = fetcher.get_page(url)
        if not list_html:
            break
            
        articles = parser.parse_list(list_html)
        print(f"   🔍 本页发现 {len(articles)} 篇文章")
        
        for article in articles:
            # 二次请求:获取详情
            detail_html = fetcher.get_page(article['url'])
            if detail_html:
                md_content, tags = parser.parse_detail(detail_html)
                
                if md_content:
                    # 合并数据
                    article['tags'] = tags
                    storage.save_post(article, md_content)
                else:
                    print(f"   ⚠️ 解析正文失败: {article['title']}")
            else:
                print(f"   ⚠️ 详情页无法访问: {article['title']}")

    print("\n🎉 全部备份完成!请查看 backup 目录。")

if __name__ == "__main__":
    main()

示例输出结果

text 复制代码
🚀 开始备份博主 [vamei] 的文章...

📄 正在处理第 1 页列表...
📡 正在抓取: https://www.cnblogs.com/vamei/default.html?page=1
   🔍 本页发现 10 篇文章
📡 正在抓取: https://www.cnblogs.com/vamei/archive/2012/09/13/2682778.html
💾 已保存: backup_vamei/Python快速教程.md
📡 正在抓取: https://www.cnblogs.com/vamei/archive/2012/09/14/2683756.html
💾 已保存: backup_vamei/Linux文件系统.md
...
🎉 全部备份完成!请查看 backup 目录。

生成的 Markdown 文件内容预览

markdown 复制代码
---
title: "Python快速教程"
date: 2012年9月13日
tags: ["Python", "教程"]
origin_url: https://www.cnblogs.com/vamei/archive/2012/09/13/2682778.html
---

# Python快速教程

作者:Vamei 出处:http://www.cnblogs.com/vamei

## 1. 基础

Python 是一种解释型、面向对象、动态数据类型的高级程序设计语言...

🔟 常见问题与排错(Expert Advice)

  1. CSS 选择器定位不到元素?

    • 原因:博客园支持用户自定义主题(Skin)。有的博主用的是"SimpleMemory"模板,有的是"LessIsMore"。它们的 HTML 结构(class 名)完全不同!
    • 解法 :如果你发现爬不到,大概率是模板问题。你需要在 parse_list 里写多套 try-except 或者 if 判断,适配常见的几套模板结构。
  2. Markdown 图片显示裂图

    • 原因 :我们只是把 <img> 标签转成了 ![](),但图片链接依然是远程链接。如果博客园开启了防盗链,你在本地 Markdown 编辑器里就看不到图片了。
    • 解法 :进阶版爬虫需要把图片下载到本地 assets 文件夹,并替换 Markdown 中的链接路径。
  3. html2text 转换格式混乱

    • 原因:原网页中包含大量的代码块(Code Block)。html2text 默认处理代码块可能不够完美。
    • 解法 :可以考虑自定义 html2text 的参数,或者在解析前先用 BS4 把 <pre> 标签里的内容提取出来单独处理,防止被转义。

1️⃣1️⃣ 进阶优化(可选但加分)

  1. 多线程下载(Threading)
    由于我们既要请求列表,又要请求详情,网络 I/O 耗时很长。可以使用 concurrent.futures.ThreadPoolExecutor 开启 5-10 个线程并发抓取详情页,速度能提升 5 倍以上。
  2. 图片本地化(Image Downloader)
    parse_detail 里,用正则找出所有的 ![]((http...)) 图片链接,下载图片并重命名,实现彻底的"离线备份"。
  3. 增量备份
    在本地建一个 history.json,记录上次备份到的最新一篇文章的日期。下次运行时,一旦遇到日期早于记录日期的文章,直接 break 终止循环,节省时间。

1️⃣2️⃣ 总结与延伸阅读

复盘

在这个项目中,我们不仅仅是在"抓数据",更是在做"数据格式化工程"。我们利用 CSS 选择器 快速切入 DOM 结构,利用 html2text 打通了 HTML 到 Markdown 的任督二脉,最终实现了一个具有实际生产价值的博客迁移工具。

下一步

  • 试试掘金/知乎 :这些现代前端渲染的站点(Vue/React)用 requests 很难拿到数据。这时候,就是你学习 SeleniumPlaywright 的最佳时机!
  • 自动化部署:结合 GitHub Actions,把这个脚本设为每周运行一次,自动抓取新文章并 Push 到你的 GitHub 仓库,实现全自动备份。

数据是新时代的石油,而爬虫就是你的钻井机。保护好你的知识资产!

🌟 文末

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

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

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

专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:

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

📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集

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

评论区留言告诉我你的需求,我会优先安排更新 ✅


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

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

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


免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。技术无罪,责任在人!!!

相关推荐
OLOLOadsd1232 小时前
柑橘类水果病害识别与分级_faster-rcnn_hrnetv2p-w32-1x_coco实现
python
10岁的博客2 小时前
Libvio.link爬虫技术全解析
爬虫
我什么都学不会4 小时前
Python练习作业3
开发语言·python
板面华仔4 小时前
机器学习入门(二)——逻辑回归 (Logistic Regression)
python·机器学习
Hgfdsaqwr4 小时前
使用Flask快速搭建轻量级Web应用
jvm·数据库·python
GHZhao_GIS_RS4 小时前
python中的sort和sorted用法汇总
python·排序·列表
永恒的溪流4 小时前
环境出问题,再修改
pytorch·python·深度学习
ruxshui4 小时前
Python多线程环境下连接对象的线程安全管理规范
开发语言·数据库·python·sql