☰
Python图书馆借阅数据分析:从爬虫到推荐系统的完整实战
2026/9/30 16:34:29 网站建设 项目流程

简介:一份面向专科与本科毕业生的原创毕业论文,围绕Python在图书馆借阅数据分析中的实际应用展开,结合网络爬虫、数据挖掘与Django框架,完整覆盖数据采集、清洗、可视化、统计分析与系统设计实现等环节。全文经降重处理且超过万字,结构包含绪论、国内外研究现状、相关技术与工具、图书馆借阅数据获取与处理、数据分析方法、系统设计与实现等章节,并涉及Pandas、Numpy、Matplotlib、Scikit-learn等常用库的运用,适合作为毕业设计写作参考、代码实现思路梳理及答辩材料准备。资源为1个docx文档,大小35KB,内含论文完整目录、摘要、图表与文字论述,便于直接查看、编辑和按需修改。已有473人浏览学习,适合需要快速搭建论文框架或借鉴数据分析与Django系统设计思路的专科、本科学生。

1. 图书馆借阅数据分析这个毕业论文资源:值得照着复现的一套完整链路

做毕业设计最怕的不是没想法,而是想法太空、代码拿不出手。这份《基于 Python 的图书馆借阅数据分析设计与实现》瞄准的正是图书馆管理系统里越攒越多的借阅记录——数据躺在数据库里没人用,而它把“从爬虫抓数据 → Pandas 清洗 → 可视化分析 → 协同过滤推荐 → Django 展示”整条链路都走通了。它不是那种只讲概念的论文,而是每一步都有对应工具和代码逻辑的工程型毕设。适合两类人:一类是专科/本科应届生,需要一份能讲清楚、能复现的毕业设计;另一类是刚入门的 Python 数据分析从业者,想在图书馆、校园这类真实业务场景里练手。下面我把这份资源里的技术主线和实操细节拆开讲。

2. 借阅数据从哪来:采集、清洗与入库的全流程

2.1 数据源与字段设计:把图书馆管理系统里的表变成分析底表

论文里提到的借阅数据来源很典型:图书馆管理系统的借阅记录(借书时间、归还时间、书名、书号、作者)、学生信息系统(年级、专业、学院)、图书馆统计报表(借阅排行榜、热门图书)。这三类数据拼起来,才能支撑后续“哪个学院借书多”“什么专业偏好什么类目”这类交叉分析。

我一般会先画一张字段表,把要用的字段固定下来,避免后边分析时缺字段再回头补:

来源关键字段用途
借阅记录表读者编号、图书ISBN、书名、分类号、借书时间、应还时间、续借次数核心分析数据
读者信息表读者编号、年级、学院、专业、读者类型用户画像
图书信息表ISBN、书名、作者、出版社、分类号图书维度关联

实际做毕设时,你可能拿不到真实系统权限,常见做法是用爬虫去抓公开的图书馆 OPAC(联机公共目录检索系统)或者用模拟数据代替。但字段结构尽量保持上述设计,否则后边的分组统计和推荐算法都无从下手。

2.2 爬虫怎么抓:requests + BeautifulSoup 的最小闭环

论文的摘录里明确提到了爬虫技术,这也是很多同学毕设答辩时被问得最多的一块。下面这套写法是抓图书馆检索页最常用的方式,不需要 Selenium 那种重型工具,适合静态页面。

import requests from bs4 import BeautifulSoup import pandas as pd import time def fetch_borrow_records(start_page, end_page): """ 抓取图书馆检索页的借阅记录,返回 DataFrame """ base_url = "http://lib.example.edu.cn/opac/search?page={}" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } rows = [] for page in range(start_page, end_page + 1): resp = requests.get(base_url.format(page), headers=headers, timeout=10) if resp.status_code != 200: continue soup = BeautifulSoup(resp.text, "html.parser") table = soup.find("table", class_="borrow-table") for tr in table.find_all("tr")[1:]: # 跳过表头 tds = tr.find_all("td") if len(tds) >= 5: rows.append({ "isbn": tds[0].text.strip(), "book_name": tds[1].text.strip(), "category": tds[2].text.strip(), "borrow_date": tds[3].text.strip(), "return_date": tds[4].text.strip(), "renew_count": tds[5].text.strip() if len(tds) > 5 else "0" }) time.sleep(1) # 控制请求频率,别把对方服务器打崩 return pd.DataFrame(rows) df = fetch_borrow_records(1, 50) print(df.shape)

