ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

B站用户行为分析系统源码解析:Python爬虫与数据可视化实战

B站用户行为分析系统源码解析:Python爬虫与数据可视化实战 简介这是一套面向课程设计与Python后端学习者的B站用户行为分析系统完整项目源码适合数据科学、机器学习方向的学生和开发者作为实战参考。项目通过爬虫采集观看历史、弹幕、评论等行为数据结合Pandas、Matplotlib完成清洗与可视化并尝试用聚类、关联规则等方法挖掘用户偏好与内容流行趋势最终输出用户画像和内容推荐列表配套界面可查看分析结果与报告。资源包共427个文件约72.41MB以png图表、js脚本、html页面、py源码及pyc缓存为主另含css样式、svg图标、sql建表脚本、pkl模型文件与doc说明文档覆盖数据爬取、处理、分析、可视化及前端展示的完整链路。目前已有252人学习下载读者可据此理解从数据采集到推荐输出的工程结构并借鉴其模块划分与排错思路。1. 从一份 B 站用户行为分析系统源码说起它到底能跑出什么如果你手头正好有一份python项目基于B站用户行为分析系统.zip第一反应大概率是这玩意儿能直接跑吗跑完能出什么图我拆过不少课程设计类的 Python 项目这份属于典型的「爬虫 数据分析 可视化 Web 界面」四件套。它的核心链路很清晰用爬虫抓取 B 站用户的观看历史、弹幕、评论等行为数据落到本地做清洗再用 Pandas 做聚合、Matplotlib 出图最后套一个前端页面把用户画像和内容推荐列表展示出来。适合两类人一是正在做课程设计、需要一份能讲清楚链路的参考实现二是想拿真实场景练 Python 数据分析与可视化的人。但要注意它不是一个开箱即用的成品前端那堆 css 文件和爬虫脚本之间的耦合关系才是你真正要花时间的地方。2. 拆开压缩包先看结构前端资源与后端脚本怎么分工2.1 从 css 文件清单反推前端页面构成项目正文里列了一串样式文件font-awesome.min.css、home.css、icon.css、responsive.css、select2.css、base.css、select2.min.css、style.css、widgets.css、autocomplete.css。这不是随便堆的从命名能反推出前端至少有这么几块文件作用推断对应页面区域base.css全局重置与基础排版所有页面home.css首页专属布局数据概览首页style.css主样式表通用组件responsive.css响应式断点移动端适配widgets.css卡片/面板类组件图表容器select2.cssselect2.min.css下拉选择框增强筛选条件区autocomplete.css输入联想搜索框icon.cssfont-awesome.min.css图标字体导航与按钮这套组合说明前端不是纯静态页面而是带交互筛选的——有下拉、有联想搜索、有响应式。常见做法是后端用 Flask 或 Django 渲染模板前端用 jQuery Select2 做异步筛选。你拿到项目后先确认templates或static目录下有没有对应的 HTML如果没有那前端就是半成品得自己补。2.2 后端脚本的典型分层一个合格的 B 站行为分析项目后端一般分四层你对照着看压缩包里缺哪层# 典型目录结构对照你的压缩包 bilibili_analysis/ ├── spider/ # 爬虫层 │ ├── user_spider.py # 抓用户信息 │ ├── video_spider.py # 抓视频/弹幕/评论 │ └── middleware.py # 请求头、延时、重试 ├── data/ # 数据层 │ ├── raw/ # 原始 json/csv │ └── clean/ # 清洗后数据 ├── analysis/ # 分析层 │ ├── user_profile.py # 用户画像 │ ├── trend.py # 内容趋势 │ └── recommend.py # 推荐列表 ├── web/ # 展示层 │ ├── app.py # Flask 入口 │ ├── static/ # 上面那堆 css │ └── templates/ # html └── requirements.txt如果压缩包里spider和analysis混在一起说明作者图省事你后续改代码会很难受。我一般会先花十分钟把目录理成上面这样再动代码。这一步不做后面调 bug 就是黑匣子。2.3 依赖安装与首次运行的最小闭环先别急着跑全流程用最小闭环验证环境。新建虚拟环境装核心依赖python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install requests pandas matplotlib flask beautifulsoup4 lxml pip freeze requirements.txt参数说明requests负责 HTTP 请求beautifulsoup4lxml做 HTML 解析pandas做数据清洗matplotlib出图flask起 Web 服务。如果项目里用了 Scrapy把requests换成scrapy但课程设计级别用 requests 足够。装完先跑一个单文件测试# test_env.py import requests, pandas as pd, matplotlib print(requests:, requests.__version__) print(pandas:, pd.__version__) print(matplotlib:, matplotlib.__version__) # 测试能否拿到 B 站公开接口的响应结构 resp requests.get( https://api.bilibili.com/x/web-interface/view, params{bvid: BV1xx411c7mD}, headers{User-Agent: Mozilla/5.0} ) print(status:, resp.status_code) print(keys:, list(resp.json().keys())[:5])逻辑说明这段不抓真实用户数据只验证两件事——依赖装对了没以及你的网络环境能不能正常访问公开接口。params里的bvid随便填一个公开视频号即可重点看status是不是 200。如果返回 412 或 -403说明请求头或频率有问题先解决这个再往下走。3. 爬虫层实操请求构造、字段提取与频率控制3.1 请求头与参数怎么设才不容易翻车B 站公开接口对请求头有基本校验缺User-Agent和Referer大概率吃 412。我一般固定这么写import requests, time, random HEADERS { User-Agent: ( Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ), Referer: https://www.bilibili.com/, Accept: application/json, text/plain, */*, } def fetch_json(url, paramsNone, retry3): for i in range(retry): try: r requests.get(url, headersHEADERS, paramsparams, timeout10) if r.status_code 200: return r.json() # 412 通常是风控退避后重试 if r.status_code 412: time.sleep(2 ** i random.random()) except requests.RequestException as e: print(f第{i1}次失败: {e}) time.sleep(1) return None参数说明timeout10防止单请求卡死retry3配合指数退避2**i第一次等 1 秒、第二次 2 秒、第三次 4 秒加随机小数避免规律性请求。Referer必须带否则部分接口直接拒绝。这套写法比裸requests.get稳得多血泪经验。3.2 用户行为字段的提取与落盘拿到 JSON 后别整包存只留分析要用的字段。以视频评论为例import pandas as pd def parse_comments(raw_json): rows [] for item in raw_json.get(data, {}).get(replies, []) or []: rows.append({ user_id: item[member][mid], username: item[member][uname], content: item[content][message], like_count: item[like], ctime: item[ctime], video_id: item.get(oid), }) df pd.DataFrame(rows) if not df.empty: df[ctime] pd.to_datetime(df[ctime], units) return df # 落盘 df parse_comments(fetch_json( https://api.bilibili.com/x/v2/reply, params{type: 1, oid: 123456, pn: 1, ps: 20} )) df.to_csv(data/raw/comments_p1.csv, indexFalse, encodingutf-8-sig)逻辑说明oid是视频的 aidpn是页码ps是每页条数。encodingutf-8-sig是为了 Excel 打开不乱码这个细节很多人栽过。字段只留六个后续做用户画像和内容趋势都够用。注意replies可能为None所以用or []兜底。3.3 频率控制与断点续爬课程设计最容易忽略的就是频率。我一般设成每请求间隔 1.5 到 3 秒随机并且把已抓的页码记到本地断了能续import os, json def crawl_pages(oid, start1, end10, cachedata/raw/_progress.json): done set() if os.path.exists(cache): done set(json.load(open(cache))) for pn in range(start, end 1): if pn in done: continue data fetch_json( https://api.bilibili.com/x/v2/reply, params{type: 1, oid: oid, pn: pn, ps: 20} ) if data: parse_comments(data).to_csv( fdata/raw/comments_{oid}_{pn}.csv, indexFalse, encodingutf-8-sig ) done.add(pn) json.dump(list(done), open(cache, w)) time.sleep(random.uniform(1.5, 3.0))参数说明cache文件记录已完成页码重跑时自动跳过。random.uniform(1.5, 3.0)是随机间隔比固定 sleep 更自然。这套断点续爬在抓多页数据时能省大量时间尤其是中途被限流的时候。4. 分析层实操用户画像、趋势与推荐列表怎么算4.1 用 Pandas 做用户活跃度与偏好聚合爬完数据后核心分析就三块用户活跃度、内容偏好、时间趋势。先做用户维度的聚合import pandas as pd df pd.read_csv(data/raw/comments_all.csv, parse_dates[ctime]) # 用户活跃度评论数 获赞数 user_stats df.groupby([user_id, username]).agg( comment_count(content, count), total_likes(like_count, sum), first_active(ctime, min), last_active(ctime, max), ).reset_index() # 活跃天数跨度 user_stats[active_days] ( user_stats[last_active] - user_stats[first_active] ).dt.days 1 # 简单分层高频/中频/低频 def level(n): if n 10: return 高频 if n 3: return 中频 return 低频 user_stats[level] user_stats[comment_count].apply(level) user_stats.to_csv(data/clean/user_profile.csv, indexFalse, encodingutf-8-sig)逻辑说明groupby按用户聚合agg同时算评论数、总赞数、首末活跃时间。active_days用时间差加 1避免同天活跃算成 0 天。分层阈值 10 和 3 是经验值你可以根据数据量调。这份user_profile.csv就是用户画像的基础表。4.2 内容趋势与可视化出图趋势分析按天聚合再用 Matplotlib 出图import matplotlib matplotlib.use(Agg) # 无界面环境必须加 import matplotlib.pyplot as plt df[date] df[ctime].dt.date daily df.groupby(date).size().reset_index(namecount) plt.figure(figsize(10, 4)) plt.plot(daily[date], daily[count], markero, linewidth1.5) plt.title(Daily Comment Trend) plt.xlabel(Date) plt.ylabel(Comments) plt.xticks(rotation45) plt.tight_layout() plt.savefig(web/static/trend.png, dpi120)参数说明matplotlib.use(Agg)是关键服务器或无显示器环境不加这句直接报错。dpi120平衡清晰度和文件大小。tight_layout()防止日期标签被裁掉。图存到static目录前端直接引用。4.3 关联规则做内容推荐的简化实现推荐列表不用上复杂模型课程设计级别用共现统计就够from itertools import combinations from collections import Counter # 假设同一用户评论过的视频集合 user_videos df.groupby(user_id)[video_id].apply(set) pair_counter Counter() for vids in user_videos: if len(vids) 2: for pair in combinations(sorted(vids), 2): pair_counter[pair] 1 # 取共现最高的前 10 对 top_pairs pair_counter.most_common(10) print(top_pairs)逻辑说明combinations生成同一用户看过的视频两两组合Counter统计共现次数。共现越高说明两个视频的受众重叠越大可以作为「看了 A 的人也看 B」的推荐依据。这是关联规则里最朴素的实现不用装mlxtend也能跑。如果项目里用了 Apriori把这段替换掉即可但思路一致。5. 避坑与排查这份源码最容易翻车的五个地方5.1 接口返回 412 或 -403现象请求 B 站接口时状态码 412或 JSON 里code为 -403。原因请求头缺失、频率过高、或 IP 被临时限制。解决补全User-Agent和Referer把间隔调到 2 秒以上加重试退避。如果持续 412换时间段再跑别硬刚。5.2 前端页面样式全丢现象Flask 起来后页面能打开但没有任何样式图标也不显示。原因static目录路径没配对或url_for(static, filename...)写错。解决确认app.py里static_folder指向正确HTML 里所有 css 引用走url_for。font-awesome.min.css还要确认字体文件路径缺字体图标就是方块。5.3 中文乱码现象CSV 用 Excel 打开全是乱码或图表标题中文变方块。原因编码不一致或 Matplotlib 没设中文字体。解决落盘统一utf-8-sigMatplotlib 加plt.rcParams[font.sans-serif] [SimHei]和plt.rcParams[axes.unicode_minus] False。Linux 没 SimHei 就装fonts-noto-cjk。5.4 爬虫中途断掉要重头跑现象抓了几百页网络一抖全白干。原因没有断点记录。解决用第 3.3 节的_progress.json方案每完成一页写一次。重跑时自动跳过已完成页码。这个后悔药一定要提前吃。5.5 依赖版本冲突现象pip install报错或跑起来ImportError。原因requirements.txt里版本锁太死或 Python 版本不匹配。解决先看项目要求的 Python 版本课程设计一般 3.8 都行。装依赖时先不锁版本跑通后再pip freeze固化。遇到lxml装不上换pip install lxml --pre或直接用html.parser替代。6. 进阶技巧把分析结果接进 Web 界面并做参数化筛选最后一章说个实用的怎么把分析结果真正接到前端并且支持按时间、按用户分层筛选。很多人跑完脚本就停了图表是静态图片没法交互。我一般会加一个轻量接口# web/app.py from flask import Flask, render_template, request, jsonify import pandas as pd app Flask(__name__) df pd.read_csv(data/clean/user_profile.csv) app.route(/) def index(): return render_template(home.html) app.route(/api/users) def api_users(): level request.args.get(level, ) min_comments int(request.args.get(min_comments, 0)) sub df[df[comment_count] min_comments] if level: sub sub[sub[level] level] return jsonify(sub.head(100).to_dict(orientrecords)) if __name__ __main__: app.run(debugTrue, port5000)逻辑说明/api/users接收level和min_comments两个查询参数返回筛选后的前 100 条记录。前端用fetch调这个接口配合select2做下拉筛选就能实现动态刷新。debugTrue只在开发时开部署要关掉。前端调用示例// static/js/filter.js async function loadUsers(level, minComments) { const url /api/users?level${level}min_comments${minComments}; const resp await fetch(url); const data await resp.json(); const tbody document.querySelector(#user-table tbody); tbody.innerHTML data.map(u trtd${u.username}/tdtd${u.comment_count}/tdtd${u.level}/td/tr ).join(); } document.querySelector(#level-select).addEventListener(change, e { loadUsers(e.target.value, 0); });参数说明level对应下拉框的值min_comments可以再接一个输入框。fetch返回 JSON 后直接拼表格不用引入前端框架。这套组合在课程设计里足够撑起「交互式分析」的演示效果。验证方法启动 Flask 后访问http://127.0.0.1:5000/api/users?level高频min_comments5看返回的 JSON 里comment_count是不是都大于等于 5level是不是都是高频。对得上说明筛选逻辑通了。再打开首页切换下拉框表格实时刷新整个链路就闭环了。从那以后我每次拿到这类课程设计源码都强制先跑一遍最小闭环再按「爬虫 → 清洗 → 分析 → 接口 → 前端」的顺序逐层验证绝不一次性全跑。希望帮到你。本文还有配套的精品资源点击获取
返回列表