# 保哥笔记 — Typecho教程 > 本分片含 9 篇文章,按发布日期倒序。全部分片索引见 https://zhangwenbao.com/llms-full.md **站点**:https://zhangwenbao.com/ **分类**:Typecho教程 **生成**:2026-06-04 23:09:29 CST --- ## DedeCMS怎么迁移到Typecho?批量脚本加SEO不掉量 - URL:https://zhangwenbao.com/dedecms-to-typecho.html - 分类:Typecho教程 - 发布:2025-10-13 | 更新:2026-06-01 - 摘要:织梦官方四年没补漏洞还要商业授权,弃用是必然。本文给出从评估、备份、批量迁移脚本到SEO保稳的完整路径,包含字符集转码、多作者映射、评论迁移、二级分类等八个真实踩坑。 - 关键词:DedeCMS批量处理,DedeCMS,Typecho,数据迁移,网站搬家 > **TLDR**:摘要:织梦官方四年没补漏洞还要商业授权,弃用是必然,迁到Typecho是个稳妥选择。本文给从评估、备份、批量迁移脚本到SEO保稳的完整路径和迁移工具的完整代码,覆盖字符集转码、多作者映射、评论迁移、二级分类等八个真实踩坑,再讲迁移后的验证和SEO持续优化。 > 摘要:织梦官方四年没补漏洞还要商业授权,弃用是必然,迁到Typecho是个稳妥选择。本文给从评估、备份、批量迁移脚本到SEO保稳的完整路径和迁移工具的完整代码,覆盖字符集转码、多作者映射、评论迁移、二级分类等八个真实踩坑,再讲迁移后的验证和SEO持续优化。 保哥最近在给一个老客户排查网站异常,发现这个用织梦DedeCMS搭的站点被上传了加密木马、被篡改了内容,百度索引清零,多个安全平台标红。这个站点跑了十二年DedeCMS,期间经历过五次大漏洞被利用、三次写shell事件——彻底没必要再继续。最终我帮客户把数据迁到Typecho,从评估到上线总共花了四天,迁完一周百度恢复索引,三个月后流量回到了被黑前的水平。下面这篇是把全流程总结成的可复制方法,附完整迁移脚本和踩坑笔记。这套脚本我自己用过六次,迁过的最大站点是8.7万篇文章。 ## 为什么要弃用DedeCMS转到Typecho ## DedeCMS的漏洞史与官方现状 DedeCMS最后一次官方安全更新停在2021年11月(v5.7.106),距今已经四年没有正式补丁。这期间CNVD/CVE收录的DedeCMS高危漏洞超过47个,其中可远程执行代码的就有11个。常见的几个:dede/article_add.php任意文件上传、dede/co_do.php任意文件包含、include/dialog/select_soft_post.php上传绕过、plus/recommend.php SQL注入 (https://zhangwenbao.com/dedecms-membership-center-pm-php-injection-vulnerability-repair-method.html)、tpl.php模板木马写入。这些漏洞修复都需要二次开发能力,普通站长根本守不住。 2022年7月织梦官方发布商业授权声明,要求所有商业用途必须购买授权(约5800元起),未授权使用面临版权诉讼。这进一步压缩了DedeCMS的使用空间。我手上有客户被代理律师函索赔过,最后协商付了2万8和解。 ## 为什么选Typecho而不是WordPress 从DedeCMS迁出有三个常见选择:Typecho、WordPress、Halo。我自己的选型逻辑: 选WordPress的场景:内容量大(5万篇以上)、需要丰富的插件生态、有专人运维、不在意性能开销。WordPress生态最完善,但默认配置下性能差,需要装W3 Total Cache或者LiteSpeed Cache才能跑得动,主题市场鱼龙混杂。 选Typecho的场景:内容量在10万篇以内、追求性能和简洁、有一定开发能力、注重SEO控制粒度。Typecho核心代码只有2.7MB(WordPress约75MB),默认配置下首屏加载时间比WordPress快3到5倍,TTFB通常在200到400毫秒。我自己运营的zhangwenbao.com就是Typecho,从2018年用到现在五年多,稳定性远胜WordPress。 选Halo的场景:技术栈是Java、需要复杂的工作流、追求现代化的管理后台。Halo是国产新一代博客系统,但生态还在建设中,主题和插件数量不如Typecho丰富。 我给这个客户最终选Typecho的原因:他的内容量2.3万篇、运维只有他一个人、原DedeCMS主题简洁不需要复杂工作流。Typecho完美匹配。 ## 性能对比的实测数据 我在同一台2核4G的腾讯云CVM上做过对照测试,相同的2.3万篇文章规模:DedeCMS默认配置首页TTFB 1.4秒、详情页TTFB 1.1秒、首页LCP 4.2秒;Typecho 1.3.0配置MySQL pagecache首页TTFB 280毫秒、详情页TTFB 220毫秒、首页LCP 1.6秒。同样的服务器配置,Typecho的Core Web Vitals表现完胜。 ## 迁移前的准备工作 ## 数据库信息确认 这一步看似简单但常踩坑。需要准确获取四组信息:DedeCMS数据库连接(host/user/pass/dbname/prefix)、Typecho数据库连接(同样四项加prefix)。表前缀默认值:DedeCMS是dede_、Typecho是typecho_,但如果安装时改过自定义前缀必须对应修改。 数据库主机地址通常是localhost(DedeCMS和Typecho在同一台服务器);如果跨服务器迁移,要确保MySQL允许远程连接(bind-address=0.0.0.0且防火墙开放3306端口)。我建议跨服务器场景下,先用mysqldump把DedeCMS数据库整库dump出来,导入到Typecho同一台服务器的临时库,这样脚本走localhost更快更稳定。 ## 环境依赖检查 PHP版本:推荐7.4或8.2(PHP 8.0之后有不少废弃警告,需要在脚本顶部加屏蔽)。必须启用的扩展:PDO MySQL、mbstring、json、curl(图片下载时用到)。检查命令:在SSH里跑php -m | grep -E "pdo_mysql|mbstring|json|curl",应该看到四行输出。 内存配置:迁移脚本在脚本顶部用ini_set设置了memory_limit=512M,但php.ini本身的限制如果是128M也会拦截。需要确认php.ini里memory_limit至少设置为256M,max_execution_time至少3600秒。Nginx侧也要确认fastcgi_read_timeout在3600秒以上,否则脚本跑到一半被Nginx 504掐断。 ## 目标系统就绪 Typecho必须先完整安装好,数据库表已经初始化。建议先发1到2篇测试文章验证安装无误,然后再开始迁移。Typecho版本推荐1.3.0(2024年12月发布,PHP 8.2兼容),1.2.1及以下版本的评论URL有XSS漏洞需要尽快升级。 主题先选一个跟原DedeCMS主题接近的避免视觉差异过大;插件先装:Sitemap(生成sitemap.xml)、CustomRSS(保留原RSS订阅地址)。不要一上来就装一堆插件,迁移完稳定运行一周后再按需添加。 ## 双向数据备份 迁移前必须双向备份。命令模板:mysqldump -u root -p --single-transaction --routines dedecms 大于 dede_backup_20260511.sql;mysqldump -u root -p --single-transaction --routines typecho 大于 typecho_backup_20260511.sql。--single-transaction保证一致性快照、--routines同时备份存储过程。备份文件下载到本地一份+保留服务器一份+异地备份一份,三份至少存留三个月。 文件层备份:DedeCMS整站打包tar.gz压缩、Typecho站点目录也打包一份。如果空间紧张,Typecho可以只备份config.inc.php和usr目录。 ## 内容预处理与图片路径规划 DedeCMS默认图片存在/uploads/目录下;Typecho默认在/usr/uploads/。两个方案: 方案A:保留DedeCMS的/uploads/路径,把整个目录复制到Typecho网站根目录的/uploads/下,迁移脚本里不替换路径。优点是文章里所有图片链接保持原样、不需要批量改写;缺点是Typecho后台媒体管理看不到这些图片(不在/usr/uploads/)。 方案B:把/uploads/目录复制到/usr/uploads/下,迁移脚本里启用str_replace替换。优点是符合Typecho规范、后台能管理;缺点是需要批量改写所有文章内容。 我推荐方案A——保留原路径风险最小,SEO友好(URL完全不变),Typecho后台看不到不影响实际访问。 ## 权限检查 数据库用户权限是常见坑。最小权限要求:对DedeCMS数据库SELECT+SHOW VIEW、对Typecho数据库SELECT+INSERT+UPDATE+CREATE TEMPORARY TABLES。授权SQL:GRANT SELECT, SHOW VIEW ON dedecms.* TO 'migrator'@'localhost'; GRANT SELECT, INSERT, UPDATE, CREATE TEMPORARY TABLES ON typecho.* TO 'migrator'@'localhost'; 建议为迁移单独建一个数据库账号migrator,迁移完之后REVOKE所有权限。这是安全最小化原则——长期共用root账号风险大。 ## 迁移工具的核心设计与完整代码 ## 工具的六个核心优势 调试友好:开启error_reporting(E_ALL)输出详细日志,每篇文章迁移进度、分类复用情况、跳过原因都打印,方便实时监控和排查。 数据库操作规范:用PDO连接+异常处理(ERRMODE_EXCEPTION)避免SQL注入风险,禁用预处理模拟(EMULATE_PREPARES=false)提升稳定性。 分类迁移智能:自动检测Typecho中已存在的分类,名称一致则复用,否则创建新分类,减少重复数据。 分批处理:通过pageSize(默认100)和offset实现分批迁移,配合延长的执行时间(3600秒)和内存限制(512M),适合处理大量数据。 冲突处理机制:把DedeCMS文章的aid作为Typecho的slug,保证URL结构不变,slug冲突时跳过并记录。 字符集兼容:连接字符串指定utf8mb4,支持emoji等特殊字符,避免迁移后的乱码问题。 ## 完整迁移脚本 把以下代码保存为dedecms-to-typecho.php,配置好两边数据库信息,上传到Typecho网站根目录,浏览器访问执行: 'localhost', 'user' => 'root', 'pass' => '', 'name' => 'dedecms', 'prefix' => 'dede_' ]; $typechoConfig = [ 'host' => 'localhost', 'user' => 'root', 'pass' => '', 'name' => 'typecho', 'prefix' => 'typecho_' ]; try { $dedeDb = new PDO( "mysql:host={$dedeConfig['host']};dbname={$dedeConfig['name']};charset=utf8mb4", $dedeConfig['user'], $dedeConfig['pass'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false] ); $typechoDb = new PDO( "mysql:host={$typechoConfig['host']};dbname={$typechoConfig['name']};charset=utf8mb4", $typechoConfig['user'], $typechoConfig['pass'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false] ); echo "数据库连接成功\n
"; } catch (PDOException $e) { die("数据库连接失败: " . $e->getMessage() . "\n
"); } // 获取DedeCMS栏目 echo "开始获取DedeCMS栏目\n
"; $dedeCateStmt = $dedeDb->query("SELECT id, typename FROM {$dedeConfig['prefix']}arctype"); $categoryMap = []; while ($cate = $dedeCateStmt->fetch(PDO::FETCH_ASSOC)) { $categoryMap[$cate['id']] = $cate['typename']; } echo "成功获取 " . count($categoryMap) . " 个DedeCMS栏目\n
"; // 处理Typecho分类 $existingCategories = []; $typechoCateStmt = $typechoDb->query("SELECT mid, name FROM {$typechoConfig['prefix']}metas WHERE type = 'category'"); while ($cate = $typechoCateStmt->fetch(PDO::FETCH_ASSOC)) { $existingCategories[$cate['name']] = $cate['mid']; } // 创建分类映射 $cateMigrationMap = []; foreach ($categoryMap as $dedeCateId => $dedeCateName) { if (isset($existingCategories[$dedeCateName])) { $cateMigrationMap[$dedeCateId] = $existingCategories[$dedeCateName]; echo "分类已存在复用: {$dedeCateName}\n
"; continue; } $stmt = $typechoDb->prepare("INSERT INTO {$typechoConfig['prefix']}metas (name, slug, type, description, count, `order`) VALUES (:name, :slug, 'category', '', 0, 0)"); $slug = preg_replace('/[^\w]+/', '-', strtolower($dedeCateName)); $stmt->execute([':name' => $dedeCateName, ':slug' => $slug]); $cateMigrationMap[$dedeCateId] = $typechoDb->lastInsertId(); echo "创建新分类: {$dedeCateName}\n
"; } // 迁移文章 $pageSize = 100; $offset = 0; $totalMigrated = 0; $duplicateSlugCount = 0; do { $stmt = $dedeDb->prepare("SELECT a.id AS aid, a.title, a.senddate, a.typeid, a.arcrank, b.body FROM {$dedeConfig['prefix']}archives a LEFT JOIN {$dedeConfig['prefix']}addonarticle b ON a.id = b.aid WHERE a.channel = 1 LIMIT :pageSize OFFSET :offset"); $stmt->bindValue(':pageSize', $pageSize, PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); $stmt->execute(); $articles = $stmt->fetchAll(PDO::FETCH_ASSOC); $articleCount = count($articles); if ($articleCount == 0) break; foreach ($articles as $article) { $targetSlug = (string)$article['aid']; $checkStmt = $typechoDb->prepare("SELECT cid FROM {$typechoConfig['prefix']}contents WHERE slug = :slug"); $checkStmt->execute([':slug' => $targetSlug]); if ($checkStmt->fetch()) { $duplicateSlugCount++; continue; } $status = $article['arcrank'] == 0 ? 'publish' : 'draft'; $content = $article['body'] ?? ''; $insertStmt = $typechoDb->prepare("INSERT INTO {$typechoConfig['prefix']}contents (title, slug, created, modified, text, authorId, status, type, commentsNum, parent, `order`) VALUES (:title, :slug, :created, :modified, :text, 1, :status, 'post', 0, 0, 0)"); $insertStmt->execute([ ':title' => $article['title'], ':slug' => $targetSlug, ':created' => $article['senddate'], ':modified' => $article['senddate'], ':text' => $content, ':status' => $status ]); $contentId = $typechoDb->lastInsertId(); if (isset($cateMigrationMap[$article['typeid']])) { $relationStmt = $typechoDb->prepare("INSERT INTO {$typechoConfig['prefix']}relationships (cid, mid) VALUES (:cid, :mid)"); $relationStmt->execute([':cid' => $contentId, ':mid' => $cateMigrationMap[$article['typeid']]]); } $totalMigrated++; } $offset += $pageSize; } while ($articleCount == $pageSize); echo "迁移完成 成功: {$totalMigrated} 跳过: {$duplicateSlugCount}\n
"; ?> 这套代码我用过六次,最大规模8.7万篇文章,单次跑完约45分钟。注意几点:图片路径替换默认注释掉了,需要的时候取消注释;作者ID硬编码为1,多作者站点见后面FAQ的修改方法;keywords迁移需要在insertStmt后追加标签处理代码(见FAQ第3条)。 ## 迁移后的关键配置 Nginx伪静态:在Typecho站点的nginx配置里加入以下规则: location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php$1 last; } } 修改完执行nginx -t检查语法,再nginx -s reload。如果是宝塔面板,直接在网站设置-伪静态里选typecho模板即可。 固定链接格式:登录Typecho后台-设置-永久链接,选自定义链接,填{slug}.html。这样文章URL变成"域名/123.html",跟DedeCMS的"域名/123.html"完全一致(前提是DedeCMS用的是默认链接格式)。 如果DedeCMS用的是"分类/aid.html"格式,Typecho的自定义链接填"{category}/{slug}.html"对应。 ## SEO保稳的核心动作 ## 4.1 301重定向规则的完整写法 大多数客户的DedeCMS都是默认链接格式(/分类slug/aid.html),迁移到Typecho后URL一致就不需要重定向。但有几种特殊情况必须做301: 情况一:DedeCMS的aid.html改成slug.html(slug用文章标题转拼音)。这时必须做aid到slug的全表映射重定向。在nginx里逐条写rewrite会爆炸,正确做法是用map指令: map $request_uri $rewrite_target { /old/123.html /new/article-title-1.html; /old/124.html /new/article-title-2.html; } server { if ($rewrite_target) { return 301 $rewrite_target; } } map文件用脚本从数据库生成,几万条规则可以正常工作(map内部用hash实现,O(1)查找)。 情况二:DedeCMS的"category/aid.html"改成纯"/slug.html"。这时用正则即可:rewrite ^/category/(\d+)\.html$ /$1.html permanent; 情况三:tag页URL变化。DedeCMS的tag URL是tags.php?/abc/,Typecho是/tag/abc.html。规则:rewrite ^/tags\.php\?/(.+)/$ /tag/$1.html permanent; ## sitemap.xml的提交与索引保稳 迁移完之后必须立即重新生成sitemap.xml并提交。Typecho装Sitemap插件,自动生成。提交渠道:Google Search Console的Sitemaps面板、百度搜索资源平台、Bing Webmaster Tools。建议同时提交。 百度有个特殊的"sitemap提交"额度,普通站点每天200条API推送限额。可以装BingIndexNow插件(Typecho生态有,能同时推送Bing和百度),新文章发布自动推送。 历史文章的索引保稳:Google Search Console的Coverage报告会显示索引状态。如果发现大量页面从Valid变成Excluded,说明robots.txt或meta robots有问题,要检查Typecho主题模板里的head输出。 ## 内链与目录结构的SEO权重保留 DedeCMS的内链分布是网站权重的核心,迁移后必须保留:tag页面、分类页面、专题页面的URL结构一致;面包屑导航在Typecho主题里实现并加BreadcrumbList结构化数据;内链锚文本如果指向具体文章,URL不变就不用动;如果有专题(DedeCMS的special_id),Typecho里可以用独立分类或者自定义页面对应。 ## 主题的SEO优化要点 选Typecho主题时优先考虑:H1只在文章页用一次(避免列表页也用H1);H2/H3层级清晰;图片自动lazy load;首屏不阻塞渲染;结构化数据完整(Article、BreadcrumbList、FAQPage (https://zhangwenbao.com/blog-faq-writing-seo-geo-guide.html)、Person);Core Web Vitals三项达标。 市面上Typecho主题良莠不齐,我自己用的zhangwenbao-v2是定制开发的。如果想用现成主题,建议Joe、handsome这两个相对完善。装完后用Google Rich Results Test验证结构化数据,用PageSpeed Insights跑Core Web Vitals。 ## 迁移后的注意事项与验证 ## 数据完整性核对 迁移完成后必须做四项核对:文章总数对得上(DedeCMS的archives表count vs Typecho的contents表count)、分类数对得上、每个分类下的文章数对得上、随机抽30篇文章看内容是否完整(特别是HTML标签、图片路径、特殊字符)。 抽查SQL:SELECT title, LENGTH(text) FROM typecho_contents ORDER BY RAND() LIMIT 30; LENGTH显示字节数,如果有一批文章LENGTH小于500说明可能内容截断,要查dede_addonarticle表的body字段是否完整。 ## 字符集陷阱 DedeCMS的旧版本(5.7之前)默认用GBK字符集,迁到Typecho的utf8mb4之前必须先转码。检查方法:mysql -u root -p -e "SELECT @@character_set_database FROM dedecms.dual"。如果显示gbk,必须先转utf8mb4: mysqldump --default-character-set=gbk -u root -p dedecms > dede_gbk.sql iconv -f gbk -t utf-8 dede_gbk.sql -o dede_utf8.sql sed -i 's/CHARSET=gbk/CHARSET=utf8mb4/g' dede_utf8.sql sed -i 's/SET NAMES gbk/SET NAMES utf8mb4/g' dede_utf8.sql mysql --default-character-set=utf8mb4 -u root -p dedecms_new < dede_utf8.sql 转完之后用dedecms_new作为迁移源,避免乱码。 ## 调试模式必须关闭 迁移脚本顶部开了error_reporting(E_ALL)和display_errors=1,迁移完成后必须修改为error_reporting(0)或直接删除脚本文件。否则任何后续访问这个URL会暴露服务器路径、数据库结构信息,是严重的安全风险。 保险做法:迁移完成后立即在网站根目录删除dedecms-to-typecho.php文件,或者用chmod 000禁止访问。 ## 性能调优与缓存 Typecho默认没有缓存,2万篇文章规模下首页加载会有压力。装PageCache插件(或者用zhangwenbao主题自带的pagecache机制)能解决80%的性能问题。Nginx侧可以加fastcgi_cache做L1缓存: fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=TYPECHO:100m max_size=1g inactive=60m; location ~ \.php$ { fastcgi_cache TYPECHO; fastcgi_cache_valid 200 30m; fastcgi_cache_key "$scheme$request_method$host$request_uri"; } 这样的两级缓存(fastcgi_cache + Typecho内部cache)能让2核4G的服务器扛住每天10万次访问。 ## 监测与异常告警 迁移后头两周必须密切监测:Google Search Console的Coverage报告每天看一次,重点关注Excluded里的"Crawled - currently not indexed"是否激增;百度站长平台的"抓取诊断"测试几个核心页面是否能正常抓取;Bing Webmaster Tools的Crawl Information看是否有大量404;服务器日志里grep "404"看是否有大量未配置的旧URL。 典型的迁移后2周流量曲线:迁移当天流量略降(DNS解析、CDN缓存切换)、迁移后2到5天流量回升到迁移前水平(搜索引擎更新索引)、迁移后2周流量超过迁移前(Typecho的Core Web Vitals优势开始生效)。如果迁移后一周流量没回升,立即排查nginx日志和Search Console报告。 ## 实战中遇到的典型问题 ## 标签数据丢失 原脚本只迁移文章和分类,标签(dede_taglist表)没动。补救方法:迁移完文章后单独跑一个标签迁移脚本,根据文章和标签的关联表dede_taglist+dede_tagindex插入到typecho_metas(type='tag')和typecho_relationships。这个补救脚本我下次再贴。 ## 自定义字段未迁移 DedeCMS的"自定义字段"(addonarticle表的description/litpic等)原脚本没处理。如果你的内容用了大量自定义字段(典型场景:电商类站点的产品描述、价格、规格),必须修改脚本添加字段映射,对应到Typecho的typecho_fields表。 ## 评论数据丢失 原脚本不迁移评论。如果评论数据重要,需要补一个评论迁移脚本,从dede_feedback映射到typecho_comments。注意字段对应:dede_feedback.dtime对应typecho_comments.created,dede_feedback.check对应typecho_comments.status(check=1转status='approved')。 ## 文章作者归属错误 原脚本authorId硬编码为1。多作者站点需要建立作者映射数组并修改插入逻辑。我自己的实操:先建立DedeCMS作者ID到Typecho作者ID的映射表(写在脚本顶部),$authorMap=[1=>2, 3=>1, 5=>3]这样的格式,然后把insertStmt里的硬编码1改成$authorMap[$article['mid']] ?? 1。 ## 图片防盗链失效 很多DedeCMS站点开了图片防盗链 (https://zhangwenbao.com/using-htaccess-to-set-up-wordpress-anti-stealing-link.html)(在nginx里限制Referer),迁移到新域名后图片可能加载失败。解决方法:在Typecho的nginx配置里把图片防盗链规则的allowed referer改成新域名+旧域名(保留旧域名是为了过渡期)。 ## 迁移之后的SEO持续优化 ## 内容质量重做 从DedeCMS迁过来的老内容很多是十年前的,不符合当下E-E-A-T (https://zhangwenbao.com/strengthen-authority-eeat-signals-ai-citations-2026.html)要求。建议挑Top 50访问量文章逐篇重写:补2000字以上的深度内容、加FAQ段、补结构化数据、新增配图。这一波内容质量重做做完,整站权重会有显著提升。 ## 内链结构重构 用Screaming Frog或者Ahrefs Site Audit跑一次站点内链结构图,识别"孤岛页面"(没有内链指向的页面)。给孤岛页面手动加内链入口,让所有页面都能在3次点击内被爬虫触达。 ## 结构化数据补全 Typecho主题如果默认不支持结构化数据,必须手动加。Article+BreadcrumbList+FAQPage是必备三件套,对应的JSON-LD代码加在主题的header.php或single.php里。补完之后用Google Rich Results Test逐页验证。 ## 性能持续优化 Cloudflare Free Plan免费用、Typecho开pagecache、图片转WebP、Hero图加fetchpriority="high"、Google Fonts本地化或者用system font。这一套打下来Core Web Vitals三项全绿不难。 ## 常见问题解答 ## 迁移后原DedeCMS的URL如何301到Typecho? 如果Typecho固定链接选{slug}.html且slug=DedeCMS的aid,URL完全一致不需要重定向。如果URL结构变了,Nginx配置文件里加rewrite规则。Apache用.htaccess的Redirect 301指令。批量映射数百条规则用Nginx的map指令最高效。重定向后必须在Google Search Console提交sitemap.xml加速搜索引擎更新索引。 ## 多作者站点怎么改作者ID硬编码? 先建立映射数组写在脚本顶部,比如authorMap=[1=2,3=1,5=3]这样DedeCMS作者ID到Typecho作者ID。找到insertStmt里的authorId硬编码1,改成authorMap[article的mid]??1。注意DedeCMS存作者的字段名要确认,通常是dede_archives的mid或writer,不同版本可能略有差异。修改后先测试1到2篇不同作者文章,确认归属正确再批量。 ## 迁移后内部链接失效怎么批量修复? 两种方式:方法一在数据库直接跑UPDATE typecho_contents SET text=REPLACE(text, 旧域名, 新域名),注意先备份再执行;方法二在迁移脚本content变量后加str_replace替换。建议方式一更可控。修复完随机抽查10到20篇文章确认内链能跳转。如果内链是相对路径不带域名,迁移后通常不需要修改。 ## PHP版本8.2兼容这套脚本吗? 兼容但有两处可能的差异。一是strtolower传null的Deprecated警告,在tag=trim(tag)后加if empty(tag) continue过滤掉空标签。二是PDO第四参数必须是数组(脚本已正确设置)。最稳的做法是在脚本顶部加error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT)屏蔽废弃警告不影响功能。 ## 怎么把DedeCMS的自定义字段也迁过去? 分两步。第一步在文章查询SQL里SELECT a.litpic和b.description这类字段;第二步在文章插入后用INSERT INTO typecho_fields cid name type str_value写入。Typecho原生支持自定义字段不需要装插件。映射示例:DedeCMS的litpic到Typecho的thumb字段、description到desc字段。注意字段类型用str_value存字符串、int_value存整数。 ## 评论数据能不能迁过去? 能但需要补脚本。从dede_feedback表查WHERE check=1的已审核评论,映射到typecho_comments表。字段对应:cid是文章ID(必须先有文章再插评论)、author是评论者名字、mail是邮箱、url是网址、text是内容、created是时间戳(DedeCMS的dtime直接是Unix时间戳)、status是approved/spam/waiting。回复关系parent字段如果原数据有就保留没有就置0。 ## PDO MySQL扩展未启用怎么办? Linux下用yum install php74-php-pdo php74-php-mysqlnd安装然后重启php-fpm或者apache。Windows IIS下打开php.ini找到分号开头的extension=pdo_mysql去掉分号保存重启IIS。验证方法是创建一个phpinfo页面看是否有PDO drivers的mysql项。常见坑:装了多个PHP版本没改对应版本的php.ini,可以用php -i grep php.ini确认当前加载的是哪个配置文件。 ## 二级分类层级会不会混乱? 原脚本只迁一级分类。修改思路:处理Typecho分类时SELECT语句加parent字段;获取DedeCMS栏目时SELECT id typename reid(reid是父分类ID 0表示一级);创建分类时如果reid不等于0就把parent设为父分类在Typecho的mid,需要先迁移完所有一级分类再迁二级。这样二级分类会自动归属到父分类下层级与DedeCMS一致。 ## Typecho后台媒体管理看不到图片怎么办? 原因是Typecho媒体库只显示后台上传的图片,迁过来的图片没在typecho_attachments表登记。补救SQL:INSERT INTO typecho_attachments扫描typecho_contents.text里出现的img src路径,提取唯一路径插入到attachments表。不影响图片实际访问,只是后台看不到管理。如果图片量大不在乎后台管理可以忽略这一步。 ## DedeCMS的数据库和文件还要保留吗? 建议数据库保留3个月用于核对,文件删除或禁用访问。保留期间做三项安全处理。一是禁用DedeCMS网站访问,Nginx的location里return 403拒绝所有访问。二是删除敏感文件包括dede/login.php后台和install.php安装文件。三是把DedeCMS数据库用户权限从读写改成只读,即使被入侵也无法修改数据。3个月后确认Typecho无问题再彻底删除。 ## Typecho各类页面meta robots和canonical配置:5类页面差异 - URL:https://zhangwenbao.com/typecho-meta-robots-canonical-seo-rules.html - 分类:Typecho教程 - 发布:2024-12-11 | 更新:2026-06-01 - 摘要:Typecho默认主题有分页稀释权重、搜索页被索引、归档页浪费爬虫预算三类问题。本文按首页、文章、分类、标签、搜索、归档等七种页面类型给出完整的meta robots与canonical PHP代码,并讲清末尾斜杠归一、Sitemap联动和摘要展示指令。 - 关键词:ECSHOP跳转,meta robots,canonical,noindex,Typecho SEO > **TLDR**:摘要:Typecho默认主题有分页稀释权重、搜索页被索引、归档页浪费爬虫预算三类问题。本文先给核心规则速览和完整的header.php代码,按首页、文章、分类、标签、搜索、归档七种页面类型配meta robots与canonical,再讲archiveUrl末尾斜杠归一、Sitemap.xml联动和实施后的验证清单。 > 摘要:Typecho默认主题有分页稀释权重、搜索页被索引、归档页浪费爬虫预算三类问题。本文先给核心规则速览和完整的header.php代码,按首页、文章、分类、标签、搜索、归档七种页面类型配meta robots与canonical,再讲archiveUrl末尾斜杠归一、Sitemap.xml联动和实施后的验证清单。 Typecho 自带的主题对 SEO 信号的处理一直比较简单粗暴——首页、分类页、文章页、搜索页全都输出一份 follow,index 完事。这种偷懒在站点小、关键词竞争弱时无所谓;一旦站点上规模、需要做精细化技术 SEO,就会暴露出一堆问题:分类页第 2 页和第 1 页争权重、搜索页大量低质量 URL 被索引、归档页占用爬虫预算却几乎不出流量。本文针对 Typecho 各页面类型——首页 / 文章页 / 单页 / 分类页 / 标签页 / 搜索页 / 归档页——以及它们的分页变体,给出一套 meta robots (https://zhangwenbao.com/is-meta-robots-noindex-nofollow-needed-with-canonical.html) 与 canonical (https://zhangwenbao.com/noindex-canonical-duplicate-page-seo.html) 的完整规则代码(基于 $this->getCurrentPage() 与 $this->is() 函数),可以直接粘贴进 header.php 立即生效,并配合 noindex (https://zhangwenbao.com/when-does-noindex-page-remove-from-google-search-results.html) 阈值、URL 末尾斜杠归一、HTTPS canonical、404 与 301 处理、Sitemap.xml 联动等周边工作把 Typecho 站点的技术 SEO 一次性做到位。 ## 为什么 Typecho 默认的 SEO 配置不够用 ## 分页页面的权重稀释 Typecho 默认主题对分类页第 2/3/4... 页输出和第 1 页同样的 index,follow——这意味着 Google 会把它们都索引,但它们的内容大部分和第 1 页重复(只是商品/文章列表不同段落)。结果是: - 分类页主关键词的权重被分散到 N 个分页 URL。 - 用户搜某分类时 Google 可能展示第 3 页给他(用户跳出率高)。 - 站内重复内容评分被拉低。 正确做法:分页页面 noindex,follow——不索引但允许爬虫跟踪链接。 ## 搜索页 URL 被索引的低质量问题 Typecho 自带的搜索功能 URL 形如 /?s=keyword。如果搜索页没设 noindex,Google 会把这些 URL 全部索引——尤其是当外链或站内链接指向某些搜索结果页时。结果: - 低质量索引页面爆炸增长。 - 每个搜索 URL 内容质量不可控(取决于谁搜了什么)。 - 潜在的 SEO 垃圾内容指控。 正确做法:搜索页 noindex,nofollow(搜索结果不索引、其上的链接也不需要爬虫跟踪——主要 URL 已经在主导航和分类里了)。 ## 归档页的价值低于成本 "2024 年 3 月归档"这种按月/年的归档页,对长尾关键词几乎没贡献,但占用爬虫预算。建议 noindex,follow——不索引归档页但让爬虫沿着归档页里的链接进入文章页。 ## 标签页的两难 标签页(tag page)介于"高价值"和"低价值"之间: - 如果你的标签使用规范、每个标签下有 5+ 篇文章——标签页是有价值的关键词聚合页,应当 index。 - 如果标签滥用、单标签只有 1-2 篇文章——标签页就是低质量内容农场,应该 noindex。 本文规则给的是"index,follow"——假设你的标签使用规范。如果不规范,先把标签清理一遍再考虑索引。 ## 核心规则速览 ## 七种页面类型的 meta robots 矩阵 页面类型 | 第 1 页 / 主页面 | 第 N 页 / 分页 | 首页 is('index') | index, follow | noindex, follow | 文章页 is('post') | index, follow | — | 单页 is('page') | index, follow | — | 分类页 is('category') | index, follow | noindex, follow | 标签页 is('tag') | index, follow | noindex, follow | 搜索页 is('search') | noindex, nofollow | noindex, nofollow | 归档页 is('archive') | noindex, follow | noindex, follow | ## 完整 max-snippet 配置 对允许索引的页面,加 max-snippet:-1, max-video-preview:-1, max-image-preview:large 让 Google 在 SERP 里显示完整摘要、完整视频预览、大图——这是 2019 年 Google 推出的指令,用于鼓励高质量内容站点更大的展示空间。具体含义: - max-snippet:-1:摘要长度无限制(默认值受 Google 限制为 160 字符左右)。 - max-video-preview:-1:视频预览长度无限制(默认仅几秒)。 - max-image-preview:large:图片预览展示大图(默认 standard 较小尺寸)。 ## 完整 header.php 代码 ## 基于 getCurrentPage 的精细化规则 把以下代码粘贴进当前模板的 header.php 的 ... 内即可生效: is('index')): ?> getCurrentPage() > 1): ?> is('post')): ?> is('page')): ?> is('category')): ?> getCurrentPage() > 1): ?> is('tag')): ?> getCurrentPage() > 1): ?> is('search')): ?> is('archive')): ?> ## 每段代码的解释 首页 is('index'):先用 getCurrentPage() > 1 判断是否为分页。第 1 页输出 index 信号,分页输出 noindex,follow 并 canonical 到主域名根 URL。 文章页 is('post') 与 单页 is('page'):永远 index,follow,canonical 指向当前 permalink。文章页和单页通常不分页(少数情况下会用 page 标签分页,但 Typecho 1.3 的 permalink() 已正确处理)。 分类页 is('category') 与 标签页 is('tag'):第 1 页 index,follow,分页 noindex,follow。canonical 都指向第 1 页的 archiveUrl,统一用 rtrim + 加斜杠确保末尾一致性。 搜索页 is('search'):永远 noindex,nofollow。不需要 canonical(搜索 URL 没有"主版本"概念)。 归档页 is('archive'):永远 noindex,follow。canonical 指向归档主页面。 ## archiveUrl 末尾斜杠问题 ## 为什么要 rtrim 加斜杠 Typecho 的 $this->archiveUrl 在不同 Typecho 版本和不同永久链接配置下可能输出 https://example.com/category/foo 或 https://example.com/category/foo/——末尾有无斜杠不一致。这会导致: - 同一个分类页有两个 URL 形式。 - canonical 指向不一致版本时 Google 困惑。 - 用户从外部点链接可能命中带斜杠版本,但站内链接指向不带斜杠版本——产生 301 跳转浪费爬虫预算。 rtrim($this->archiveUrl, '/') . '/' 强制让 archiveUrl 以斜杠结尾,所有 canonical 都用斜杠版本——配合 nginx / htaccess 把不带斜杠的版本 301 到带斜杠版本,全站归一。 ## nginx 末尾斜杠归一配置 location ~ ^/category/[^/]+$ { return 301 $scheme://$host$uri/; } location ~ ^/tag/[^/]+$ { return 301 $scheme://$host$uri/; } ## Typecho 的特殊场景 ## 多分类绑定一篇文章 Typecho 允许一篇文章关联多个分类。这时 $this->permalink() 仍然返回唯一的 permalink(基于主分类或 cid),所以 canonical 是单一的——不会有重复内容问题。 ## 友链 / 自定义页面 Typecho 的"独立页面"通过 is('page') 识别。友链页、关于页、联系页等都属于这一类。规则统一 index,follow + canonical 到 permalink。 ## 评论分页 文章评论很多时 Typecho 支持评论分页(URL 形如 /post.html/comment-page-2)。这种 URL 的处理: is('post')): ?> _commentsCurrentPage ?? 1; ?> 1): ?> 评论分页 noindex,follow——评论本身不构成主要内容信号,分页让 canonical 指回主 URL。 ## 404 页面 Typecho 的 404 页通过 $this->is('404') 判断。404 页面应该返回 HTTP 404 状态码(Typecho 默认正确)+ noindex: is('404')): ?> ## 主题切换时的兼容性 不同主题的 header.php 结构差异较大。粘贴本文代码前先备份原 header.php。如果原代码已有 ,删除原有的避免重复。 ## 配套优化:Sitemap.xml 联动 ## 从 sitemap 移除 noindex URL 本文规则下 search 页、archive 页、分页都设了 noindex——它们不应该出现在 sitemap.xml 里(出现在 sitemap 里却 noindex 是矛盾信号,Google 会困惑)。 如果你用 Typecho 的 Sitemap 插件(zhangwenbao 站点已启用),检查它的设置: - 关闭"包含搜索页"、"包含归档页"。 - 关闭分页 URL 的输出。 - 只保留首页第 1 页、所有文章页、所有单页、所有分类第 1 页、所有标签第 1 页。 ## 主动通过 Search Console 提交 sitemap 修改 sitemap 后立即在 Search Console → Sitemap 里重新提交。Google 会重新抓取并按新结构索引——通常 1-2 周看到效果。 ## 常见问题解答 ## 本文规则代码粘贴后没生效怎么排查 三步排查:F12 看 head 里实际输出的 meta robots 标签是否是新内容;如果还是旧的,检查是否 Typecho 模板缓存没刷新——临时改 functions.php 加一个空格触发重新加载;如果输出新标签但 Google 没重新抓取,主动 Search Console 触发 URL 检查请求重新索引。 ## 分页 noindex 后是否影响分类页第 1 页的排名 不影响反而提升。分页 noindex 让权重信号集中到第 1 页(canonical 都指向第 1 页),第 1 页主关键词的排名通常会有改善。这是分页归一策略的核心收益。 ## standard 主题需要改吗 需要。Typecho 自带 default、jasmine 等主题对 SEO 信号处理都比较粗糙——大多直接 index,follow 完事。建议根据本文规则手动改造 header.php。如果不愿改主题源码,可以写一个 Typecho 插件挂在 themeFile 钩子上输出 head 内容(更高级的做法)。 ## Yoast SEO 类似的全功能 SEO 插件 Typecho 有没有 Typecho 生态里没有 Yoast 这种成熟的全功能 SEO 插件。最接近的是 SEO_Master、OneSEO 等小型插件,功能远不及 Yoast。建议要么用本文方案手动改 header.php(最稳),要么转用 WordPress 配 Yoast/RankMath(如果 SEO 是核心需求)。 ## HTTPS 站点 canonical 写 http 会怎样 会被 Google 视为指向另一个 URL(http 和 https 是两个 URL)。如果你的站是 HTTPS,所有 canonical 必须输出 https 版本。$this->options->siteUrl() 在 Typecho 后台 URL 设置正确时会输出 https,但有些站点后台还设着 http——务必先统一后台 URL 为 https,再让 canonical 跟随输出。 ## 分类页 canonical 都指第 1 页那能不能直接 301 把分页都跳到第 1 页 不能。301 会让用户也无法访问分页内容。分页是为用户体验存在的(让大量内容分块加载),noindex 是给搜索引擎的指令——两者目的不同。让用户能翻页 + 让搜索引擎只索引第 1 页才是合理的组合。 ## 用 Typecho 的"伪静态"和"原生 URL"对 canonical 配置有影响吗 没影响。$this->permalink() 和 $this->archiveUrl 自动根据当前永久链接设置输出正确的 URL。所以无论你用 /[slug].html、/archives/[cid]/、还是其他格式,本文代码都不需要修改。 ## 是否需要给 RSS feed 也设 noindex RSS feed 是 XML 内容不是 HTML,不能写 meta 标签。但可以用 X-Robots-Tag: noindex HTTP 头: location ~ /feed { add_header X-Robots-Tag "noindex, follow"; } 不过 RSS feed 默认不会被 Google 当成网页索引,多数情况不需要单独处理。 ## 为什么不直接用 robots.txt 禁止搜索页和归档页 robots.txt Disallow 会让 Googlebot (https://zhangwenbao.com/google-404-crawl-seo-positive-signal.html) 完全访问不到页面——读不到 meta robots 也读不到 canonical。结果是:之前已被索引的搜索页 / 归档页会一直留在索引里,因为 Google 进不去再确认是否还存在。正确做法是先 noindex(让 Google 看到指令并去索引),等 4-6 周后再考虑 robots.txt Disallow——这时索引已清空。 ## 实施后的验证清单 - F12 → Elements → 检查 head 里 meta robots 和 canonical 输出是否符合本文规则。 - 抽样 10 个 URL(首页第 1 页、首页第 2 页、文章页、分类第 1 页、分类第 2 页、标签第 1 页、搜索页、归档页、404、单页)逐一验证。 - 用 curl -I 测试 archiveUrl 类页面的末尾斜杠归一。 - Search Console → URL 检查 → 看 Google 实际选择的 canonical 是否与你设置一致。 - 更新 Sitemap.xml 移除 noindex 类型 URL。 - 4-8 周后看 Search Console 覆盖报告——索引页面数应稳定下降(noindex 生效),关键词排名应略有改善。 这套规则在我自己的 Typecho 站上跑了 3 年,覆盖了我经手过的二十几个 Typecho 客户站。改完后通常 1-2 个月看到主关键词的排名提升 3-7 位,索引页面数减少 30-50%(去掉了大量低质量分页和搜索页)。技术 SEO 不是玄学——精细化的指令让搜索引擎更准确地理解你的内容,自然会有正向反馈。 ## Canonical主题集群:完整4篇延伸阅读 本文是Canonical主题集群的一部分。如果你想系统理解Canonical标签从基础概念、算法决策、与noindex联动到CMS实现的完整链路,建议继续阅读以下3篇: - Canonical URL是什么?SEO优化必备的规范网址设置指南 (https://zhangwenbao.com/canonical-url-seo-guide.html)——基础概念入门:定义、作用、设置方法,含分页/电商筛选/AMP/PC-移动端/hreflang多场景实操指南。 - Google选择Canonical URL的9大决策逻辑与排查实操指南 (https://zhangwenbao.com/google-canonical-url-selection-logic.html)——Google内部决策机制深度解析:精确重复/部分匹配/URL参数推断/移动端版本/渲染失败等9种场景,附canonical被选错的系统化排查流程。 - noindex和Canonical能同时用吗?避坑指南 (https://zhangwenbao.com/noindex-canonical-duplicate-page-seo.html)——noindex(指令)vs Canonical(信号)的本质区别、信号冲突机制、5个场景的正确选择、follow/nofollow与X-Robots-Tag HTTP头高级用法。 ## 权威参考资料 ## Typecho导航菜单怎么把分类加进去?从最小代码到下拉菜单 - URL:https://zhangwenbao.com/typecho-add-category-to-navigation-menu.html - 分类:Typecho教程 - 发布:2017-01-16 | 更新:2026-06-01 - 摘要:给Typecho顶部导航加分类,得先懂contents与metas两表分离的设计。本文从原理讲到最小可用代码、首页加页面加分类的合并写法、parent树形渲染下拉菜单、白黑名单、无障碍aria-current、跨请求缓存,最后给新手从0到1的最短路径和七项避坑,是主题二开必备。 - 关键词:Typecho分类,Typecho菜单,Typecho主题,Widget组件,header.php > **TLDR**:摘要:给Typecho顶部导航加分类,得先懂contents与metas两表分离的设计——默认主题只显示独立页面。本文从原理讲到最小可用代码、页面与分类合并显示的写法、多级分类与下拉菜单、按parent树形渲染、排除某些分类、带图标的菜单,再顺手补SEO与aria-current无障碍、给菜单做条数缓存、改完不生效的常见原因,最后给从0到1的最短路径。 > 摘要:给Typecho顶部导航加分类,得先懂contents与metas两表分离的设计——默认主题只显示独立页面。本文从原理讲到最小可用代码、页面与分类合并显示的写法、多级分类与下拉菜单、按parent树形渲染、排除某些分类、带图标的菜单,再顺手补SEO与aria-current无障碍、给菜单做条数缓存、改完不生效的常见原因,最后给从0到1的最短路径。 保哥维护 Typecho 站点这些年,被人问得最多的就是导航条的事。默认主题里 header.php 只渲染独立页面(Page),分类(Category)压根不出现在顶部菜单。这件事在官方文档里其实有暗示,但没有一个完整的、能直接复制到生产站的范例。本文把我自己常用的几段代码、踩过的坑、以及和搜索引擎抓取相关的小细节都摊开来写一遍,方便后来人少走弯路。 ## 为什么默认主题只显示独立页面 如果你打开 Typecho 自带 default 主题的 header.php,会看到类似 Widget_Contents_Page_List 的组件调用。这个组件查询的是 type='page' 的内容,所以分类天然就不会出现。Typecho 的设计哲学是把内容(contents)和分类(metas)分两张表存:typecho_contents 放文章和页面,typecho_metas 放分类和标签。两边通过 typecho_relationships 表多对多关联。所以菜单要同时显示页面和分类,就需要分别调用两个 Widget。 保哥早年第一次改这个的时候,犯过一个低级错误:把分类列表 Widget 写在 Widget_Contents_Page_List 的 while 循环里面,结果分类菜单只在第一个页面循环里出现一次,逻辑全乱。所以请记住,两个 Widget 是平级关系,foreach/while 各自闭合,互不嵌套。 另一个常被问的版本差异:Typecho 1.0、1.1、1.2、1.3 这四个版本里 Widget_Metas_Category_List 的方法签名几乎没变,只在 1.3 里 toString 部分加了新的命名空间形式。如果你跟着本文 1.3 的写法做,往老版本上回退也能用。 ## 最小可用代码:把分类塞进顶部导航 下面这段是我现在线上用的版本,直接放在 header.php 的