几个容易忽略的点:

  • headers里的 User-Agent 必须带,很多图书馆系统会拦截默认的 Python 请求头,返回 403。
  • time.sleep(1)是血泪经验,爬太快 IP 会被封,尤其做毕设期间反复调试,封一次就得等半小时。
  • tds[0].text.strip()里的.strip()不能省,HTML 解析出来的文本经常带空格和换行,不清理后边做匹配会出问题。
  • 如果页面是动态加载的,requests 拿不到表格,这时候再考虑 Selenium 或抓接口,别一上来就上重武器。

2.3 清洗与入库:Pandas 去重、缺失值、时间字段规范化

爬下来的数据大概率是脏的,直接分析就是给自己挖坑。论文第三章的“数据抓取与清洗”环节,实操时按这个顺序处理就够了:

import pandas as pd import sqlite3 # 1. 去重:同一本书同一个人同一天借阅,视为重复记录 df = df.drop_duplicates(subset=["isbn", "borrow_date"]) # 2. 缺失值:借阅日期缺失的记录直接丢弃,没有分析价值 df = df.dropna(subset=["borrow_date", "book_name"]) # 3. 时间字段统一格式:这个字段最坑,后边细说 df["borrow_date"] = pd.to_datetime(df["borrow_date"], format="%Y-%m-%d", errors="coerce") df["return_date"] = pd.to_datetime(df["return_date"], format="%Y-%m-%d", errors="coerce") # 4. 计算借阅时长(天) df["borrow_days"] = (df["return_date"] - df["borrow_date"]).dt.days # 5. 入库 SQLite conn = sqlite3.connect("library_borrow.db") df.to_sql("borrow_records", conn, if_exists="replace", index=False) conn.close()

这里的errors="coerce"很关键:解析失败的日期会被置为 NaT(缺失时间),而不会让整个程序中断。borrow_days是后边做借阅时长分布、超期分析的核心指标,如果因为日期格式不一致算出来是负数,优先检查是不是借书日期和还书日期填反了。

入库建议用 SQLite 而不是 MySQL,零配置、单文件,交毕设时直接把.db文件拷走就能演示。如果导师要求 MySQL,把sqlite3.connect换成pymysql.connect即可,SQL 语句基本不用改。

3. Pandas 与可视化:把借阅行为拆成能写进论文的指标和图表

3.1 指标口径:借阅频次、续借率、时段分布怎么算才严谨

论文摘要里反复提到“借阅偏好、阅读习惯、借阅异常”,但落到代码上,第一步是把这些虚的概念翻译成可计算的指标。我在做类似项目时,最常用的指标就是下面这五个:

指标计算口径业务含义
人均借阅量借阅记录数 ÷ 活跃读者数衡量整体借阅活跃度
图书借阅频次单本图书被借次数找热门图书和滞销图书
续借率续借次数>0的记录数 ÷ 总借阅记录数判断图书是否够借期
时段分布按小时/月份分组计数找借阅高峰时段
分类占比各分类借阅量 ÷ 总借阅量馆藏结构是否合理
import pandas as pd import sqlite3 conn = sqlite3.connect("library_borrow.db") df = pd.read_sql("SELECT * FROM borrow_records", conn) conn.close() # 时间字段已经是清洗后的 datetime,直接提取月份和小时 df["month"] = df["borrow_date"].dt.month df["hour"] = df["borrow_date"].dt.hour # 人均借阅量:每个读者借了多少本 reader_stats = df.groupby("reader_id")["book_name"].count().reset_index() reader_stats.columns = ["reader_id", "borrow_count"] avg_per_reader = reader_stats["borrow_count"].mean() # 每月借阅趋势 monthly_trend = df.groupby("month")["book_name"].count().reset_index() monthly_trend.columns = ["month", "borrow_total"] # 分类借阅排行 category_rank = df.groupby("category")["book_name"].count().sort_values(ascending=False) print("人均借阅量:", round(avg_per_reader, 2)) print(monthly_trend.head()) print(category_rank.head(10))

