Typecho文章批量导出至Astro Markdown工具
引言
Typecho 作为一款轻量级的博客系统,曾经是许多博主的选择。然而,随着技术的不断发展,Typecho 已经很久没有更新了。今年我发现了 Astro 这个现代化静态站点生成框架,同时被 Firefly 主题 这个非常符合我审美的主题所吸引,于是毅然决定将博客迁移到 Astro。
为了方便 Typecho 用户迁移导出 Markdown 文档,并适配 Astro 的 Firefly 主题而制作的定制化的导出工具。它不仅能将 Typecho 的文章导出为标准的 Markdown 格式,还能自动生成符合 Firefly 主题要求的 Frontmatter 配置。
功能介绍
本工具提供了以下核心功能:
- 按分类目录导出:文章将按照分类自动组织到对应的子目录中
- 符合 Astro 5.0 标准:生成标准的 YAML Frontmatter,包含所有必要字段
- 智能图片处理:
- 自动将引用式图片转换为直接链接格式
- 清理 Typecho 图片 URL 中的冗余参数(如
#vwid) - 移除所有图片引用定义行
- 自动摘要生成:从文章内容中智能提取前100字作为描述
- 灵活的封面图片配置:支持使用 Firefly 主题内置的随机二次元图片 API,或自定义图片 URL
- 优化日期格式:Frontmatter 中的日期格式简化为
YYYY-MM-DD,更易阅读 - 安全文件名处理:自动清理文件名中的非法字符,确保文件系统兼容性
使用方法
⚠️ ⚠️⚠️特别提醒
使用本工具请务必预先全量备份服务器的网站数据(数据库+网站文件),本工具不处理博客服务器中的静态图片资源,仅导出文章和适配生成 Frontmatter 属性。
也因此,图片资源存储在网站服务器中的使用者请务必斟酌再三,这部分使用者后期需要打包下载服务器的图片文件导入到 Astro,然后批处理 md 文档中的图片引用地址改成相对地址才能正常显示图片哦。
第一步:准备导出脚本
在你的 Typecho 网站根目录创建一个 converter.php 文件,将以下完整代码复制到文件中:
<?php/** * Typecho 文章导出工具 (适配 Astro 5.0 + Firefly 主题) * * 功能特性: * 1. 按分类目录导出文章到对应的子目录 * 2. 构建符合 Astro 5.0 标准的 YAML Frontmatter * 3. 自动处理引用式图片,转换为直接链接格式 * 4. 清理图片 URL 中的冗余参数 * 5. 生成文章摘要(自动截取前100字) * 6. 支持自定义文章封面图片配置 * * 使用前请先配置数据库连接信息和导出选项 * 运行后会在指定目录生成按分类组织的 Markdown 文件 */
// 1. 基础配置error_reporting(E_ALL);ini_set('display_errors', 1);
$dbConfig = [ 'host' => 'localhost', // 数据库主机地址 'name' => '数据库名', // 数据库名称 'user' => '数据库用户名', // 数据库用户名 'pass' => '数据库密码', // 数据库密码 'baseDir' => 'astro_posts', // 导出文件存放的目录 'image' => 'api' // 文章封面图片配置:默认值为 'api',将使用 Firefly 主题内置的随机二次元图片 API // 可自定义为其他图片 URL,例如:'https://example.com/image.jpg' // 或者保留为空字符串 '' 则不生成 image 属性];
try { // 创建数据库连接 $dsn = "mysql:host={$dbConfig['host']};dbname={$dbConfig['name']};charset=utf8mb4"; $pdo = new PDO($dsn, $dbConfig['user'], $dbConfig['pass'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]); echo "✅ 数据库连接成功\n";
// 2. 执行 SQL 查询 $sql = " SELECT c.cid, c.title, c.text, c.slug, c.created, c.modified, (SELECT GROUP_CONCAT(m.name) FROM typecho_relationships r JOIN typecho_metas m ON r.mid = m.mid WHERE r.cid = c.cid AND m.type = 'tag') as tags, (SELECT m.name FROM typecho_relationships r JOIN typecho_metas m ON r.mid = m.mid WHERE r.cid = c.cid AND m.type = 'category' LIMIT 1) as category FROM typecho_contents c WHERE c.type = 'post' AND c.status = 'publish' ORDER BY c.created DESC ";
// 执行查询获取所有已发布的文章 $stmt = $pdo->query($sql); $posts = $stmt->fetchAll();
if (!$posts) die("❌ 没有找到文章\n");
echo "📂 开始导出 " . count($posts) . " 篇文章...\n";
foreach ($posts as $index => $post) { // 3. 处理分类目录:根据文章分类创建对应的导出目录 $categoryName = !empty($post['category']) ? $post['category'] : 'Uncategorized'; $targetFolder = $dbConfig['baseDir'] . DIRECTORY_SEPARATOR . sanitize_filename($categoryName); if (!is_dir($targetFolder)) { mkdir($targetFolder, 0755, true); }
// 4. 处理文件名:移除非法字符,确保文件名安全 $fileName = sanitize_filename($post['title']); $exportPath = $targetFolder . DIRECTORY_SEPARATOR . $fileName . ".md"; // Windows 系统需要转换编码 if (strpos(PHP_OS, "WIN") !== false) { $exportPath = iconv("UTF-8", "GBK//IGNORE", $exportPath); }
// 5. 处理文章内容:移除 Typecho 的 Markdown 注释标记 $rawContent = $post['text']; $content = str_replace('<!--markdown-->', '', $rawContent);
// 6. 生成文章摘要:从内容中提取前100字作为描述 // 6.1 清理文本:移除所有 HTML 和 Markdown 标记 $cleanText = strip_tags($content); // 去除 HTML 标签 $cleanText = preg_replace('/!\[.*?\]\(.*?\)/s', '', $cleanText); // 去除图片标记 $cleanText = preg_replace('/!\[.*?\]\[.*?\]/s', '', $cleanText); // 去除引用式图片标记 $cleanText = preg_replace('/\[(.*?)\]\(.*?\)/s', '$1', $cleanText); // 去除链接标记但保留文字 $cleanText = preg_replace('/#+\s*/', '', $cleanText); // 去除标题标记 $cleanText = preg_replace('/\s+/', ' ', $cleanText); // 合并多余空格 $cleanText = trim($cleanText);
// 6.2 截取前100字作为摘要 $description = mb_substr($cleanText, 0, 100, 'UTF-8'); if (mb_strlen($cleanText, 'UTF-8') > 100) { $description .= '...'; }
// 7. 准备 Frontmatter(符合 Astro 5.0 标准) $pubDate = date('Y-m-d', $post['created']); // 发布日期,格式:YYYY-MM-DD $upDate = date('Y-m-d', $post['modified']); // 更新日期,格式:YYYY-MM-DD $tags = $post['tags'] ? explode(',', $post['tags']) : []; // 标签字符串转数组 $tags = array_filter(array_map('trim', $tags)); // 清理标签中的空格
$yaml = "---\n"; $yaml .= "title: " . json_encode($post['title'], JSON_UNESCAPED_UNICODE) . "\n"; $yaml .= "published: $pubDate\n"; $yaml .= "updated: $upDate\n"; $yaml .= "author: \"MoeWah\"\n"; // 生成 image 属性,如果配置不为空则添加 if (!empty($dbConfig['image'])) { $yaml .= "image: " . json_encode($dbConfig['image'], JSON_UNESCAPED_UNICODE) . "\n"; } $yaml .= "description: " . json_encode($description, JSON_UNESCAPED_UNICODE) . "\n"; $yaml .= "category: " . json_encode($categoryName, JSON_UNESCAPED_UNICODE) . "\n";
// 7.2 处理标签:生成正确的 YAML 数组格式 if (count($tags) > 0) { $yaml .= "tags:\n - " . implode("\n - ", array_map(fn($t) => json_encode($t, JSON_UNESCAPED_UNICODE), $tags)) . "\n"; } else { $yaml .= "tags: []\n"; // 空数组,避免生成 `- []` 的错误格式 }
$yaml .= "slug: " . json_encode($post['slug'], JSON_UNESCAPED_UNICODE) . "\n"; $yaml .= "draft: false\n"; $yaml .= "toc: true\n"; $yaml .= "---\n\n";
// 8. 处理正文内容:将所有引用式图片转换为直接链接格式 // 8.1 清理直接链接图片中的 #vwid 后缀参数(Typecho 特有的图片参数) $content = preg_replace('/(https?:\/\/[^\s\)]+?)(#vwid=[^?\s\)]+)/i', '$1', $content);
// 8.2 收集所有图片引用定义(Markdown 引用格式:[id]: URL) $imageRefs = []; if (preg_match_all('/^\[([^\]]+)\]:\s*(https?:\/\/[^\s]+)/im', $content, $refMatches, PREG_SET_ORDER)) { foreach ($refMatches as $match) { $refId = $match[1]; $imageUrl = $match[2]; // 清理图片URL后缀参数 $cleanUrl = preg_replace('/(#vwid=[^?\s\)]+)/i', '', $imageUrl); $imageRefs[$refId] = $cleanUrl; } }
// 8.3 将引用式图片标记转换为直接链接格式 // 处理格式:![描述][id]、![][id]、![id] foreach ($imageRefs as $refId => $imageUrl) { // 匹配格式 ![描述][id] 或 ![][id] $pattern = '/!\[(.*?)\]\[\s*' . preg_quote($refId, '/') . '\s*\]/'; $content = preg_replace_callback($pattern, function($matches) use ($imageUrl) { $altText = $matches[1]; return ""; }, $content);
// 匹配格式 ![id] 这种简写形式 $pattern2 = '/!\[\s*' . preg_quote($refId, '/') . '\s*\]/'; $content = preg_replace($pattern2, "", $content); }
// 8.4 移除所有图片引用定义行(已经转换为直接链接,不再需要引用定义) $content = preg_replace('/\[[^\]]+\]:\s*https?:\/\/.*$/m', '', $content);
// 8.5 清理多余的空行(三个以上连续空行合并为两个空行) $content = preg_replace('/\n\s*\n\s*\n/', "\n\n", $content); $content = trim($content);
// 9. 合并 Frontmatter 和正文内容 $fullOutput = $yaml . $content;
// 10. 写入文件到指定路径 if (file_put_contents($exportPath, $fullOutput)) { echo "[" . ($index + 1) . "] 已导出: {$post['title']}\n"; } }
echo "\n🎉 导出完成!已将引用式图片转换为直接链接格式。\n"; echo "📁 文件保存在目录: " . $dbConfig['baseDir'] . "\n";
} catch (Exception $e) { die("❌ 错误: " . $e->getMessage() . "\n");}
/** * 文件名清理函数 * * @param string $filename 原始文件名 * @return string 清理后的安全文件名 * * 功能: * 1. 替换非法字符为连字符 * 2. 限制文件名长度不超过80个字符 * 3. 去除文件名两端的点和连字符 */function sanitize_filename($filename) { // 定义需要替换的非法字符 $invalid = array(" ", "?", "\\", "/", ":", "|", "*", "\"", "<", ">"); $filename = str_replace($invalid, '-', $filename);
// 限制文件名长度,避免过长的文件名 $filename = mb_substr($filename, 0, 80, 'utf-8');
// 去除文件名两端的点和连字符(避免隐藏文件或非法文件) return trim($filename, '.-');}?>第二步:配置数据库连接
用文本编辑器打开 converter.php,找到以下配置部分,将数据库信息更改为你自己的 Typecho 数据库信息:
$dbConfig = [ 'host' => 'localhost', // 数据库主机地址(通常为 localhost) 'name' => '数据库名', // Typecho 数据库名称 'user' => '数据库用户名', // 数据库用户名 'pass' => '数据库密码', // 数据库密码 'baseDir' => 'astro_posts', // 导出文件存放的目录(可修改) 'image' => 'api' // 封面图片配置(默认使用 Firefly 主题 API)];封面图片配置说明:
'api':默认值,使用 Firefly 主题内置的随机二次元图片 API'https://example.com/image.jpg':自定义静态图片 URL'':留空则不生成 image 属性
第三步:运行导出脚本
打开你的服务器终端,进入到 Typecho 网站根目录,执行以下命令:
php converter.php脚本将自动连接数据库,导出所有已发布的文章,并显示导出进度:
✅ 数据库连接成功📂 开始导出 23 篇文章...[1] 已导出: 我的第一篇文章[2] 已导出: 技术分享:PHP 开发技巧...🎉 导出完成!已将引用式图片转换为直接链接格式。📁 文件保存在目录: astro_posts第四步:获取导出的文件
导出的 Markdown 文件将按照分类组织在 astro_posts/ 目录下:
astro_posts/├── 技术文章/│ ├── 我的第一篇文章.md│ └── 技术分享-PHP-开发技巧.md├── 生活随笔/│ └── 周末游记.md└── Uncategorized/ └── 未分类文章.md每个 Markdown 文件都包含完整的 Frontmatter 和文章内容,可以直接用于 Astro + Firefly 主题。
导出的 Frontmatter 示例
以下是导出的 Markdown 文件中的 Frontmatter 示例:
---title: "我的第一篇文章"published: 2019-06-19updated: 2021-04-30author: "MoeWah"image: "api"description: "这篇文章是关于 Typecho 导出工具的详细介绍..."category: Astro教程tags: - "PHP" - "Typecho" - "博客迁移"slug: "my-first-post"draft: falsetoc: true---结语
这个导出工具是我从 Typecho 迁移到 Astro + Firefly 过程中的产物,希望它也能帮助到其他有类似需求的博主。工具已经优化了 Frontmatter 格式,处理了图片引用转换,并提供了灵活的配置选项。
最后,祝大家在博客迁移过程中一切顺利,享受 “Astro 框架 + Firefly 主题”带来的现代化博客体验!
注意事项:
- 请确保在运行脚本前备份你的 Typecho 数据库
- 脚本只会导出已发布的文章(
status = 'publish') - 如果文章数量很多,导出过程可能需要一些时间
- 导出的 Markdown 文件可以直接复制到 Astro 项目的
src/content/posts/目录中使用
推荐文章
基于标签匹配 · 智能推荐支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!
喵斯基部落