python脚本批量导出ZEEKLOG里的文章

python脚本批量导出ZEEKLOG里的文章

一 导出全部已发布文章

首先,需要在本地安装3.8版本以上的python,安装python步骤

检查是否安装成功

pip3 --version 

安装后执行

pip3 install requests beautifulsoup4 markdownify 

新建脚本,脚本名字随意,这里是:ZEEKLOG_downloader.py

脚本内容如下:

# -*- coding: utf-8 -*-import os import re import requests import time from bs4 import BeautifulSoup from markdownify import markdownify as md from urllib.parse import urlparse, unquote import hashlib from pathlib import Path # ================== 配置区 ================== ZEEKLOG_USERNAME ="qq_33417321"# ←←← 修改为你想下载的用户名 SAVE_DIR = Path("ZEEKLOG_articles")# 文章保存根目录(自动跨平台) HEADERS ={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36","Referer":"https://blog.ZEEKLOG.net/"}defsanitize_filename(name:str)->str:"""清理文件名,移除 Windows 非法字符和‘原创’字样""" name = name.replace("原创","").strip()# 移除 Windows 非法字符 name = re.sub(r'[\\/*?:"<>|\r\n]',"_", name)return name or"untitled"defget_article_list(username):"""获取博主文章列表(标题和URL)""" url =f"https://blog.ZEEKLOG.net/{username}/article/list" articles =[] page =1whileTrue: response = requests.get(f"{url}/{page}", headers=HEADERS) soup = BeautifulSoup(response.text,'html.parser') items = soup.select(".article-list .article-item-box")ifnot items:breakfor item in items: title_elem = item.select_one("h4 a")ifnot title_elem:continue title = title_elem.text.strip() link = title_elem["href"] articles.append({"title": title,"url": link}) page +=1 time.sleep(1)return articles defdownload_image(img_url, save_path: Path):"""下载单张图片到本地"""try: img_headers = HEADERS.copy() img_headers["Accept"]="image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" response = requests.get(img_url, headers=img_headers, stream=True, timeout=30)if response.status_code ==200: save_path.parent.mkdir(parents=True, exist_ok=True)withopen(save_path,'wb')as f:for chunk in response.iter_content(chunk_size=8192):if chunk: f.write(chunk)returnTrueelse:print(f"图片下载失败(状态码:{response.status_code}):{img_url}")returnFalseexcept Exception as e:print(f"图片下载异常:{img_url},错误:{str(e)}")returnFalsedefget_image_extension(img_url):"""从URL中获取图片扩展名""" parsed_url = urlparse(img_url) path = parsed_url.path.lower() extensions =['.jpg','.jpeg','.png','.gif','.webp','.bmp','.svg']for ext in extensions:if ext in path:return ext return'.jpg'defprocess_images_in_content(content, article_title):"""处理内容中的图片,下载并替换为本地路径""" soup = BeautifulSoup(content,'html.parser') img_tags = soup.find_all('img')ifnot img_tags:return content # 清理文章标题用于路径 safe_title = sanitize_filename(article_title) global_image_dir = SAVE_DIR /"images" article_image_dir = global_image_dir / safe_title for img in img_tags: img_url = img.get('src','')ifnot img_url:continue# 处理协议相对路径if img_url.startswith('//'): img_url ='https:'+ img_url elifnot img_url.startswith(('http://','https://')):continue# 跳过无法处理的相对路径try: img_hash = hashlib.md5(img_url.encode()).hexdigest()[:8] img_ext = get_image_extension(img_url) img_filename =f"{img_hash}{img_ext}" local_img_path = article_image_dir / img_filename # Markdown 中使用正斜杠(/),兼容所有平台 md_img_path =f"./images/{safe_title}/{img_filename}"ifnot local_img_path.exists():print(f" 下载图片:{img_filename}")if download_image(img_url, local_img_path): img['src']= md_img_path else:print(f" 图片下载失败,保留原链接:{img_url}")else: img['src']= md_img_path except Exception as e:print(f" 处理图片时出错:{img_url},错误:{str(e)}")continuereturnstr(soup)defdownload_article(url, article_title):"""下载单篇文章,处理图片后转为Markdown"""try: response = requests.get(url, headers=HEADERS, timeout=30) soup = BeautifulSoup(response.text,'html.parser') content = soup.select_one("article")ifnot content:print(f" 未找到文章内容")returnNone processed_content = process_images_in_content(str(content), article_title) markdown_content = md(processed_content)return markdown_content except Exception as e:print(f" 下载文章时出错:{str(e)}")returnNonedefsave_to_markdown(title, content, save_dir: Path):"""保存Markdown文件""" save_dir.mkdir(parents=True, exist_ok=True) safe_title = sanitize_filename(title) filename = save_dir /f"{safe_title}.md"withopen(filename,"w", encoding="utf-8")as f: f.write(f"# {title}\n\n") f.write(content)print(f" 已保存:{filename}")return filename if __name__ =="__main__":print("开始获取文章列表...") articles = get_article_list(ZEEKLOG_USERNAME)print(f"找到 {len(articles)} 篇文章") success_count =0 fail_count =0for i, article inenumerate(articles,1): title = article["title"] url = article["url"]print(f"\n[{i}/{len(articles)}] 处理文章:{title}") content = download_article(url, title)if content: save_to_markdown(title, content, SAVE_DIR) success_count +=1else:print(f" 文章下载失败:{title}") fail_count +=1 time.sleep(2)print(f"\n处理完成!成功:{success_count}篇,失败:{fail_count}篇")print(f"文章保存在:{SAVE_DIR.resolve()}")print("图片保存在:./images/ 目录下,Markdown文件可离线查看")