groupby之后记得用reset_index()把分组键还原成普通列,不然后边用 Matplotlib 画图时,索引列不好引用。sort_values(ascending=False)出来的结果直接就是排行榜,这组数据能写进论文的“实验结果与分析”章节,配合图表展示比纯文字有说服力得多。

3.2 Matplotlib + Seaborn 出图:借阅趋势、分类占比、热力图

可视化是这份论文里最出效果的部分。答辩时老师不一定细看你的算法,但一定会看图表。我常用的搭配是 Seaborn 定样式、Matplotlib 微调细节,两行代码就能出一张能放进论文的图:

import matplotlib.pyplot as plt import seaborn as sns # 中文字体必须设置,否则图表里全是方块 plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"] plt.rcParams["axes.unicode_minus"] = False # 图1:月度借阅趋势折线图 fig, ax = plt.subplots(figsize=(10, 5)) sns.lineplot(data=monthly_trend, x="month", y="borrow_total", marker="o", ax=ax) ax.set_title("2024 年图书馆月度借阅量趋势") ax.set_xlabel("月份") ax.set_ylabel("借阅量(册)") plt.tight_layout() plt.savefig("monthly_trend.png", dpi=150) # 图2:分类借阅占比环形图 fig, ax = plt.subplots(figsize=(8, 8)) category_top = category_rank.head(8) # 取前8类,其余归为“其他” ax.pie(category_top.values, labels=category_top.index, autopct="%1.1f%%", startangle=90, counterclock=False) ax.add_artist(plt.Circle((0, 0), 0.7, color="white")) # 中心挖空,变成环形 ax.set_title("图书分类借阅占比") plt.tight_layout() plt.savefig("category_pie.png", dpi=150)

两个细节值得记一下:

  • plt.rcParams["font.sans-serif"]不设置的话,所有中文标签都会变成方框,这是 Matplotlib 最著名的坑之一,论文里截图出现方框非常尴尬。
  • dpi=150保证导出的 PNG 插入 Word 后不会模糊。答辩 PPT 里如果放原图,150 到 200 之间最合适,太大文件体积也大。

3.3 借阅时段热力图:找出真正的高峰时段

除了趋势和占比,借阅时段分析是这份论文里很有亮点的部分,因为它能直接给图书馆排班和开放时间提供建议。按“星期 × 小时”做一张热力图,一眼就能看出高峰:

import numpy as np # 构造 星期(0-6) × 小时(8-22) 的透视表 df["weekday"] = df["borrow_date"].dt.weekday df["hour"] = df["borrow_date"].dt.hour pivot = df.pivot_table(index="weekday", columns="hour", values="book_name", aggfunc="count", fill_value=0) fig, ax = plt.subplots(figsize=(12, 4)) sns.heatmap(pivot, cmap="YlOrRd", annot=True, fmt="d", linewidths=0.5, ax=ax) ax.set_xticklabels([f"{h}:00" for h in pivot.columns]) ax.set_yticklabels(["周一", "周二", "周三", "周四", "周五", "周六", "周日"]) ax.set_title("借阅时段分布热力图") plt.tight_layout() plt.savefig("borrow_heatmap.png", dpi=150)

fill_value=0一定要加,否则没有借阅记录的时间段会变成 NaN,热力图里显示成灰色空格,影响整体观感。这个图做完,基本就能回答“图书馆几点最挤”“周末要不要开馆”这类问题了,论文的结论章节也顺理成章。

4. 借阅推荐与行为模型:协同过滤、异常监测与 Django 接口

