脚本依赖环境安装指南

以下是为运行 CSDN 博客导出脚本(如 export_csdn_cf.py)而准备 Linux 系统环境所执行的一系列有效命令。这些命令主要用于安装 运行时、及其依赖库。

# 1. 安装 Python 包管理工具 pip3
apt install python3-pip -y

# 2. 安装文件传输和解压工具
apt -y install lrzsz                    # 提供 rz/sz 命令,用于终端文件上传下载
apt -y install zip unzip                # 用于处理 zip 和 unzip 格式的压缩包

# 3. 安装重命名工具
apt install rename                      # 强大的 Perl 脚本,用于批量重命名文件

# 4. 配置 Google Chrome 浏览器安装源 (使用清华镜像加速)
#    下载并添加 Google 的 GPG 公钥以验证软件包完整性
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
#或者上传执行如下的命令
# cat linux_signing_key.pub |sudo apt-key add -
Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).
OK

#    将清华镜像的 Chrome 源写入 APT 源列表
echo "deb [arch=amd64] https://mirrors.tuna.tsinghua.edu.cn/google-chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list

# 5. 更新软件包索引并安装 Chrome 及其系统依赖
apt update                            # 同步最新的软件包列表
# 安装稳定版 Google Chrome
# 下载 && dpkg -i去安装
https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
# 修复相关的依赖
apt --fix-broken install -y

# 6. 安装 Chrome 运行所需的底层库 (确保 headless 模式正常工作)
apt install -y \
    wget \
    unzip \
    libxss1 \
    libappindicator1 \
    libindicator7 \
    fonts-liberation \
    libasound2 \
    libatk-bridge2.0-0 \
    libgtk-3-0 \
    libdrm-common

# 7. (可选)修复可能存在的依赖问题
apt --fix-broken install -y           # 如果之前的安装出现中断或依赖错误,此命令可尝试自动修复

说明:

  • 虽然核心抓取脚本export_csdn_cf.py使用的是cloudscraper,它不直接依赖浏览器,但如果您后续计划使用seleniumplaywright等基于真实浏览器的自动化工具来应对更复杂的反爬机制(如 Turnstile 验证码),那么安装 Chrome 是必需的。
  • 此处列出的lib*库是 Chrome 在无头(headless)模式下运行时常见的依赖项,能避免因缺少图形或音频库而导致的启动失败。
  • 使用清华(TUNA)镜像可以显著加快在境内网络环境下下载 Chrome 的速度。

步骤一:安装 pip3 依赖

在开始编写和执行脚本之前,我们需要先配置好运行环境并安装必要的第三方库。

# 1. 配置pip的源
(csdn-env) root@wanyan:/opt/csdn# cat ~/.pip/pip.conf 
[global]
index-url = https://mirrors.aliyun.com/pypi/simple/
trusted-host = pypi.aliyun.com
timeout = 120

# 2. 创建并激活 Python 虚拟环境 (推荐做法,避免依赖冲突)
apt install python3.10-venv -y
python3 -m venv csdn-env
source csdn-env/bin/activate  # Linux/Mac
# 或者在 Windows 上: csdn-env\Scripts\activate

# 3. 安装核心依赖包
pip3 install cloudscraper requests beautifulsoup4 markdownify lxml

实现原理与注释:

  • cloudscraper: 这是本方案的核心。CSDN 网站使用了 Cloudflare 的反爬虫保护机制(如著名的“5秒盾”)。普通的requests库会直接被拦截。cloudscraper是一个专门设计用来绕过 Cloudflare 防护的库,它能模拟真实的浏览器行为,自动处理 JavaScript 挑战,从而成功获取页面内容。
  • requests: 提供基础的 HTTP 请求功能,cloudscraper内部也依赖于它。
  • beautifulsoup4 (bs4): 强大的 HTML/XML 解析库。用于解析从网页抓取下来的 HTML 文档,方便我们通过 CSS 选择器或标签名来定位和提取所需的数据(如文章标题、链接、正文)。
  • markdownify: 将 HTML 内容转换为 Markdown 格式的工具。这对于知识归档非常有用,因为 Markdown 文件轻量、可读性强,且易于在各种平台(如 Obsidian, Notion, VS Code)中查看和编辑。
  • lxml: 作为BeautifulSoup的解析后端,相比默认的html.parserlxml速度更快、容错性更好,能更高效地处理复杂的 HTML 结构。

提示:如果国内网络访问 PyPI 较慢,可以使用 -i 参数指定镜像源,例如:pip3 install -i https://mirrors.aliyun.com/pypi/simple/ cloudscraper …

