Python分析独立站访问日志,3类页面别再投广告

独立站后台一般只告诉你两件事:这个页面来了多少人,这个月收到几条询盘。它不会告诉你这两件事之间的关系,也不会告诉你钱该往哪个页面投。

访问日志里其实有答案。把日志和询盘记录按落地页对齐,就能算出每个页面的询盘密度,然后一眼看到哪些页面在空转。

下面这套脚本大约六十行,只用标准库,跑在 Python 3.8 以上。

准备三样东西

第一样是 Nginx 或 Apache 的访问日志,combined 格式。默认配置下长这样。

复制代码
203.0.113.47 - - [22/Oct/2026:14:03:11 +0800] "GET /products/stainless-flange HTTP/1.1" 200 5120 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."

第二样是询盘记录,导成 CSV,至少要有落地页这一列。多数建站工具的表单后台能导,字段名不一定叫 path,统一成 path 就行。

复制代码
path,company,date
/contact,buyer1,2026-10-03
/products/,buyer2,2026-10-07

第三样是一个口径决定:询盘算在哪个页面上。表单提交发生在 /contact,但客户是在产品页看完才点的。这套脚本按表单页面统计,也就是最后一步。要按来源页统计,得另外记 referer,那是另一件事。

解析日志:三个过滤条件

原始日志不能直接用,至少要做三件事。

复制代码
LOG_RE = re.compile(
    r'(?P<ip>\S+) \S+ \S+ \[(?P<time>[^\]]+)\] '
    r'"(?P<method>[A-Z]+) (?P<path>\S+)[^"]*" '
    r'(?P<status>\d{3}) (?P<size>\S+) '
    r'"[^"]*" "(?P<ua>[^"]*)"'
)

BOT_RE = re.compile(r'bot|spider|crawl|slurp|facebookexternalhit|headless', re.I)

正则要把 UA 单独抓出来,别拿整行去匹配 bot 关键词。整行匹配会把 referer 里带「robot」字样的正常请求也误杀,我第一版就是这么写的,少统计了大约一成的真实访问。

三个过滤条件:状态码只留 200,UA 命中 bot 关键词的丢掉,静态资源后缀的丢掉。第三条容易被忘,一个页面带二十张图,日志里就是二十一条记录,全算进去 PV 会虚高好几倍。

复制代码
def parse_log(path):
    rows = []
    with open(path, encoding='utf-8', errors='replace') as f:
        for line in f:
            m = LOG_RE.match(line)
            if not m:
                continue
            d = m.groupdict()
            if d['status'] != '200':
                continue
            if BOT_RE.search(d['ua']):
                continue
            clean = norm_path(d['path'])
            if clean.endswith(STATIC_EXT):
                continue
            rows.append((d['ip'], clean))
    return rows

聚合与计算

按路径聚合 PV 和独立 IP,然后和询盘数对齐,算出每千次访问的询盘数。用千次而不是百分比,是因为小站的分母太小,百分比会显示成一堆零点几,看不清差别。

复制代码
def build_report(pv, ips, inq, min_pv=250):
    out = []
    for p, n in pv.items():
        if n < min_pv:
            continue
        q = inq.get(p, 0)
        rate = q / n * 1000
        out.append((rate, n, len(ips[p]), q, p))
    out.sort()
    return out

min_pv 这个阈值是必须的。访问量太小的页面,一次询盘就能把密度拉到很高,排序会失真。样本只有几百 PV 的站,阈值给 50 到 100;一天几千 PV 的站,给 250 以上。

排序用升序。排在前面的是「来了不少人但没询盘」的页面,那才是要看的。

跑一遍样本数据

我自己造了一份样本日志,7295 行,混了 600 条爬虫和 120 条 404,加上 22 条真实询盘记录。写完先跑了一遍,两个版本的输出逐条对过。阈值给 250。

复制代码
有效请求 4975 条,页面 6 个,参与统计 6 个(PV>=250)
path                                       PV     UV     询盘      每千次询盘
/products/stainless-flange                405    203      0       0.00
/about                                    620    224      0       0.00
/                                        2140    240      6       2.80
/products                                 830    236      3       3.61
/blog/dnv-flange-standard                 530    213      2       3.77
/contact                                  450    205     11      24.44

第一行就是答案。一个产品详情页,一天 405 次访问、203 个独立 IP,零询盘。首页的询盘密度是 2.8,这个页面是 0。

这类页面通常有三种情况,处理方式完全不一样。

**第一种,页面承接不了。**客户搜具体规格进来,页面上只有一张白底图和一句欢迎咨询。他要的尺寸表、材质、交期、检测记录一样没有。这种页面最值得改,改完见效也最快。

**第二种,词和页面对不上。**页面被一个泛词带流量,来的都是同行和比价的。日志看不出这一点,要去看搜索词报告,那是另一个数据源。

**第三种,这个页面本来就不该承担转化。**像 /about 那种,620 PV 零询盘很正常,客户看完公司介绍不会立刻下单,他会回产品页。这类页面不要拿询盘密度去考核,否则你会把有用的页面优化坏。

把三种分开之后,广告该投哪儿也就清楚了。第一种先改再投,第二种先换词,第三种根本不投。

一个把最重要页面藏起来的坑

上面那个结果能出来,是因为我先修了一个 bug。原始版本跑同样的数据,结果是这个。