其中,脚本里ZEEKLOG_USERNAME的值,改为你要获取的ZEEKLOG的用户名

获取用户名:点击作者头像后,链接里的这个值就是用户名(红框里的内容)

在这里插入图片描述

执行脚本

python ZEEKLOG_downloader.py 

执行日志入下:

在这里插入图片描述

由于要下载ZEEKLOG文章里的图片,所以很慢,静静等待即可。下载过程中,会在脚本所在目录生成一个ZEEKLOG_articles文件夹,里边是md文件以及存md里的图片的文件夹。

在这里插入图片描述

二 导出指定日期后的文章

上边的脚本,一次性导出了所有已发布的文章,但是有时候我们的文章太多,每次备份不需要全部导出,只导出指定时间以后的文章,那么使用如下脚本即可,如脚本名为new.py,内容如下

# -*- coding: utf-8 -*-import os import re import requests import time from bs4 import BeautifulSoup from markdownify import markdownify as md from urllib.parse import urlparse, unquote import hashlib from pathlib import Path from datetime import datetime # ================== 配置区 ================== ZEEKLOG_USERNAME ="qq_33417321"# ←←← 修改为你想下载的用户名 SAVE_DIR = Path("ZEEKLOG_articles")# 文章保存根目录(自动跨平台)# ⬇️ 新增:设置最小发布日期(含) MIN_PUBLISH_DATE = datetime(2026,2,7)# 只下载 2026年2月1日及之后的文章 HEADERS ={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36","Referer":"https://blog.ZEEKLOG.net/"}defsanitize_filename(name:str)->str:"""清理文件名,移除 Windows 非法字符和‘原创’字样""" name = name.replace("原创","").strip() name = re.sub(r'[\\/*?:"<>|\r\n]',"_", name)return name or"untitled"defparse_publish_date(date_str:str)-> datetime |None:"""尝试解析 ZEEKLOG 日期字符串,支持 '2025-06-15 10:30:00' 或 '2025-06-15' 等""" date_str = date_str.strip()for fmt in["%Y-%m-%d %H:%M:%S",# 带秒,如 2025-06-15 10:30:09"%Y-%m-%d %H:%M",# 不带秒,如 2025-06-15 10:30"%Y-%m-%d"# 只有日期,如 2025-06-15]:try:return datetime.strptime(date_str, fmt)except ValueError:continueprint(f" 无法解析日期:{date_str}")returnNonedefget_article_list(username, min_date=None):"""获取博主文章列表(标题、URL、发布时间),可选按 min_date 过滤""" url =f"https://blog.ZEEKLOG.net/{username}/article/list" articles =[] page =1 early_stop =Falsewhilenot early_stop:print(f" 正在抓取第 {page} 页...") response = requests.get(f"{url}/{page}", headers=HEADERS) soup = BeautifulSoup(response.text,'html.parser') items = soup.select(".article-list .article-item-box")ifnot items:break current_page_has_valid =False# 当前页是否有满足条件的文章for item in items: title_elem = item.select_one("h4 a") date_elem = item.select_one(".date")# ZEEKLOG 通常用 .date 类表示发布时间ifnot title_elem:continue title = title_elem.text.strip() link = title_elem["href"] pub_date =Noneif date_elem: raw_date = date_elem.text.strip() pub_date = parse_publish_date(raw_date)# 如果设置了最小日期,且文章发布时间早于该日期,则跳过if min_date and pub_date and pub_date < min_date:continue# 如果发布时间未知但设置了 min_date,保守起见也跳过(或可选择保留)if min_date andnot pub_date:print(f" 警告:无法获取文章 [{title}] 的发布时间,跳过(因设置了日期过滤)")continue articles.append({"title": title,"url": link,"publish_date": pub_date }) current_page_has_valid =True# 如果当前页没有任何有效文章(全部早于 min_date),可提前终止if min_date andnot current_page_has_valid and page >1:print(" 后续页面文章均早于指定日期,停止翻页。") early_stop =True page +=1 time.sleep(1)return articles # ========== 以下函数保持不变 ==========defdownload_image(img_url, save_path: Path):try: img_headers = HEADERS.copy() img_headers["Accept"]="image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" response = requests.get(img_url, headers=img_headers, stream=True, timeout=30)if response.status_code ==200: save_path.parent.mkdir(parents=True, exist_ok=True)withopen(save_path,'wb')as f:for chunk in response.iter_content(chunk_size=8192):if chunk: f.write(chunk)returnTrueelse:print(f"图片下载失败(状态码:{response.status_code}):{img_url}")returnFalseexcept Exception as e:print(f"图片下载异常:{img_url},错误:{str(e)}")returnFalsedefget_image_extension(img_url): parsed_url = urlparse(img_url) path = parsed_url.path.lower() extensions =['.jpg','.jpeg','.png','.gif','.webp','.bmp','.svg']for ext in extensions:if ext in path:return ext return'.jpg'defprocess_images_in_content(content, article_title): soup = BeautifulSoup(content,'html.parser') img_tags = soup.find_all('img')ifnot img_tags:return content safe_title = sanitize_filename(article_title) global_image_dir = SAVE_DIR /"images" article_image_dir = global_image_dir / safe_title for img in img_tags: img_url = img.get('src','')ifnot img_url:continueif img_url.startswith('//'): img_url ='https:'+ img_url elifnot img_url.startswith(('http://','https://')):continuetry: img_hash = hashlib.md5(img_url.encode()).hexdigest()[:8] img_ext = get_image_extension(img_url) img_filename =f"{img_hash}{img_ext}" local_img_path = article_image_dir / img_filename md_img_path =f"./images/{safe_title}/{img_filename}"ifnot local_img_path.exists():print(f" 下载图片:{img_filename}")if download_image(img_url, local_img_path): img['src']= md_img_path else:print(f" 图片下载失败,保留原链接:{img_url}")else: img['src']= md_img_path except Exception as e:print(f" 处理图片时出错:{img_url},错误:{str(e)}")continuereturnstr(soup)defdownload_article(url, article_title):try: response = requests.get(url, headers=HEADERS, timeout=30) soup = BeautifulSoup(response.text,'html.parser') content = soup.select_one("article")ifnot content:print(f" 未找到文章内容")returnNone processed_content = process_images_in_content(str(content), article_title) markdown_content = md(processed_content)return markdown_content except Exception as e:print(f" 下载文章时出错:{str(e)}")returnNonedefsave_to_markdown(title, content, save_dir: Path): save_dir.mkdir(parents=True, exist_ok=True) safe_title = sanitize_filename(title) filename = save_dir /f"{safe_title}.md"withopen(filename,"w", encoding="utf-8")as f: f.write(f"# {title}\n\n") f.write(content)print(f" 已保存:{filename}")return filename # ========== 主程序入口 ==========if __name__ =="__main__":print("开始获取文章列表...") articles = get_article_list(ZEEKLOG_USERNAME, min_date=MIN_PUBLISH_DATE)print(f"找到 {len(articles)} 篇符合条件的文章(发布日期 ≥ {MIN_PUBLISH_DATE.strftime('%Y-%m-%d')})") success_count =0 fail_count =0for i, article inenumerate(articles,1): title = article["title"] url = article["url"] pub_date = article.get("publish_date") date_str = pub_date.strftime("%Y-%m-%d")if pub_date else"未知"print(f"\n[{i}/{len(articles)}] 处理文章:{title} (发布于 {date_str})") content = download_article(url, title)if content: save_to_markdown(title, content, SAVE_DIR) success_count +=1else:print(f" 文章下载失败:{title}") fail_count +=1 time.sleep(2)print(f"\n处理完成!成功:{success_count}篇,失败:{fail_count}篇")print(f"文章保存在:{SAVE_DIR.resolve()}")print("图片保存在:./images/ 目录下,Markdown文件可离线查看")