4.1 最简单的协同过滤:借阅矩阵 + 余弦相似度

论文摘要里提到推荐系统,这在毕设里是个加分项,但不能写得太复杂,否则答辩时讲不清楚。基于用户的协同过滤是最容易讲明白的推荐方案:读者 A 和读者 B 借阅记录相似,那 A 借过的书 B 没借过,就可以推荐给 B。

import pandas as pd from sklearn.metrics.pairwise import cosine_similarity import numpy as np # 构造用户-图书借阅矩阵:行是读者,列是图书,值是借阅次数 # 数据量大的话用 pivot_table,缺失值填 0 borrow_matrix = df.pivot_table(index="reader_id", columns="book_name", values="borrow_count", fill_value=0) # 计算读者之间的余弦相似度 reader_sim = cosine_similarity(borrow_matrix) reader_sim_df = pd.DataFrame(reader_sim, index=borrow_matrix.index, columns=borrow_matrix.index) def recommend_for_reader(reader_id, top_n=5): """ 给指定读者推荐图书:找相似度最高的前5个读者, 收集他们借过、目标读者没借过的书 """ sim_scores = reader_sim_df[reader_id].sort_values(ascending=False) similar_readers = sim_scores.iloc[1:6].index # 去掉自己 # 目标读者已经借过的书 borrowed = set(borrow_matrix.loc[reader_id][borrow_matrix.loc[reader_id] > 0].index) candidates = {} for r in similar_readers: books = borrow_matrix.loc[r] for book in books[books > 0].index: if book not in borrowed: candidates[book] = candidates.get(book, 0) + 1 ranked = sorted(candidates.items(), key=lambda x: x[1], reverse=True) return [book for book, _ in ranked[:top_n]] recommendations = recommend_for_reader(borrow_matrix.index[0], top_n=5) print(recommendations)

注意sim_scores.iloc[1:6]里的iloc[1:]是把读者自己排除掉,自己和自己的相似度一定是 1,排第一,不排除的话推荐结果全是自己借过的书,毫无意义。候选图书按“被几个相似读者借过”排序,这个逻辑在论文里非常好解释,不需要搬出复杂的矩阵分解。

4.2 异常借阅监测:规则比模型更实用

论文原文提到“检测用户的借阅异常行为,提供对图书盗窃、破坏等行为的预警”。实际操作中,毕设阶段做规则引擎比做异常检测模型更靠谱,也更容易讲清楚。

# 异常规则1:借阅量异常大(比如一个月借了超过20本) df["month"] = df["borrow_date"].dt.month monthly_count = df.groupby(["reader_id", "month"]).size().reset_index(name="count") abnormal_volume = monthly_count[monthly_count["count"] > 20] # 异常规则2:借阅时长异常长(超过60天未还) df["borrow_days"] = (df["return_date"] - df["borrow_date"]).dt.days abnormal_duration = df[df["borrow_days"] > 60] # 异常规则3:同一本书连续借阅多次(疑似恶意占用) repeat_borrow = df.groupby(["reader_id", "isbn"]).size().reset_index(name="count") abnormal_repeat = repeat_borrow[repeat_borrow["count"] >= 3] print("异常借阅量读者数:", abnormal_volume["reader_id"].nunique()) print("超期未还记录数:", len(abnormal_duration)) print("重复借阅图书数:", len(abnormal_repeat))

阈值(20本、60天、3次)需要根据自己数据集的分布调整,不要照搬。我把这三条规则跑完后会分别统计命中数量,如果某个规则的命中数特别多,说明阈值设置太宽松,会被答辩老师追问。规则的输出也方便做成表格放进论文,比黑匣子模型更直观。

4.3 Django 接口:把分析结果变成可访问的 Web 服务

摘要里明确提到了 Django,毕业设计的系统展示环节离不开它。不需要做得多复杂,把分析和推荐结果通过 JSON 接口暴露出来,前端页面能调就行。下面是最小可用的接口写法:

# views.py 核心代码(省略 Django 工程初始化) import json from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt @csrf_exempt def api_recommend(request): """ 推荐接口:GET 传入 reader_id,返回推荐图书列表 示例: /api/recommend?reader_id=2023001 """ reader_id = request.GET.get("reader_id", "") if not reader_id: return JsonResponse({"error": "缺少 reader_id 参数"}, status=400) # 调用前面写好的推荐函数 books = recommend_for_reader(reader_id, top_n=5) return JsonResponse({ "reader_id": reader_id, "recommendations": books }) @csrf_exempt def api_stats(request): """ 统计接口:返回借阅总量、人均借阅量、热门分类 Top5 前端大屏展示用的核心数据 """ stats = { "total_borrow": int(df.shape[0]), "avg_per_reader": round(avg_per_reader, 2), "hot_categories": category_rank.head(5).to_dict() } return JsonResponse(stats, json_dumps_params={"ensure_ascii": False})

json_dumps_params={"ensure_ascii": False}这个参数很多人会漏掉,不设置的话中文图书名会变成\u开头的一串转义字符,前端拿到后还得手动解码。接口写好后,Django 的 urls.py 里加两行路由,用python manage.py runserver起服务就能访问了。整个系统演示时,浏览器打开页面、输入读者号、看到推荐结果,这比干讲代码效果好得多。

5. 避坑与排错:借阅数据项目最常见的五个坑

5.1 中文编码翻车:CSV 打开乱码、入库报错

现象:爬虫抓下来的数据用 Excel 打开全是乱码,或者 Pandas 读 CSV 时报UnicodeDecodeError。

原因:requests 拿到的页面编码是utf-8,但 Windows 下 Excel 默认用gbk打开 CSV;写入 CSV 时没指定编码,或者 Pandas 默认用了系统编码。

解决:写入和读取都显式指定编码:

df.to_csv("borrow_data.csv", index=False, encoding="utf-8-sig") pd.read_csv("borrow_data.csv", encoding="utf-8-sig")

utf-8-sig会写入 BOM 头,Excel 才能正确识别 UTF-8。数据库入库时同理,SQLite 本身不受影响,但导出数据给老师时如果用的 CSV,一定带这个参数。

5.2 时间字段格式不统一,借阅时长算成负数

现象:borrow_days出现负值,或者pd.to_datetime抛异常。

原因:系统里有的日期格式是2024-03-15,有的是2024/3/15,还有的是15-03-2024,Pandas 解析不了混合格式。

解决:解析时不要指定单一format,改用errors="coerce"+ 手动规范:

df["borrow_date"] = pd.to_datetime(df["borrow_date"], errors="coerce") # 检查哪些日期没解析成功 na_mask = df["borrow_date"].isna() print(df.loc[na_mask, "borrow_date"].head())

如果只有个别行解析失败,直接把这些行丢掉即可;如果失败的行很多,先去看看原始数据到底是什么格式,再针对性处理。借阅时长出现负数,优先检查是不是把还书日期和借书日期搞反了。

5.3 去重误删合法记录:同一本书隔月再借被误杀

现象:drop_duplicates(subset=["isbn", "borrow_date"])之后数据量骤减,明显不对。

原因:把isbn和borrow_date作为去重键,但同一个读者同一本书在不同月份借阅是合法行为,不是重复记录。

解决:去重键必须加reader_id,而且要明确业务上的重复定义——同一个人、同一本书、同一天,才是真正需要去掉的重复项:

df = df.drop_duplicates(subset=["reader_id", "isbn", "borrow_date"])

如果只按 ISBN 去重,那热门书只会保留一条记录,整个分析就废了。这个坑我踩过一次,当时跑出来图书排名全是错的,排查了半天才发现是去重逻辑写错了。

5.4 Pandas 版本差异:groupby 聚合结果时对时错

现象:在本地跑没问题,换到另一台电脑或者 Colab 上,groupby(...).agg的结果不一样,甚至报FutureWarning。