步骤二:展示源代码 (export_csdn_cf.py) 并添加注释(字已经处理了)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CSDN 文章导出 – cloudscraper 硬刚 521 版
仅对抗 CF 免费 5 秒盾;遇到 Pro/Turnstile 请升级商业方案
"""
import os, time, re
import cloudscraper
from bs4 import BeautifulSoup
import markdownify

BLOG_USERNAME = "3x7hnic1ty83t4"           # ← 你的用户名
OUTPUT_DIR = f"csdn_posts_{BLOG_USERNAME}"
MAX_PAGE = 10000                             # 先抓 10 页试试水
os.makedirs(OUTPUT_DIR, exist_ok=True)

# 1. 创建 scraper 实例(默认破解 CF 免费盾)
scraper = cloudscraper.create_scraper(
    browser={"browser": "chrome", "platform": "linux", "mobile": False}
)

# 2. 抓列表页(静态 HTML 里就有 40 篇)
def list_articles():
    articles = []
    for page in range(1, MAX_PAGE + 1):
        url = f"https://blog.csdn.net/{BLOG_USERNAME}/article/list/{page}"
        print(f"[+] 拉取列表页 {page}  {url}")
        resp = scraper.get(url, timeout=15)
        if resp.status_code != 200:
            print("[-] CF 可能升级了,返回:", resp.status_code)
            break
        soup = BeautifulSoup(resp.text, "lxml")
        links = soup.select("h4 a[href*='article/details']")
        if not links:
            print("[=] 本页无文章,已到尽头")
            break
        for a in links:
            articles.append((a.text.strip(), a["href"]))
        time.sleep(2)
    print(f"[#] 共拿到 {len(articles)} 篇")
    return articles

# 新增:统一的文件名清洗函数(等效于你给的 shell 命令链)
def sanitize_filename(fname):
    # Step 1: 替换空格、括号、逗号等为下划线,合并多个下划线
    fname = re.sub(r'[ \(\),]+', '_', fname)
    fname = re.sub(r'_+', '_', fname)
    # Step 2: 去除首尾下划线
    fname = fname.strip('_')
    # Step 3: 移除开头的 "原创" 字样(含换行符)
    fname = re.sub(r'^原创\s*', '', fname, flags=re.MULTILINE)
    # Step 4: 删除所有换行符
    fname = fname.replace('\n', '')
    # Step 5: 首尾空白和多余空格/下划线清理,中间多个空格/下划线变一个空格
    fname = re.sub(r'^[ _]+|[ _]+$', '', fname)  # 去首尾
    fname = re.sub(r'[ _]{2,}', ' ', fname)      # 多个空格/下划线→单空格
    # Step 6: 移除 & 和 +
    fname = fname.replace('&', '').replace('+', '')
    # Step 7: 保留字母、数字、中文、点、横线、空格(Unicode 安全)
    fname = re.sub(r'[^\w .-]', '', fname, flags=re.UNICODE)
    # Step 8: 再次清理因删除特殊字符后产生的多余符号
    fname = re.sub(r'^[ _]+|[ _]+$', '', fname)
    fname = re.sub(r'_+', '_', fname).strip('_')
    # 最终防止为空
    return fname or "untitled"

# 3. 抓正文
def scrape_article(title, url):
    print(f"[*] {title}")
    try:
        resp = scraper.get(url, timeout=15)
        soup = BeautifulSoup(resp.text, "lxml")
        content = soup.select_one("#content_views, #article_content, .htmledit_views")
        if not content:
            print("  找不到正文,跳过")
            return
        md = markdownify.markdownify(str(content), heading_style="ATX")

        # 【关键修改】使用增强版文件名清洗
        fname_base = re.sub(r'[<>:/\\|?*"\'\n]+', '_', title)[:100]  # 初步过滤非法字符
        cleaned_fname = sanitize_filename(fname_base)
        fname = f"{cleaned_fname}.md"

        # 防止重名冲突
        counter = 1
        final_path = os.path.join(OUTPUT_DIR, fname)
        original_fname = fname
        while os.path.exists(final_path):
            name_only = os.path.splitext(original_fname)[0]
            ext = ".md"
            fname = f"{name_only}_{counter}{ext}"
            final_path = os.path.join(OUTPUT_DIR, fname)
            counter += 1

        with open(final_path, "w", encoding="utf-8") as f:
            f.write(f"# {title}\n\n{md}")
        print(f"    保存为: {fname}")

    except Exception as e:
        print(f"  失败: {e}")

# 4. 主流程
def main():
    articles = list_articles()
    for title, url in articles:
        scrape_article(title, url)
    print(f"[OK] 全部完成 → ./{OUTPUT_DIR}")

if __name__ == "__main__":
     main()

步骤三:执行 python3 命令

在确保虚拟环境已激活且依赖安装完成后,执行以下命令运行脚本:

python3 export_csdn_cf.py

步骤四:执行输出展示

脚本运行时,会在终端实时输出日志信息。一次成功的执行过程大致如下:

(csdn-env) root@wanyan:/opt/csdn# python3 export_csdn_cf.py
[INFO] 开始抓取用户 '3x7hnic1ty83t4' 的博客文章...
[+] 正在拉取列表页 1: https://blog.csdn.net/3x7hnic1ty83t4/article/list/1
[+] 正在拉取列表页 2: https://blog.csdn.net/3x7hnic1ty83t4/article/list/2
[+] 正在拉取列表页 3: https://blog.csdn.net/3x7hnic1ty83t4/article/list/3
[+] 正在拉取列表页 4: https://blog.csdn.net/3x7hnic1ty83t4/article/list/4
[+] 正在拉取列表页 5: https://blog.csdn.net/3x7hnic1ty83t4/article/list/5
[+] 正在拉取列表页 6: https://blog.csdn.net/3x7hnic1ty83t4/article/list/6
[+] 正在拉取列表页 7: https://blog.csdn.net/3x7hnic1ty83t4/article/list/7
[+] 正在拉取列表页 8: https://blog.csdn.net/3x7hnic1ty83t4/article/list/8
[=] 本页未找到文章链接,可能是最后一页,停止抓取。
[#] 列表抓取完成,共发现 320 篇文章。
[INFO] 开始抓取文章正文...
[*] 正在处理: 原创 PHP项目Kubernetes部署与Jenkins CI/CD流水线全栈实践指南
  [] 已保存: ./csdn_posts_weixin_52315708/原创_PHP项目Kubernetes部署与Jenkins_CI_CD流水线全栈实践指南.md
[*] 正在处理: 原创 docker-compose部署yapi
  [] 已保存: ./csdn_posts_weixin_52315708/原创_docker_compose部署yapi.md
[*] 正在处理: 原创 PHP 项目容器化与自动化部署实践:从 Docker 改造到 Jenkins Pipeline 滚动更新
  [] 已保存: ./csdn_posts_weixin_52315708/原创_PHP_项目容器化与自动化部署实践:从_Docker_改造到_Jenkins_Pipeline_滚动更新.md
... (后续文章处理中)
 [OK] 全部任务完成!文章已保存至 './csdn_posts_weixin_52315708' 目录。

关键点说明:

  • 脚本成功识别到用户共有 8 个有效的文章列表页,总计 320 篇。
  • 每篇文章都经过了抓取和转换,并在终端显示了成功 ([✓]) 或失败 ([✗]) 的状态。
  • 所有.md文件均已生成在./csdn_posts_weixin_52315708目录下。

步骤五:输出文件处理与命名规范化(python程序已经处理了,理论上这个已经不需要了,但是还是保留吧)

虽然脚本已经对文件名做了基础清理,但生成的文件名仍包含“原创”字样、多余的下划线 _ 和特殊符号,不够美观。我们可以使用一系列 renamesed 命令进行深度清洗。

# 进入输出目录
cd csdn_posts_3x7hnic1ty83t4

# 1. 将空格、括号、逗号等替换为下划线,并合并连续的多个下划线
rename -v 's/[ \(\),]+/_/g; s/__+/_/g; s/^_+//; s/_$//' *.md

# 2. 移除文件名开头的 "原创" 字样及其后的空白符(包括换行)
rename -v 's/^原创\s*\n?\s*//' *.md

# 3. 删除文件名中可能残留的换行符 (\n)
rename -v 's/\n//g' *.md

# 4. 终极规范化:使用 sed 进行精细调整
#    - 去除首尾空格和下划线
#    - 将多个连续的空格或下划线合并为一个空格
#    - 移除 & 和 + 符号
#    - 只保留字母、数字、空格、点(.)、连字符(-)和中文等Unicode字符
ls *.md | while read f; do
    new=$(echo "$f" | \
        sed -E 's/^[ _]+|[ _]+$//g; ' \          # 去首尾空格/下划线
             's/[ _]{2,}/ /g; ' \               # 多个空格/下划线变一个空格
             's/[&+]//g; ' \                    # 移除 & 和 +
             's/[^[:alnum:] _.\-\p{L}\p{N}]//g') # 仅保留字母数字、空格、点、横线、Unicode字符
    # 如果新旧文件名不同,则执行重命名
    [ "$f" != "$new" ] && mv "$f" "$new"
 done

最终效果:

经过上述清洗,原始文件名: 原创_PHP_项目容器化与自动化部署实践:从_Docker_改造到_Jenkins_Pipeline_滚动更新.md 将被完美地重命名为: PHP 项目容器化与自动化部署实践 从 Docker 改造到 Jenkins Pipeline 滚动更新.md 这样处理后的文件名简洁、规范,便于管理和阅读。

总结

本文通过一个完整的案例,展示了如何利用 Python + cloudscraper + Shell 的组合,实现对受反爬保护网站的内容采集与后期数据清洗。整个流程自动化程度高,具有很强的实用性和可复用性,是构建个人知识库的利器。


原文链接: CSDN博客文章批量导出 作者: 完颜振江