记得修改MIN_PUBLISH_DATE 里的开始日期。之后执行脚本后,下载下来就是指定日期后的文章了。

Read more

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

摘要:本文聚焦OpenClaw从测试环境走向生产环境的核心痛点,围绕“性能优化、安全加固、监控运维”三大维度展开实操讲解。先明确生产环境硬件/系统选型标准,再通过硬件层资源管控、模型调度策略、缓存优化等手段提升响应速度(实测响应效率提升50%+);接着从网络、权限、数据三层构建安全防护体系,集成火山引擎安全方案拦截高危操作;最后落地TenacitOS可视化监控与Prometheus告警体系,配套完整故障排查清单和虚拟实战案例。全文所有配置、代码均经实测验证,兼顾新手入门实操性和进阶读者的生产级部署需求,帮助开发者真正实现OpenClaw从“能用”到“放心用”的跨越。 优质专栏欢迎订阅! 【DeepSeek深度应用】【Python高阶开发:AI自动化与数据工程实战】【YOLOv11工业级实战】 【机器视觉:C# + HALCON】【大模型微调实战:平民级微调技术全解】 【人工智能之深度学习】【AI 赋能:Python 人工智能应用实战】【数字孪生与仿真技术实战指南】 【AI工程化落地与YOLOv8/v9实战】【C#工业上位机高级应用:高并发通信+性能优化】 【Java生产级避坑指南:

By Ne0inhk
ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

🎬 渡水无言:个人主页渡水无言 ❄专栏传送门: 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》 ❄专栏传送门: 《freertos专栏》《STM32 HAL库专栏》 ⭐️流水不争先,争的是滔滔不绝  📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生 | 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生 在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连 目录 前言  一、实验基础说明 1.1、互斥体简介 1.2 本次实验设计思路 二、硬件原理分析(看过之前博客的可以忽略) 三、实验程序编写 3.1 互斥体 LED 驱动代码(mutex.c) 3.2.1、设备结构体定义(28-39

By Ne0inhk
Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 后端工程师扔给你一个 Swagger (OpenAPI) 文档地址,你会怎么做? 1. 对着文档,手写 Dart Model 类(容易写错字段类型)。 2. 手写 Retrofit/Dio 的 API 接口定义(容易拼错 URL)。 3. 当后端修改了字段名,你对着报错修半天。 这是重复劳动的地狱。 swagger_dart_code_generator 可以将 Swagger (JSON/YAML) 文件直接转换为高质量的 Dart 代码,包括: * Model 类:支持 json_serializable,带 fromJson/

By Ne0inhk
Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

文章目录 * 前言 * make/makefile * 文件的三个时间 * Linux第一个小程序-进度条 * 回车和换行 * 缓冲区 * 程序的代码展示 * git指令 * 关于gitee * Linux调试器-gdb使用 * 作业部分 前言 做 Linux 开发时,你是不是也遇到过这些 “卡脖子” 时刻?写 makefile 时,明明语法没错却报错,最后发现是依赖方法行没加 Tab;想提交代码到 gitee,记不清 git add/commit/push 的 “三板斧”,还得反复搜教程;用 gdb 调试程序,输了命令没反应,才想起编译时没加-g生成 debug 版本;甚至连写个进度条,都搞不懂\r和\n的区别,导致进度条乱跳…… 其实这些问题,

By Ne0inhk