原因:Pandas 1.x 到 2.x 版本间对agg的字符串参数和排序行为有调整,比如groupby默认sort=True,老版本和新版本对分组的默认排序行为不完全一致。

解决:在代码里显式声明参数,别依赖默认值:

df.groupby(["category"], sort=False)["book_name"].count()

同时把requirements.txt里的 Pandas 版本钉死,比如pandas==2.1.4。交毕设时,环境最容易出问题,钉版本是给自己留后悔药。

5.5 Django 里图表路径不对,页面图片全部 404

现象:Django 页面能打开,但 Matplotlib 生成的图表全部加载不出来,控制台报 404。

原因:savefig保存到了项目根目录,但 Django 的静态文件服务没配置,浏览器访问不到那个路径。

解决:设置STATICFILES_DIRS,把图表输出到静态目录:

# settings.py import os STATIC_URL = "/static/" STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]

保存图片时直接指定:

plt.savefig("static/images/monthly_trend.png", dpi=150)

模板里用{% static 'images/monthly_trend.png' %}引用。开发阶段记得DEBUG=True,Django 才会自动服务静态文件,否则要手动配置静态文件访问路由。

6. 进阶:把借阅分析做成一份自动化月度报表

项目做完后,如果只是跑一次出结果,多少有点浪费。我习惯把整套分析封装成一个脚本,每个月自动生成一份 Markdown 格式的借阅分析报表,里面包含核心指标、趋势图和Top榜。这样不管是自己复盘还是给导师看,都是现成的材料。

# generate_report.py import pandas as pd import sqlite3 import matplotlib.pyplot as plt import seaborn as sns from datetime import datetime def generate_monthly_report(year, month): """生成指定月份的借阅分析报表 Markdown 文件""" conn = sqlite3.connect("library_borrow.db") # 只查该月数据 df = pd.read_sql( "SELECT * FROM borrow_records WHERE strftime('%Y-%m', borrow_date)=?", conn, params=[f"{year}-{month:02d}"] ) conn.close() if df.empty: return # 计算核心指标 total = df.shape[0] active_readers = df["reader_id"].nunique() avg_per_reader = round(total / active_readers, 2) if active_readers else 0 renew_rate = round((df["renew_count"] > 0).mean() * 100, 1) # 生成图表 plt.rcParams["font.sans-serif"] = ["SimHei"] daily_trend = df.groupby(df["borrow_date"].dt.day).size() fig, ax = plt.subplots(figsize=(10, 4)) sns.lineplot(x=daily_trend.index, y=daily_trend.values, marker="o", ax=ax) ax.set_title(f"{year}年{month}月每日借阅量") ax.set_xlabel("日期") ax.set_ylabel("借阅量") plt.tight_layout() img_path = f"static/images/report_{year}_{month}.png" plt.savefig(img_path, dpi=150) # 写 Markdown 报表 lines = [ f"# {year}年{month}月图书馆借阅分析报告", "", "## 核心指标", f"- 总借阅量:{total} 册", f"- 活跃读者数:{active_readers} 人", f"- 人均借阅量:{avg_per_reader} 册", f"- 续借率:{renew_rate}%", "", "## 借阅趋势", f"![月度借阅趋势](./{img_path})", "", f"报告生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}", ] with open(f"report_{year}_{month}.md", "w", encoding="utf-8") as f: f.write("\n".join(lines)) # 调用方式:生成 2024 年 6 月报表 generate_monthly_report(2024, 6)

这套脚本我觉得是整份资源里最值得扩写的地方——毕设答辩时,导师问“你的系统能不能实际用”,直接把历史数据跑一遍出报表,比嘴上说“能”有力得多。从那以后,我每次做数据分析类的项目,都会强制走一遍这个流程:先定指标口径,再写清洗逻辑,最后封装成可重复执行的脚本。这样做的好处是,任何时间点重新跑一遍数据,结果都是可复现的——这在论文里叫“实验可重复性”,在导师眼里叫态度端正。希望这份拆解能帮到你,尤其是那些还在纠结毕设怎么做、代码怎么落的同学。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询