简介:面向DEDECMS(织梦)站长与SEO初学者的蜘蛛爬行插件,定位于在站点环境内模拟百度、谷歌、360等搜索引擎爬虫,辅助排查抓取异常、链接结构与页面代码问题。压缩包共38个文件,约36KB,以27个PHP脚本为核心,配合5个GIF图标、2个JS与2个CSS前端资源、1个SQL数据库脚本和1个TXT说明文件,体量轻便、模块划分明确。其功能覆盖模拟爬取、抓取日志分析、死链检测、内链结构检查、URL规范化及HTML代码优化建议,可把散落的访问记录整理成可读报告,帮助读者理解爬虫如何沿链接发现页面、判断404与加载缓慢页面,并据此调整站点结构。已有376人学习或下载,适合希望用小型工具完成日常SEO自检、建立抓取与链接问题排查思路的DEDECMS使用者。
1. 蜘蛛爬行插件到底补的是哪块短板:从「发布到收录」的黑匣子说起
内容更新完、后台显示已发布,搜索引擎那边却要等上一两周才见影子,这是很多 dede 站点最熟悉的憋屈。dede蜘蛛爬行插件补的正是「发布到收录」之间的黑匣子:它把每一次真实蜘蛛来访(UA、IP、URL、状态码、页面生成耗时)落进数据库,把最新内容整理成一份可抓取的入口清单,再按可配置的节奏去调用搜索引擎的开放推送接口,让抓取从「干等」变成「有据可查地催」。它不是采集器,也不生成内容,本质是一套抓取侧的可观测性加调度工具。适合还在用 dede 维护内容站、能读写一点 PHP、想搞清楚「蜘蛛到底来没来、来了抓了什么」的站长和 SEO 执行者;纯静态站或者不打算动模板的项目,装了也是摆设。
2. 拆包看结构:三张表、五个入口和一条执行链路
拿到一个 dede插件,先别急着传上去点安装。我的习惯是先看目录:文件放哪、表建几张、钩子挂在哪一层,这三件事决定了后面出问题是能查还是只能猜。蜘蛛爬行插件本身不复杂,复杂的是它要和 dede 那套「plus 目录 + 模板标签 + 数据表」的老架构对上,任何一处放错位置都会表现成「装了但没数据」。
2.1 目录里每个文件到底干什么
解压后大致是这个形状(不同版本文件名会有出入,职责基本一致):
spidercrawl/ ├── install.php # 建表 + 写入默认配置,只跑一次 ├── uninstall.php # 删表,慎点 ├── config.php # UA 池、频率、开关、推送配额,唯一需要手改的文件 ├── lib/ │ ├── Db.php # 单例连接 + insert/update 封装 │ ├── UaPool.php # UA 特征串匹配 │ └── UrlPool.php # URL 池取号、标记状态、去重 ├── plus/ │ └── spider_hit.php # 前端采集入口,被模板以 1×1 图片方式调用 ├── cron/ │ ├── crawl_tick.php # 取 URL 池里的链接做预热抓取 │ └── push_tick.php # 消费推送队列,带退避重试 └── tpl/ ├── index.htm # 后台面板首页 └── log.htm # 日志列表,带筛选和分页这个拆法是有讲究的:config.php单独抽出来,是为了升级时只覆盖lib/和cron/,不动配置;plus/spider_hit.php单独放,是因为 dede 的plus目录天然对外可访问,不需要改伪静态;cron/独立,是因为它必须能被命令行直接php xxx.php跑起来,一旦在文件头 require 了 dede 的common.inc.php,命令行下会因为找不到 session 目录、模板常量而报一堆 warning,所以 cron 里的文件只依赖lib/,不引整站入口。tpl/用.htm而不是框架模板,是照顾 dede 后台那套老模板引擎,直接include就能渲染,省得再学一遍语法。
有一点要提前说清楚:这套东西对 dede 是「外挂」而不是「内核插件」。dede 的插件体系一直比较弱,真正的钩子点就那么几个,所以它能做的只有两件事——在前端加一个可被请求的入口,在后台加一个可被访问的页面。指望它自动挂在文章发布事件上,会失望。
2.2 三张表的字段取舍与索引设计
整个插件的数据模型就三张表,字段不多,但每个字段都有存在的理由:
| 表名 | 作用 | 关键字段 | 数据量级 |
|---|---|---|---|
spider_log | 每次蜘蛛来访一条 | spider、url_md5、ip、status、cost_ms、created | 大,按天归档 |
spider_url | 待抓/已抓的 URL 池 | url_md5、url、state、last_code、retry | 中,万级 |
spider_push | 推送队列与结果 | url_md5、state、try_count、next_time | 小,千级 |
spider_log里ip存int而不是字符串,不是为了省那几个字节,是因为ip2long之后能直接走索引做「同一 IP 高频访问」的聚合,字符串比较在这个量级上会明显拖慢。url同时存了原文和md5,md5用来做唯一键——URL 带参数时长度可能超过 255,直接给url建唯一索引会踩到索引长度上限,而且参数顺序一变就判成两条,md5前先做一次规范化(去 utm 参数、统一小写、去掉末尾斜杠)才靠得住。
status和cost_ms是我坚持要加的两列。很多人只关心「蜘蛛来没来」,但真正能定位问题的是这两个:status里出现大量 404 说明站内死链多,出现 503 说明你服务器在蜘蛛来的时候扛不住;cost_ms持续走高,往往比收录下降早一周出现,是个挺好的预警信号。
spider_url的state只设计三个值:0 待抓、1 抓取中、2 已完成。加一个「抓取中」不是洁癖,是为了防止两个 cron 实例重叠时同一条链接被抓两遍——取号时用UPDATE ... SET state=1 WHERE state=0 ORDER BY id LIMIT n再回查,这是最省事的做法,不需要额外的锁表。
spider_push的next_time是退避重试的核心。推送失败不要立刻重试,写死一个next_time = now + 2^try_count * 60,try_count到 5 就置为失败终态,别让它无限循环。这条规则看起来粗糙,但能挡住 90% 的「重试风暴」。
2.3 从一次访问到一条记录的执行链路
完整链路是四段,任何一段断了都会表现为「面板空白」:
- 访客或蜘蛛请求页面 → 模板 footer 里那张 1×1 图片被请求 → 打到
plus/spider_hit.php。 - 入口文件读
HTTP_USER_AGENT,交给UaPool::match()判断是不是已知蜘蛛,不是就直接exit,不落库,避免把普通访客也写进去把表撑爆。 - 命中后组装一行记录写入
spider_log,同时把 URL 的md5upsert 进spider_url,state置 0。 cron/push_tick.php定时把spider_url里新增的链接搬进spider_push,调推送接口,按结果改state和next_time。
第二段是整条链路里最容易翻车的地方。模板里挂图片的方式有两种,一种是直接在footer.htm里写<img src="/plus/spider_hit.php?u=..." width="1" height="1">,另一种是在 JS 里new Image().src=...。前者的好处是蜘蛛不执行 JS 也能触发,坏处是会在 HTML 里留一个可见的 img 标签,某些编辑器会把它吃掉;后者对蜘蛛无效,只对人有效。我一般选前者,并且把 URL 参数做urlencode,否则带&的地址会把参数截断。
还有一层兜底:模板挂载只能覆盖「蜘蛛渲染了页面」的情况,如果蜘蛛只抓了 HTML 没请求图片(很多引擎的轻量抓取就是这样),日志里就一条都没有。所以真正可靠的原始数据源是 nginx 的 access log,插件里的cron/目录通常还会带一个import_log.php,按小时增量解析日志补录。两路数据合并的时候用url_md5 + created(按小时取整)做去重,否则同一次访问会被记两遍,后面做漏斗分析全是水分。
3. 从上传到跑通:四步把爬行日志写进数据库
动手之前先明确一件事:这套东西只有「跑通一条记录」才算装完,装完插件看到后台有个菜单不算。下面四步按顺序走,每一步都有可验证的输出。
3.1 前置检查:版本、权限、伪静态
先在服务器上把环境摸清楚,不要凭记忆:
# 1. PHP 版本与关键扩展,dede 老站常见 5.6,插件至少要 7.0 php -v php -m | grep -E 'pdo_mysql|curl|json' # 2. 站点根目录,确认 dede 的 common.inc.php 在哪一层 ls -l /data/wwwroot/site/include/common.inc.php # 3. 目录写权限,日志和缓存目录必须可写 namei -l /data/wwwroot/site/data/三条命令的输出决定了后面能不能走通。PHP 低于 7.0 的话,lib/里用到的??和标量类型声明会直接白屏,这种情况要么改代码,要么放弃;pdo_mysql缺失是最常见的坑,插件里Db.php用的是 PDO 而不是 dede 自带的$dsql,因为$dsql在 cron 环境下初始化会拉一堆不必要的全局变量。
权限那一条要特别注意:dede 的data/目录经常被设成 755 且属主是 www,你用 root 传上去的插件文件属主是 root,PHP-FPM 以 www 运行时读得到但写不了日志,表现就是「面板能打开,一条数据都没有」。用chown -R www:www spidercrawl/一次性解决。
伪静态也要确认。如果站点开了全站 rewrite,/plus/有可能被规则吃掉,请求spider_hit.php返回的是首页 HTML 而不是图片。测法很简单:
curl -sI "https://你的域名/plus/spider_hit.php?u=%2F" | head -n 3 # 期望看到 HTTP/1.1 200 和 Content-Type: image/gif返回text/html就说明 rewrite 顺序有问题,nginx 里加一条location ^~ /plus/ { }让它在正则规则之前匹配即可。
3.2 建表与初始化配置
表结构不建议直接跑install.php,那个文件通常会带一堆演示数据。手动执行建表语句更可控:
-- 表前缀按你站点的实际配置替换,dede 默认是 dede_ CREATE TABLE IF NOT EXISTS `dede_spider_log` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `spider` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '蜘蛛标识,来自 UA 匹配结果', `url_md5` CHAR(32) NOT NULL DEFAULT '' COMMENT '规范化 URL 的 md5', `url` VARCHAR(500) NOT NULL DEFAULT '', `ip` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'ip2long 后的值', `status` SMALLINT NOT NULL DEFAULT 0 COMMENT 'HTTP 状态码', `cost_ms` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '页面生成耗时(毫秒)', `created` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Unix 时间戳', PRIMARY KEY (`id`), KEY `idx_created` (`created`), KEY `idx_md5_created` (`url_md5`, `created`), KEY `idx_spider_created` (`spider`, `created`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `dede_spider_url` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `url_md5` CHAR(32) NOT NULL, `url` VARCHAR(500) NOT NULL, `state` TINYINT NOT NULL DEFAULT 0 COMMENT '0待抓 1抓取中 2完成', `last_code` SMALLINT NOT NULL DEFAULT 0, `retry` TINYINT NOT NULL DEFAULT 0, `updated` INT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), UNIQUE KEY `uk_md5` (`url_md5`), KEY `idx_state` (`state`, `id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;建完立刻验证索引有没有落地,别等到数据涨到百万行才发现漏了:
SHOW INDEX FROM dede_spider_log; EXPLAIN SELECT COUNT(*) FROM dede_spider_log WHERE created >= UNIX_TIMESTAMP(NOW()) - 86400;第二条的type应该是range而不是ALL。如果是ALL,说明idx_created没建上,后台面板统计会随着数据增长越来越慢,最后直接把 MySQL 连接数打满。
配置文件config.php至少要改三项:数据库连接(如果你的 dede 用的是自定义前缀,这里要同步)、站点根 URL、推送接口的 token。前两项填错的表现是安装完就 500,第三项填错的表现是队列一直重试,日志里全是 401。
3.3 采集入口:一段 20 行的 PHP
核心采集逻辑就这么点东西,不复杂但每行都有用:
<?php // plus/spider_hit.php 蜘蛛来访采集入口,被模板以 1×1 图片方式调用 require_once dirname(__FILE__) . '/../lib/Db.php'; require_once dirname(__FILE__) . '/../lib/UaPool.php'; $ua = isset($_SERVER['HTTP_USER_AGENT']) ? substr($_SERVER['HTTP_USER_AGENT'], 0, 255) : ''; $hit = UaPool::match($ua); // 命中返回 spider 标识,未命中返回 null if ($hit === null) { // 普通访客不落库,直接输出空图退出,避免日志表被真人流量冲垮 header('Content-Type: image/gif'); echo base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'); exit; } $url = isset($_GET['u']) ? urldecode($_GET['u']) : ''; $norm = UaPool::normalizeUrl($url); // 去 utm 参数、统一小写、去尾斜杠 Db::insert('spider_log', array( 'spider' => $hit, 'url_md5' => md5($norm), 'url' => substr($norm, 0, 500), 'ip' => ip2long($_SERVER['REMOTE_ADDR']), 'status' => isset($_GET['s']) ? (int)$_GET['s'] : 200, 'cost_ms' => isset($_GET['t']) ? (int)$_GET['t'] : 0, 'created' => time(), )); header('Content-Type: image/gif'); echo base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');几个参数必须解释清楚。u是当前页面的相对路径,由模板侧用urlencode传进来,不传就等于记录了一个空 URL,后面做漏斗时这条数据是废的。s状态码和t耗时由模板侧从 dede 的$cfg_变量或者自定义计时器取,取不到就默认 200 和 0,不要让流程因为缺参数中断。substr($ua, 0, 255)这一步是防御性的:有扫描器会塞几 KB 的 UA 进来,不截断会直接把spider_log写报错。
UaPool::match()的实现思路是「特征串包含匹配」而不是整串相等。UA 里的版本号、系统标识天天变,整串比对第二天就全漏了。匹配表放配置文件里,形如'engine_main' => array('SpiderMain', 'spider-main'),数组里任何一个子串出现即判定命中。这个设计的好处是可维护——发现新蜘蛛,加一行字符串就行,不用动代码。
3.4 计划任务:crawl_tick 与 push_tick
两个 cron 各司其职,频率别设太狠:
# 每 5 分钟处理一次 URL 池,单轮上限 20 条 */5 * * * * /usr/bin/php /data/wwwroot/site/spidercrawl/cron/crawl_tick.php >> /data/logs/spider_crawl.log 2>&1 # 每 10 分钟消费一次推送队列,失败重试靠表里的 next_time 控制 */10 * * * * /usr/bin/php /data/wwwroot/site/spidercrawl/cron/push_tick.php >> /data/logs/spider_push.log 2>&1 # 每天凌晨 3 点把 30 天前的日志归档到历史表 0 3 * * * /usr/bin/php /data/wwwroot/site/spidercrawl/cron/archive_log.php >> /data/logs/spider_archive.log 2>&1crawl_tick.php里的关键参数是$limit = 20和usleep(300000)。前者决定单轮抓多少条,后者决定条与条之间的间隔。20 条乘以 300ms 大约 6 秒,加上请求本身的耗时,一轮十几秒跑完,5 分钟一轮,一天大概能过 5000 条左右——对万级 URL 池的站点刚好,对十万级就得调高频率或者加多进程。注意这里的抓取用的是带自身标识的 UA(比如SpiderCrawlWarmup/1.0 (+site-owner)),不是伪装成搜索引擎,目的是预热缓存、暴露死链,不是骗统计。
push_tick.php的逻辑是先按next_time <= now AND state IN (0,3)取 50 条,逐条调接口,成功置 2,失败try_count+1并按2^try_count * 60秒写回next_time,try_count超过 5 直接置 4(终态失败)。这一步的坑在于「接口返回 200 但不代表提交成功」,很多推送接口把错误信息放在响应体的error字段里,必须解析 JSON 判断,只看 HTTP 状态码会造成「面板显示全成功,实际一条没进去」。
跑通的标准是三个:spider_log有新增行、spider_url的state在变、spider_push里能看到成功记录。三个都满足,安装才算完。
4. 参数怎么定:频率、并发、UA 池与 URL 池的六个建议值
插件装完只是能跑,跑得对不对全看参数。这一章给的都是我在实际站点上验证过的区间,不是理论最优,照抄能避开大部分翻车。
4.1 频率与并发:把「催」控制在礼貌范围内
| 参数 | 位置 | 保守值 | 激进值 | 说明 |
|---|---|---|---|---|
| 单轮抓取条数 | crawl_tick.php | 20 | 100 | 超过 100 条一轮跑不完 5 分钟周期 |
| 条间隔 | crawl_tick.php | 300ms | 50ms | 低于 50ms 容易触发源站限流 |
| 单条超时 | curlCURLOPT_TIMEOUT | 8s | 3s | 超时太短会误判慢页面为死链 |
| 推送并发 | push_tick.php | 1 | 3 | 多数接口按 QPS 限流,串行最省心 |
| 每日推送上限 | config.php | 500 | 2000 | 按接口配额给,别一次用完 |
| 日志保留天数 | archive_log.php | 30 | 90 | 超过 90 天查询会明显变慢 |
这里最容易被忽略的是「单条超时」和「每日推送上限」的配合。超时设太短,一个正常但生成慢的页面会被标成失败,重试几次后进终态,你以为是蜘蛛没来,其实是自己把它判死了。推送上限则直接关系到配额,接口一般按天给额度,你在早上十点把额度打光,下午发的新文章就得等第二天,反而不如均匀分发。
我的做法是把推送做成「按小时配额」而不是「按天配额」,push_tick.php每次取数前先查当前小时已推条数,超过日上限/24 * 1.5就跳过。这样既不会一次打光,也不会在流量低谷期浪费额度。
4.2 UA 池:特征串从哪里来
UA 池不能靠猜,得从你自己的访问日志里捞。这一步用一条 awk 就能完成:
# 从昨天的 access.log 里提取疑似蜘蛛的 UA,按出现次数排序取前 20 awk -F'"' '{print $6}' /data/logs/nginx/access.log.1 \ | grep -iE 'spider|bot|crawl|slurp' \ | sort | uniq -c | sort -rn | head -n 20输出里出现次数多、且明显不是你真实用户的那些,就是需要进池子的。把整串 UA 去掉版本号之后剩下的固定部分,作为特征串填进config.php的ua_pool。
有一个反直觉的点:UA 池不是越大越好。池子里塞了几十种冷门蜘蛛,match的循环次数上去,每个页面请求都要多跑几十次字符串包含判断,高峰期对 PHP 进程是不小的开销。我的习惯是只保留最近 30 天日志里真实出现过的,每月清理一次,通常维持在 8 到 12 个特征串。
另外别把「疑似」当「确定」。有些采集器和安全扫描器会把自己的 UA 伪装成主流蜘蛛,光看 UA 会误判。判断是不是真蜘蛛,看三个东西:IP 反解归属、是否请求了robots.txt、访问频率是否呈现明显的爬行梯度。前两个在日志里能直接看到,第三个要按 IP 聚合算一下。插件里那个「按 IP 统计」的视图就是干这个的,别忽略它。
4.3 URL 池与去重:入池之前先做三件事
URL 池的质量决定了后面所有数据的质量。入池前必须做三件事,缺一件后面就得返工:
- 规范化:去掉
utm_*、from=、spm=这类渠道参数,统一小写,去掉末尾多余的/,index.php统一成/。 - 过滤:排除
?参数里带page=超过 100 的分页、排除搜索结果页、排除tag和author这类聚合页——这些页面数量无限,进了池子就是灾难。 - 限深:同一栏目下最多取前 50 条,剩下的靠站点地图递进,不要一次性全塞进去。
规范化之后再做 md5,顺序不能反。反了的话?a=1&b=2和?b=2&a=1会算成两个不同的 md5,池子里全是重复项,抓取次数白白浪费。
// lib/UrlPool.php 里的规范化实现,顺序不要调换 public static function normalizeUrl($url) { // 1. 去掉锚点,锚点对蜘蛛没有意义 $url = preg_replace('/#.*$/', '', $url); // 2. 拆分 query,剔除渠道参数 $parts = parse_url($url); $query = array(); if (!empty($parts['query'])) { parse_str($parts['query'], $query); foreach (array_keys($query) as $k) { if (preg_match('/^(utm_|from|spm|ref)/i', $k)) { unset($query[$k]); } } } // 3. 参数按 key 排序,保证顺序不影响结果 ksort($query); $path = isset($parts['path']) ? strtolower(rtrim($parts['path'], '/')) : '/'; return $path . ($query ? '?' . http_build_query($query) : ''); }ksort这一步是整段代码的核心,很多人漏掉它,结果就是同一篇文章在池子里存了三四份。strtolower只对 path 做不对 query 做,是因为 query 里的大小写可能有业务含义。
4.4 伪静态、robots 和 sitemap 的配合
插件只负责「记录」和「催」,真正决定蜘蛛来不来、来多勤的,还是站点本身对外的可抓取性。三件事必须一起做:
robots.txt 别把采集入口挡了。如果你在robots.txt里写了Disallow: /plus/,spider_hit.php就不会被请求,模板那一路数据直接归零。要么把采集入口换个不在/plus/下的路径,要么接受这个事实、全靠 nginx 日志补录。我一般选后者,因为日志更真实。
User-agent: * Disallow: /data/ Disallow: /include/ Disallow: /*?*page= Allow: / Sitemap: https://你的域名/sitemap.xmlsitemap 要按更新时间分段。一个塞了五万条 URL 的sitemap.xml,蜘蛛抓到一半就放弃了。常见做法是拆成sitemap_index.xml加按栏目/按月分的子图,每个子图不超过 5000 条,并在<lastmod>里写真实更新时间而不是当前时间——写当前时间等于每次都告诉蜘蛛「全站都变了」,几次之后就没人信了。
伪静态规则要和 URL 池的规范化规则对得上。池子里存的是/news/202401/123.html,实际访问却要 301 到/news/123.html,那每一次抓取都多一次跳转,日志里status全是 301,看着抓取量很大,实际有效抓取只有一半。这个坑很隐蔽,因为面板上的总数是好看的。
三件事做完,再看日志里的status分布:2xx 占 85% 以上、404 低于 5%、5xx 接近 0,才算健康。达不到就先别调频率参数,先把站的抓取结构修好。
5. 避坑排查:五条踩过的血泪记录
5.1 面板打开转圈,MySQL 连接数被打满
现象:装完第三天后台面板打不开,前端也变慢,show processlist里一堆Sending data。
原因:index.htm里的概览数字用了SELECT COUNT(*) FROM dede_spider_log,全表扫描;同时Db.php每次请求都新建 PDO 连接,面板一次刷新要连十几次。
解决:概览页改成查汇总表——每天归档任务顺手写一条spider_stat_daily,面板只读这张几万行的表;Db.php改单例,static $pdo只初始化一次。改完之后面板响应从 8 秒降到 200ms 以内。
5.2 日志一条都没有,但网站访问正常
现象:装完两天,spider_log是空的,面板显示 0。
原因:模板 footer 里那张图片的src写成了相对路径plus/spider_hit.php,在/news/2024/xxx.html这种深路径页面下被解析成/news/2024/plus/spider_hit.php,404。蜘蛛抓到的大部分正好是深层页面。
解决:src一律用绝对路径/plus/spider_hit.php,或者用 dede 的{dede:global.cfg_cmsurl/}拼。改完当天就有数据。
5.3 推送接口全是失败,try_count涨到 5
现象:spider_push里九成记录是终态失败,日志里全是 401。
原因:推送 token 用了测试环境的,正式环境没换;同时因为重试没有配额控制,一分钟内打了几百次,被接口侧临时封了。
解决:换 token 之后先手动跑一次push_tick.php验证;再把退避逻辑从「固定 60 秒」改成2^try_count * 60并加小时配额,重试风暴就没了。教训是上线前一定先用一条 URL 手动跑通全流程。
5.4 蜘蛛 IP 永远是同一个,URL 全是首页
现象:spider_log里 90% 记录来自同一个 IP,url全是/。
原因:站点前面挂了一层缓存层,所有回源请求的REMOTE_ADDR都是缓存节点的 IP;同时采集入口只在首页模板挂了,内页模板没加。
解决:ip2long($_SERVER['REMOTE_ADDR'])改成优先取HTTP_X_FORWARDED_FOR的第一段,取不到再回落到REMOTE_ADDR;内页模板统一在 footer 里追加采集代码。这里要提醒一句,X-Forwarded-For是可以伪造的,做统计够用,别拿它做任何权限判断。
5.5 日志时间比实际早 8 小时
现象:面板上蜘蛛访问都集中在凌晨,跟实际抓取时间对不上。
原因:created存的是time(),本身没问题;问题在展示层用了FROM_UNIXTIME(created),而 MySQL 的time_zone是 UTC,PHP 是 Asia/Shanghai。
解决:统一约定「库里只存 UTC 时间戳,展示时由 PHP 用date('Y-m-d H:i', $ts)转换」。不要两边都做时区转换,那是重复转换,比不转换更难查。这条规则写进项目 README,后面接手的人就不会再踩。
6. 验证与进阶:用一条 SQL 把爬行日志变成收录漏斗
6.1 先跑三个校验
装完别急着看数字好不好看,先确认数据是对的。三个校验我每次都会跑:
-- 校验一:日志表里是否混进了非蜘蛛流量(spider 为空说明 UA 匹配有漏) SELECT spider, COUNT(*) AS c FROM dede_spider_log WHERE created >= UNIX_TIMESTAMP(NOW()) - 86400 GROUP BY spider; -- 校验二:同一个 url_md5 在 1 小时内是否被重复记录(重复率高说明采集入口挂了两次) SELECT url_md5, COUNT(*) AS c FROM dede_spider_log WHERE created >= UNIX_TIMESTAMP(NOW()) - 3600 GROUP BY url_md5 HAVING c > 3 ORDER BY c DESC LIMIT 20; -- 校验三:状态码分布,404 超过 10% 就要回去查站内死链 SELECT status, COUNT(*) AS c FROM dede_spider_log WHERE created >= UNIX_TIMESTAMP(NOW()) - 7 * 86400 GROUP BY status ORDER BY c DESC;第一条返回里如果有空spider的行,说明UaPool的匹配表漏了某个特征串;第二条重复率高,通常是模板里挂了两个采集代码,或者日志补录任务和实时采集重叠了;第三条最直接,404 占比高的时候,先去跑一遍站内链接检查,别急着调抓取频率——蜘蛛来了发现是死链,来十次也没用。
6.2 漏斗 SQL 与指标口径
真正能拿来做决策的是下面这条:
-- 近 7 天漏斗:被抓取 → 进过推送队列 → 推送成功,附带平均页面生成耗时 SELECT COUNT(DISTINCT l.url_md5) AS `被抓取`, COUNT(DISTINCT p.url_md5) AS `进过队列`, COUNT(DISTINCT CASE WHEN p.state = 2 THEN p.url_md5 END) AS `推送成功`, ROUND(AVG(l.cost_ms), 1) AS `平均耗时ms`, ROUND(SUM(l.status = 404) / COUNT(*) * 100, 2) AS `404占比` FROM dede_spider_log l LEFT JOIN dede_spider_push p ON p.url_md5 = l.url_md5 WHERE l.created >= UNIX_TIMESTAMP(NOW()) - 7 * 86400;看这条结果的方式是「逐段衰减」。被抓取到进过队列掉得厉害,问题在池子——URL 没入池或者筛选规则太狠;进过队列到推送成功掉得厉害,问题在接口——配额、token、退避策略;三个数都不低但收录还是慢,那就不是插件能解决的了,得回头看内容质量和站内结构。平均耗时ms这个数我一般盯着 800ms 这条线,超过就说明页面生成有慢查询,蜘蛛抓一次的成本变高,抓取预算会被压缩。
进阶用法上有两个我自己常用的小技巧。一是把spider_log按url_md5聚合成「页面级抓取频次」,找出那些被抓了十几次但一直没被推送的页面——这些往往是高价值但被遗漏的,手动补进池子效果最明显。二是把cost_ms按栏目分组,找出最慢的那个栏目单独优化,比全站优化见效快得多。
从那以后我每次接手一个 dede 站,第一件事都是把爬行日志和推送队列先跑起来,跑满三天再谈内容策略——没有这份数据的任何 SEO 判断,本质都是猜。插件包和建表脚本放在同一份资源里,按第 3 章的四步走一遍,能跑通一条记录就算成功。希望帮到你。
本文还有配套的精品资源,点击获取