简介:这是一款面向站长、SEO 从业者与运维人员的蜘蛛日志在线分析工具源码,用于解决搜索引擎爬虫抓取记录难以直观查看的问题。用户可将网站访问日志内容粘贴到工具中,一键分析即可快速掌握各搜索引擎蜘蛛的抓取频次与路径,适用于日常站点健康监测与收录优化场景。压缩包共 105 个文件,约 1.47MB,以 38 个 jpg、31 个 gif、14 个 png 等图片素材为主,配合 15 个 js 脚本、4 个 css 样式表及 1 个 html 页面构成完整前端界面,另有少量 txt、htc 等辅助文件,整体结构轻量、便于二次修改与本地部署。目前已有 89 人学习下载。源码完整呈现了日志解析与结果展示的实现思路,读者可据此了解前端交互逻辑、页面布局与资源组织方式,并在此基础上扩展统计维度或适配自有服务器环境,适合希望自建轻量日志分析工具的开发者参考借鉴。
1. 蜘蛛日志在线分析工具:从一堆 access.log 到能看懂的流量画像
手里拿到一个蜘蛛日志在线分析工具源码.zip,第一反应往往不是“这玩意儿能干嘛”,而是“我手上那几百兆 access.log 终于有地方倒了”。搜索引擎蜘蛛每天爬你站点,留下的日志里藏着抓取频次、抓取路径、状态码分布、响应耗时,甚至能反推出哪些页面被反复抓、哪些页面被蜘蛛嫌弃。问题在于,原始日志是纯文本,一行一条,几万行起步,肉眼翻等于自虐。
这个工具要解决的就是这件事:把 Nginx 或 Apache 的 access.log 丢进去,在线解析出蜘蛛类型、抓取量趋势、状态码占比、热门抓取 URL、单蜘蛛行为画像。适合谁?做 SEO 的技术同学、运维里兼着看爬虫流量的、以及自己搭站想知道搜索引擎到底爬了什么的独立开发者。它不替代 ELK,但胜在轻、能本地跑、源码可控,改两行就能接自己的日志格式。下面按“先跑通、再调参、最后避坑”的顺序拆。
2. 日志解析与蜘蛛识别:正则、UA 库和字段映射怎么定
2.1 先搞清楚 access.log 里到底有什么
Nginx 默认的combined格式长这样:
127.0.0.1 - - [10/Oct/2024:13:55:36 +0800] "GET /article/123 HTTP/1.1" 200 5120 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"拆开看是 9 个字段:远端 IP、标识、用户、时间、请求行、状态码、响应体大小、Referer、User-Agent。蜘蛛识别几乎全靠最后一个 UA 字段,但这里有个血泪经验:不要只匹配Googlebot这种裸词。真实流量里有人伪造 UA,也有正经蜘蛛的 UA 里带版本号和说明 URL。常见做法是维护一张“蜘蛛名 → UA 特征正则”的映射表,而不是写死一个字符串。
Apache 的格式略有差异,时间字段是[10/Oct/2024:13:55:36 +0800],请求行同样是"GET /path HTTP/1.1"。如果你的日志用了自定义log_format,字段顺序可能变,这时候工具里的正则就得跟着改。我一般会先跑一条命令确认格式:
# 取前 3 行看字段结构,确认是 combined 还是自定义格式 head -n 3 /var/log/nginx/access.log如果输出里字段数量对不上,别急着改代码,先看 Nginx 配置里的log_format定义,把字段顺序抄下来,再对应调整解析正则的捕获组。
2.2 用 Python 写一个可复用的解析器
源码里通常会有parser.py或类似文件,核心逻辑就是一行日志 → 一个字典。下面这段是我自己常用的写法,和工具源码思路一致,可以直接抄:
import re from datetime import datetime # 匹配 Nginx combined 格式,命名捕获组方便后续取值 LOG_PATTERN = re.compile( r'(?P<ip>\S+) \S+ \S+ ' r'\[(?P<time>[^\]]+)\] ' r'"(?P<method>\S+) (?P<path>\S+) \S+" ' r'(?P<status>\d{3}) (?P<size>\d+|-) ' r'"(?P<referer>[^"]*)" ' r'"(?P<ua>[^"]*)"' ) def parse_line(line): m = LOG_PATTERN.match(line.strip()) if not m: return None # 格式不匹配的行直接丢弃,不要抛异常中断整个文件 d = m.groupdict() d['status'] = int(d['status']) d['size'] = int(d['size']) if d['size'] != '-' else 0 # 时间转成 datetime,方便后面按小时聚合 d['time'] = datetime.strptime(d['time'], '%d/%b/%Y:%H:%M:%S %z') return d逻辑说明:命名捕获组让字段取值不依赖位置索引,以后日志格式微调(比如多一个字段)只要改正则就行。size为-时置 0,避免后面求和报错。时间解析用%d/%b/%Y:%H:%M:%S %z,注意%b是英文月份缩写,如果你的日志是中文月份,得先做映射。
参数说明:LOG_PATTERN里的\S+匹配非空白,[^\]]+匹配到右括号前,[^"]*匹配引号内内容。这三个是解析日志最常用的模式,改格式时优先动它们。
2.3 蜘蛛识别表怎么建才不容易翻车
识别蜘蛛不要用if 'googlebot' in ua.lower()这种写法,大小写和伪造都扛不住。我一般用一张表:
| 蜘蛛名 | UA 特征正则 | 备注 |
|---|---|---|
| Googlebot | Googlebot/\d+\.\d+ | 带版本号,伪造者常漏 |
| Bingbot | bingbot/\d+\.\d+ | 同上 |
| Baiduspider | Baiduspider/\d+\.\d+ | 国内站点重点 |
| YandexBot | YandexBot/\d+\.\d+ | 外贸站关注 |
| Sogou spider | Sogou web spider/\d+ | 注意空格 |
| 360Spider | 360Spider | 无版本号,需配合 IP 段 |
SPIDER_RULES = [ ('Googlebot', re.compile(r'Googlebot/\d+\.\d+', re.I)), ('Bingbot', re.compile(r'bingbot/\d+\.\d+', re.I)), ('Baiduspider', re.compile(r'Baiduspider/\d+\.\d+', re.I)), ('YandexBot', re.compile(r'YandexBot/\d+\.\d+', re.I)), ('Sogou', re.compile(r'Sogou web spider/\d+', re.I)), ('360Spider', re.compile(r'360Spider', re.I)), ] def detect_spider(ua): for name, pattern in SPIDER_RULES: if pattern.search(ua): return name return 'Other'逻辑说明:re.I忽略大小写,search而不是match,因为 UA 前面还有一堆 Mozilla 兼容串。返回Other而不是None,方便后面统计“非蜘蛛流量”占比。
参数说明:版本号\d+\.\d+是区分真假蜘蛛的第一道门槛,伪造 UA 经常只写Googlebot不带版本。第二道门槛是 IP 反查,但离线工具一般不做,因为反查慢且依赖网络,可以留到进阶章讲。
3. 在线分析服务怎么搭:从 Flask 接口到前端图表
3.1 最小可跑的服务骨架
源码里如果是 Web 工具,大概率是 Flask 或 FastAPI。下面用 Flask 写一个最小可跑的上传 + 分析接口,思路和工具源码一致:
from flask import Flask, request, jsonify from parser import parse_line, detect_spider from collections import Counter, defaultdict app = Flask(__name__) @app.route('/analyze', methods=['POST']) def analyze(): file = request.files.get('logfile') if not file: return jsonify({'error': 'no file'}), 400 spider_counter = Counter() status_counter = Counter() path_counter = Counter() hourly = defaultdict(int) for raw in file.stream: line = raw.decode('utf-8', errors='ignore') rec = parse_line(line) if not rec: continue spider = detect_spider(rec['ua']) spider_counter[spider] += 1 status_counter[rec['status']] += 1 path_counter[rec['path']] += 1 hourly[rec['time'].strftime('%Y-%m-%d %H')] += 1 return jsonify({ 'spider': spider_counter.most_common(), 'status': status_counter.most_common(), 'top_paths': path_counter.most_common(20), 'hourly': sorted(hourly.items()), })逻辑说明:file.stream逐行读,不一次性 load 进内存,几百兆日志也不会爆。errors='ignore'处理编码混杂的日志行。四个 Counter 分别对应蜘蛛分布、状态码分布、热门路径、按小时趋势。
参数说明:top_paths取前 20,太多前端画不下;hourly用%Y-%m-%d %H做 key,排序后直接喂给图表库。如果日志跨月,这个 key 也能正确排序。
3.2 前端图表用 ECharts 还是 Chart.js
工具源码里前端一般两种选型:ECharts 功能全、中文文档好,适合饼图 + 折线图组合;Chart.js 轻、上手快,适合只画一两个图。我一般选 ECharts,因为蜘蛛分布饼图和小时趋势折线图能放一个页面,配置项也直观。
// 蜘蛛分布饼图,数据来自 /analyze 返回的 spider 数组 const spiderChart = echarts.init(document.getElementById('spider-pie')); spiderChart.setOption({ tooltip: { trigger: 'item' }, series: [{ type: 'pie', radius: '60%', data: spiderData.map(([name, value]) => ({ name, value })), }], });逻辑说明:spiderData是后端返回的[[name, count], ...],直接 map 成 ECharts 需要的{name, value}结构。radius: '60%'留出标签空间,蜘蛛名较长时不会挤在一起。
参数说明:如果蜘蛛种类超过 8 种,饼图会糊,这时候改成横向柱状图更清楚。ECharts 的type: 'bar'配合yAxis: { type: 'category' }即可。
3.3 大文件上传的三个必调参数
日志文件动辄几百兆,上传这一步最容易翻车。三个参数必须调:
| 参数 | 位置 | 建议值 | 原因 |
|---|---|---|---|
MAX_CONTENT_LENGTH | Flask 配置 | 500 * 1024 * 1024 | 默认无限制,但反向代理会拦 |
client_max_body_size | Nginx 配置 | 500m | 不调直接 413 |
timeout | 前端 fetch / axios | 300000 | 默认 30s 不够解析 |
app.config['MAX_CONTENT_LENGTH'] = 500 * 1024 * 1024 # 500MB逻辑说明:Flask 这个配置是硬上限,超过直接 413。Nginx 的client_max_body_size是另一道坎,两个都要改。前端超时设 5 分钟,给解析留足时间。
参数说明:500MB 是经验值,再大建议走分片上传或直接读服务器本地文件路径,而不是浏览器上传。
4. 避坑与排查:蜘蛛日志分析里最容易翻车的 5 个点
4.1 现象:蜘蛛统计里出现大量“Other”,但明明有 Googlebot
原因:UA 正则写得太死,比如只匹配Googlebot/2.1,而实际流量里是Googlebot/2.1 (+http://www.google.com/bot.html),search能命中,但如果用了fullmatch就挂了。另一个原因是日志里 UA 字段被截断,Nginx 默认不截断,但有些 CDN 回源日志会。
解决:把fullmatch改成search,并在解析前打印几行 UA 确认完整性。如果 UA 确实被截断,只能靠 IP 段辅助判断,但离线工具一般不做,建议在日志采集层就保证 UA 完整。
4.2 现象:状态码统计里 200 占比异常低,大量 499
原因:499 是 Nginx 特有的“客户端主动断开”,蜘蛛抓取时如果响应太慢会主动断,说明你服务器响应时间有问题,不是蜘蛛的问题。很多人第一次看到 499 会以为是蜘蛛被封,其实是自己慢。
解决:把 499 单独列出来,结合响应时间字段(如果日志格式里有$request_time)一起看。如果 499 集中在某些路径,优先优化那些路径的响应速度。
4.3 现象:按小时趋势图出现巨大尖峰,但当天流量正常
原因:时间解析时区没处理。%z捕获了+0800,但strftime('%Y-%m-%d %H')输出的是本地时间,如果服务器 UTC 而日志是 +0800,就会错位 8 小时,导致尖峰。
解决:解析时统一转成 UTC 或统一转成日志时区,再聚合。简单做法是d['time'] = d['time'].astimezone(timezone.utc),然后聚合用 UTC 小时。
4.4 现象:热门路径里全是静态资源,看不到内容页
原因:蜘蛛确实会抓 CSS、JS、图片,但分析时如果不排除,热门路径就被/static/刷屏了。
解决:加一个过滤规则,排除常见静态后缀:
STATIC_EXT = re.compile(r'\.(css|js|png|jpg|jpeg|gif|ico|svg|woff2?)$', re.I) def is_static(path): return bool(STATIC_EXT.search(path))逻辑说明:在统计path_counter前判断if not is_static(rec['path']),把静态资源排除。参数说明:后缀列表按自己站点实际情况增删,比如有.webp就加上。
4.5 现象:解析到某一行直接报错退出,整个文件分析失败
原因:日志里混入了非标准行,比如 Nginx 启动时的提示信息、或者被截断的半行。
解决:parse_line返回None时continue,不要抛异常。同时加一个计数器记录丢弃行数,分析结果里返回dropped_lines,方便判断日志质量。如果丢弃行数超过 1%,说明日志格式和正则不匹配,得回去改正则。
5. 进阶:把蜘蛛日志变成可行动的 SEO 信号
跑通基础分析只是第一步,真正有价值的是从日志里读出“蜘蛛喜欢什么、讨厌什么”。我一般会加三个进阶指标。
第一个是抓取频次 vs 页面更新频次。如果某个页面每周更新一次,但蜘蛛每天抓三次,说明蜘蛛在浪费预算;反过来,每天更新但蜘蛛一周才来一次,说明抓取预算没给够。实现上按 URL 聚合抓取次数,再和 CMS 的更新时间做对比,这个对比表比任何 SEO 工具都直接。
第二个是状态码趋势。单独看一天 404 没意义,但看 404 按天变化就有意义。如果某天 404 突然翻倍,大概率是改版或删页面没做跳转。用defaultdict(list)按天存状态码,前端画堆叠折线图。
from collections import defaultdict daily_status = defaultdict(lambda: defaultdict(int)) # 在解析循环里: day = rec['time'].strftime('%Y-%m-%d') daily_status[day][rec['status']] += 1逻辑说明:defaultdict(lambda: defaultdict(int))两层嵌套,第一层按天,第二层按状态码。输出时转成{day: {status: count}}给前端。
参数说明:如果日志跨年,%Y-%m-%d足够;如果只看近期,可以改成%m-%d让图更紧凑。
第三个是蜘蛛 IP 验证。前面说过离线工具一般不做反查,但如果你有已知蜘蛛 IP 段列表(各搜索引擎官方都有公布),可以做一个简单校验:UA 说是 Googlebot 但 IP 不在 Google 段内,标记为“疑似伪造”。这个不需要联网,把 IP 段写成 CIDR 列表,用ipaddress模块判断即可。
import ipaddress GOOGLEBOT_CIDRS = [ipaddress.ip_network('66.249.64.0/19')] # 示例段,实际按官方列表补全 def is_real_googlebot(ip): addr = ipaddress.ip_address(ip) return any(addr in net for net in GOOGLEBOT_CIDRS)逻辑说明:ipaddress.ip_network解析 CIDR,addr in net判断归属。参数说明:CIDR 列表要按官方公布的最新段维护,这里只是示例,实际使用时去各搜索引擎的官方文档抄全。
最后一个习惯:每次分析完,把dropped_lines、Other占比、499 数量这三个数记下来。这三个数稳定,说明日志采集和解析都健康;任何一个突然变化,先查采集层,再查解析层,最后才看业务层。这个顺序帮我省过很多次无效排查。希望帮到你。
本文还有配套的精品资源,点击获取