复制代码
有效请求 4975 条,页面 7 个,参与统计 5 个(PV>=250)
path                                       PV     UV     询盘      每千次询盘
/about                                    620    224      0       0.00
/                                        2140    240      6       2.80
/products/                                830    236      3       3.61
/blog/dnv-flange-standard                 530    213      2       3.77
/contact                                  450    205     11      24.44

注意看,/products/stainless-flange 没了。页面数写着 7 个,参与统计只有 5 个,中间那两个去哪了。

原因是路径末尾带不带斜杠被当成了两个页面。/products/stainless-flange 有 220 条,/products/stainless-flange/ 有 185 条,各自都低于 250 的阈值,双双被过滤掉。合并起来是 405 条,本来该排第一的页面,就这样消失了。

这个 bug 特别容易漏,因为程序不报错。它不报错,它只是安静地少给你一行,而少的恰好是你最该看的那一行。

修法是一行。

复制代码
def norm_path(p):
    p = p.split('?')[0].rstrip('/')
    return p or '/'

去查询串,去末尾斜杠,空字符串兜回根路径。三件事都得做,少一件就会出别的错。

修完又冒出第二个坑

把日志这边规范化之后,我重跑,结果 /products 的询盘从 3 变成 0 了。

因为询盘表里写的是 /products/,带斜杠,日志聚合出来的键是 /products,不带斜杠,两边对不上,q 取不到值,全算成零。它的每千次询盘从 3.61 掉到 0.00,看上去像是个新发现的问题页面。

规范化只做一半,等于把错误从一个地方搬到另一个地方。两处都要过同一个函数。

复制代码
def load_inquiries(path_csv):
    inq = defaultdict(int)
    with open(path_csv, encoding='utf-8') as f:
        for row in csv.DictReader(f):
            inq[norm_path(row['path'])] += 1
    return inq

这类问题在数据对齐里很常见。两份数据来自两个系统,看起来字段同名,实际格式总有一点差别。斜杠、大小写、末尾空格、URL 编码、http 与 https,任何一个不一致都会让关联失败,而且失败的样子是「值为零」,不是「报错」。

所以跑完之后要先做一次 sanity check:询盘总数对不对。日志里关联到的询盘数加起来,应该等于询盘表的总行数。不等就说明有关联不上的,先修对齐,别看结论。

这套脚本的三个局限

**看不到「看了但没提交」。**日志只有请求,没有注意力。一个页面可能被认真读了五分钟然后关掉,也可能打开两秒就跳走,日志里都是 1 条 200。要区分得引入前端埋点,那是另一套东西。

**UV 是拿 IP 去重,不精确。**同一个公司的采购部可能共用一个出口 IP,算成 1 个人。移动网络切换基站又会把一个人算成好几个。要准确得用 cookie 或者设备指纹,日志层面做不到。

**单页应用会失真。**如果站点是 SPA,路由在前端切换,日志里只有一条 /,所有子页面都不出现。这种情况要先确认日志里有没有子页面的请求记录,没有的话这套方法用不了,得从别的地方拿数据。

跑之前先做一件事

别急着改脚本,先确认你的日志里到底有没有 UA 字段。有些服务器的日志格式被改过,只留了 IP、时间、路径、状态码。没有 UA 就过滤不了爬虫,结论会严重偏差。可以先 head 几行看一眼,格式不对的话先去 nginx.conf 里把 log_format 补上,等新日志攒几天再跑。

脚本全文在上面各段里,拼起来就能跑。样本数据和脚本我放在同一个目录,改两个路径参数就能换成自己的数据。跑出来的第一行如果是个高 PV 零询盘的产品页,那基本就找对地方了。

顺带说一句,这类日志分析在独立站上价值比在平台店铺上大,因为平台不给你原始日志,你只能用它的后台报表,而报表的维度是它定的。自己的站,数据在自己手里,能问的问题也就多一些。

你跑完之后排在第一行的是哪个页面?如果是个 PV 不低、询盘为零的产品页,可以把页面链接贴在评论里,我帮你看问题出在承接、词,还是这个页面本来就不该承担转化。

相关推荐
m0_380474161 小时前
AI 正在离开屏幕:从 AI 眼镜到原生智能体手机,终端入口之争打响
人工智能·智能手机
灵动的艺术1 小时前
如一 Agent 架构解读(十二):记忆系统(下)——记忆的生命周期工程
人工智能·agent·如一
保卫大狮兄1 小时前
简道云 CRM 管理系统:如何做老客户分层经营?新客与老客的策略如何区分?
人工智能·crm·销售
一个处女座的程序猿1 小时前
Agent之GPT-6:GPT-6.1 Sol 解读—让高阶 AI 能力以更低成本落地
人工智能·agent·gpt-6.1 sol
千桐科技1 小时前
大模型落地不“纸上谈兵”:qKnow专业版用原生MCP为AI装上“手脚”
大数据·人工智能·qknow
2601_954811821 小时前
人工智能机器视觉实验箱选型:昇腾算力与工业数据集架构对比
人工智能·架构
小天源1 小时前
智叟云集系统适用场景与业务实践
大数据·人工智能
Juicedata1 小时前
面向 EB 级 AI 存储:JuiceFS 企业版架构演进
数据库·人工智能·架构
EDPJ2 小时前
(2026|COLM|Cornell,跨层/连续块共享专家池,跨层专家复用,扩大路由组合空间,深度 embedding)MoRE:专家复用 MoE
人工智能