尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

知乎数据分析与处理系统:从爬虫到可视化完整实战

知乎数据分析与处理系统:从爬虫到可视化完整实战 简介这套源码实现了一个基于Python的知乎数据分析与处理系统适合计算机相关专业学生、爬虫与NLP初学者以及想了解用户画像构建的开发者。项目覆盖数据爬取、中文分词、词频统计、KMeans聚类和验证码识别等模块并提供多线程优化思路可直接用于知乎公开数据的研究与分析。压缩包共23个文件以8个Python脚本为核心包含问题抓取、专栏处理、关注者获取、验证码识别与聚类等代码同时附带CNN.pdf说明文档、README.md和11张运行效果图整体约2.52MB结构清晰便于查阅。已有148人学习下载适合作为课程设计或毕业设计的参考实现可依据README快速搭建环境结合jieba、sklearn等库复现数据清洗、特征提取与聚类分析全流程。通过词云和图表展示的中间结果有助于直观理解知乎用户兴趣分布与行为模式是一份兼具教学与实战价值的完整源码包。1. 知乎数据分析与处理系统是什么值不值得花时间跑一遍想研究某个话题下什么样的回答最容易获得赞同人工翻几十页答案看到眼睛发酸结论还是说不清楚。这套系统做的事情就是把这条链路自动化把知乎上某个问题或话题下的公开回答抓下来清洗成干净的结构化数据再算出一批可解释的指标最后画成图、导出表格。标题里的源码 zip 不是什么黑匣子它是一条“采集 → 清洗 → 存储 → 分析 → 可视化”的完整流水线每一段都能看到中间结果。这套系统最适合三类人想摸清内容规律、提高选题效率的知乎运营者需要给课程设计找一个完整“数据分析项目”案例的在校生以及刚学完 pandas 和 requests、想拿真实数据练手的 Python 学习者。作为课程设计的切入点尤其合适因为爬虫、数据库、清洗、可视化四个环节全都能讲清楚答辩时有内容可讲。比起算法复杂度这份源码更大的价值在于让你理解真实项目里“数据从哪来、脏在哪、怎么变成结论”的完整过程。至于它能跑到多大规模、需要改哪些参数下面按模块一层层拆开讲。2. 环境与依赖先安全解压 zip、把最小启动命令跑通2.1 解压先做完整性检查linux 下的 unzip 命令与 Windows 下的 7-Zip拿到 zip 先别急着双击解压。这类源码包最常见的问题是下载不完整解压到一半报“文件损坏”很多人第一反应是源码写错了其实是包本身没下载全。我做这类项目的第一件事是完整性检查Windows 上用 7-Zip 打开看文件列表不急着释放在 Linux 或云服务器上就先用 unzip 的测试模式跑一遍。网上搜“linux解压缩命令zip”时最该先掌握的是-t和-d这两个参数。unzip -t zhihu_analysis_system.zip unzip -o zhihu_analysis_system.zip -d zhihu_analysis-t只测试压缩包完整度不实际释放文件-o表示覆盖解压-d指定目标目录。测试输出末尾出现No errors detected in compressed data就说明文件完整可以继续。任何 CRC 或 unexpected end of file 级别的报错都先回下载源重新拉一次包不要带病运行。解压这一步花两分钟能避免后面所有“跑不通”的误判。解压完先别急着装环境花一分钟看目录结构。这类系统的源码一般会拆成几个独立模块采集端目录、清洗脚本、分析绘图脚本、一个 config 配置文件、一个 requirements.txt 依赖清单可能还有一个初始化数据库的脚本。先打开 requirements.txt 看一眼就能预测项目依赖哪些库、有没有明显的不适配包比直接跑主脚本省事得多。2.2 Python 环境准备用 conda 或 venv 隔离依赖按 python 安装教程的思路把解释器装明白这套系统离不开 Python但我不建议直接往系统全局环境里pip install。数据类项目依赖的库版本非常容易互相打架pandas 升一个大版本就可能改掉几个 API。我的习惯是用 conda 单独建一个环境按最常见的 python 安装教程思路把解释器问题一次解决先确认 Python 3.8 以上再创建独立环境最后在虚拟环境里装依赖。这样以后跑别的项目互不干扰。conda create -n zhihu python3.9 -y conda activate zhihu pip install -r requirements.txtcreate -n zhihu是给环境取名python3.9指定解释器版本。依赖装完后用pip list看一眼关键库是否齐全重点确认 requests、pandas、matplotlib、jieba、openpyxl、tqdm 这几个都在。如果你用的是 VS Code记得在“选择解释器”里把路径切到刚才创建的虚拟环境否则终端里明明激活了环境编辑器运行时却用的全局 Python报错找半天都找不到原因。Windows 上如果后续读取 Excel 文件报xlrd相关错误把pd.read_excel()的引擎参数显式写成openpyxl即可。2.3 启动前必须改的四个配置点依赖装完先别跑主脚本先打开配置文件改四个位置。这套系统一般会有一个 config.ini 或 config.py 集中管理运行参数常见的四件事Cookie、请求间隔、数据库路径、采集对象列表。逐个填对后面能少踩一半坑。我一般用 INI 格式做示例键名按常见习惯写成这样[crawl] cookie 你的登录Cookie question_ids 317794443,43913059 min_interval 1.5 max_interval 2.5 timeout 10 [storage] db_path data/zhihu.db raw_html_dir data/raw_htmlCookie 的获取方式是浏览器登录知乎后按 F12 打开开发者工具切到 Network网络面板刷新页面任意一个请求的请求头里都能看到 Cookie 字符串整段复制过来。question_ids填你要采集的问题 ID就是知乎问题链接末尾那一串数字。min_interval和max_interval是两次请求之间的随机等待秒数初始值建议设成 1.5 到 2.5 秒短到 0.5 秒以内很容易触发频率限制。db_path指向 SQLite 文件路径Windows 下注意反斜杠要写data/zhihu.db这种正斜杠形式。配置改完后找一下初始化脚本常见名字是init_db.py或create_tables.py先运行它把数据库表建好。然后跑通最小启动命令python init_db.py python run_crawl.py --config config.inirun_crawl.py是采集主入口--config参数指定刚改好的配置文件。看到终端开始逐页输出进度条说明整条链路已经通了。这里每个步骤都是向后依赖的配置不对采集就是空的库表没建写入直接报错Cookie 过期解析出来什么都没有。所以第一次运行务必按这个顺序走别跳步。3. 采集层知乎公开数据抓什么、存什么、请求参数怎么定3.1 为什么这套系统常见选型是 requests 而不是 Scrapy知乎数据分析系统里最核心的部分是采集层。技术选型上这类单机源码包大多用 requests 写采集脚本而不是直接上 Scrapy。原因很实际Scrapy 的异步引擎、中间件、Item Pipeline 在小数据量下是负担调试问题时要多绕一层requests 写出来的脚本是线性的一个断点下去能看清每一步的输入输出对新手和课程设计场景友好得多。如果以后数据量真的大到需要分布式采集再往 Scrapy 迁移也不迟因为字段设计和存储结构到时候是可以复用的。requests 方案需要自己补两块能力重试和限速。会话级重试建议用HTTPAdapter来挂而不是单纯try/except这样连接超时和读取超时都能覆盖到。参考写法如下import requests from requests.adapters import HTTPAdapter session requests.Session() adapter HTTPAdapter(max_retries3) session.mount(https://, adapter) session.mount(http://, adapter) resp session.get(url, headersheaders, timeout10)max_retries3表示一次请求最多重试 3 次重试之间 requests 会自动等待timeout10是连接和读取的共用超时时间单位是秒。这个参数很关键不设 timeout请求卡住时线程会一直占着采集脚本跑一个小时后看着像“挂了”实际是几十个请求都在那干等。设了超时后单次请求最多 10 秒就返回结果配合后面的断点续采整个采集过程可靠很多。3.2 字段粒度决定分析上限一张答案表该存哪些字段采集脚本里最不该省事的地方是字段设计。很多人抓数据时贪快只存回答正文后面想分析“什么时间段发布更容易被赞同”的时候才发现发布时间没存只能回去重爬。我整理过这类源码里最常见的表结构两张表就够用question 表存问题维度的信息answer 表存回答维度的信息。表名字段类型说明questionquestion_idTEXT问题 ID主键questiontitleTEXT问题标题questionfollower_countINTEGER关注人数answeranswer_idTEXT回答 ID主键answerquestion_idTEXT所属问题answerauthorTEXT作者昵称answervoteup_countINTEGER赞同数answercomment_countINTEGER评论数answercreated_timeINTEGER发布时间戳answercontent_textTEXT回答正文清洗后answerraw_htmlTEXT采集时的原始 HTMLanswerurlTEXT回答链接answercrawl_timeTEXT采集时间answer_id 用 TEXT 类型而不是自增整数是因为知乎回答的 ID 本身就是全局唯一的数字字符串天然适合做主键也方便去重。created_time 存原始时间戳不存格式化字符串原因是分析时既可能按小时聚合也可能按星期聚合时间戳在任何维度都能转换格式化字符串还要先解析回去。raw_html 这个字段容易被忽略但它非常有用清洗时把原始内容留下来后面发现解析规则写错了还能回炉重挖不用重新请求接口。建表语句和字段同步改参考如下CREATE TABLE IF NOT EXISTS answer ( answer_id TEXT PRIMARY KEY, question_id TEXT NOT NULL, author TEXT, voteup_count INTEGER DEFAULT 0, comment_count INTEGER DEFAULT 0, created_time INTEGER, content_text TEXT, raw_html TEXT, url TEXT, crawl_time TEXT DEFAULT (datetime(now, localtime)) );3.3 核心采集循环分页拉取、解析落库、断点续采采集主循环的逻辑通常是从配置文件读取 question_id循环请求答案分页接口每页解析一批回答写入 SQLite再翻下一页。知乎网页端的答案接口会返回 JSON 格式数据包含data列表和paging分页信息下面这段是从这类系统里抽出来的通用写法你按自己拿到的包结构调整路径。import json import random import sqlite3 import time def fetch_answers(session, question_id, offset0): url fhttps://www.zhihu.com/api/v4/questions/{question_id}/answers params { include: content, offset: offset, limit: 5 } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Cookie: CONFIG[cookie] } resp session.get(url, paramsparams, headersheaders, timeout10) resp.raise_for_status() return resp.json()offset是分页偏移limit是每页条数。这里有讲究limit不是越大越好一次拉 50 条虽然请求次数少但接口数据包过大时解析容易超时我通常设 5 到 10 条配合 1.5 秒以上的间隔每页处理完就落一次库整体进度可以随时看到。resp.raise_for_status()会在返回状态码不是 200 时直接抛出异常配合前面的HTTPAdapter重试机制能自动跳过偶发的 5xx 错误。拿到 JSON 后的处理逻辑是遍历data数组提取回答 ID、作者、赞同数、发布时间、正文内容插入数据库。断点续采的关键在这里插入前先查一遍已有 ID已存在的跳过不存在才写入。这样脚本中途停了重启后接着跑不会产生重复数据。def save_answers(conn, items): existing set() rows conn.execute(SELECT answer_id FROM answer).fetchall() for row in rows: existing.add(row[0]) new_items [it for it in items if it[answer_id] not in existing] conn.executemany( INSERT OR IGNORE INTO answer(answer_id, question_id, author, voteup_count, created_time, content_text, raw_html, url) VALUES(:answer_id, :question_id, :author, :voteup_count, :created_time, :content_text, :raw_html, :url), new_items ) conn.commit() return len(new_items)INSERT OR IGNORE配合主键约束做第二层去重和实时查询 existing 形成双保险。每页都 commit 一次而不是攒到全部跑完再提交这样哪怕中途断电已采集的数据也不会丢。重启后从断点继续是这套系统在可靠性上的关键设计也是我强烈不建议删掉的一环。4. 处理层把脏文本清洗成能画图的数据再做特征工程4.1 原始数据的脏不在格式而在“语义空值”采集完成后数据库里存的是接近原始的内容直接拿去做数据分析十有八九会得出错误结论。脏不是指格式乱而是三类“语义空值”第一类是平台占位文本比如“该回答已被删除”“以上回答由 AI 生成”这些文本本身就是替代内容统计词频时会把“删除”顶到前面第二类是 HTML 残留标签和图片说明文字它们占字符长度干扰字数统计第三类是重复采到的回答回答内容存在但 answer_id 不一致比如转发的补充说明。这一类问题在数据分析与数据挖掘实战里有个专门的名字叫“数据质量治理”。它的优先级永远高于模型和画图因为图表只会忠实地展示你喂进去的数据有多脏。我遇到过一个用户拿了完整源码跑完词频 TOP3 是“图片”“链接”“以上”极大影响判断这就是没做语义空值过滤的典型结果。4.2 pandas 清洗三步去重、剥标签、修时间清洗脚本的常见做法是用 pandas 从 SQLite 读出全表然后按三步处理。第一步去重按 answer_id 保留最后一条第二步过滤语义空值凡是正文匹配到“已删除”“审核中”等关键词的行直接剔除这一步通常放在去重后面因为重复的记录可能一条是正常内容一条是占位文本第三步处理时间字段把 UNIX 时间戳转成带时区的本地时间。import pandas as pd conn sqlite3.connect(data/zhihu.db) df pd.read_sql(SELECT * FROM answer, conn) # 1. 按 answer_id 去重保留最后一条 df df.drop_duplicates(subset[answer_id], keeplast) # 2. 过滤语义空值 placeholder_keywords 已删除|审核中|以上回答由AI生成|内容不可见 df df[~df[content_text].fillna().str.contains(placeholder_keywords, regexTrue)] # 3. 时间戳转带时区的本地时间 df[created_at] pd.to_datetime(df[created_time], units, utcTrue) \ .dt.tz_convert(Asia/Shanghai) df[crawl_date] df[created_at].dt.strftime(%Y-%m-%d) df.to_csv(data/zhihu_clean.csv, indexFalse)keeplast在去重时保留重复记录里最后写入的那条因为后写入的通常是内容更新或补采的版本数据更新鲜。fillna().str.contains(regexTrue)的作用是把空值先填成空字符串再执行正则匹配避免 NaN 导致匹配结果变成 False静默丢数据。时间戳转换时units告诉 pandas 当前值是秒级时间戳tz_convert(Asia/Shanghai)统一成北京时间避免后续做小时统计时对不上。4.3 特征工程把文本变成可统计的指标清洗完的 DataFrame 还只是“干净的数据”要得出结论需要构造特征。这一步在数据分析实战里叫特征构造说白了就是把文本和原始计数变成有业务含义的指标。做这个系统时我通常构造三个新特征回答字符长度、点赞密度、发布时段。代码不多但每个特征都对应一个可回答的问题。import jieba df[char_len] df[content_text].str.len() df[word_count] df[content_text].apply(lambda x: len(jieba.lcut(x))) df[like_per_100] df[voteup_count] / (df[char_len] / 100) df[hour] df[created_at].dt.hour df[weekday] df[created_at].dt.weekdaychar_len是纯字符数用来判断回答是短评还是长文word_count基于 jieba 分词结果统计比字符数更接近“篇幅”的感受like_per_100是每 100 字获得的赞同数用来衡量内容的“赞同密度”比单纯看总赞同量更公平——因为长文天然更容易积累赞同但只有密度高的内容才说明每读 100 字就有一次互动。hour和weekday是给时间聚合用的后面画热力图时直接按这两个字段分组即可。这一章做完数据已经是可以直接灌进 matplotlib 或 pandas 内置绘图接口的状态。下一步的分析不该盲目发散先定一个具体问题比如“哪种篇幅的内容更容易被赞同”再选对应的图和指标。这也引出了这套系统最容易出错但最值得看的一章常见问题排查。5. 常见问题排查四个让系统跑不动的典型踩坑记录这套系统整体不复杂真出问题也几乎都在同样的位置。我把踩过的坑按“现象→原因→解决”整理成四条血泪记录覆盖采集、解析、解压、画图四个环节照着逐条对照能省下大量排查时间。很多报错不是代码逻辑错了而是运行条件没满足。5.1 采集中断后从头再来重复数据堆满表现象采集脚本跑了一个小时终端不再输出新进度进程还在但没有任何动作重启脚本后发现数据表里出现大量重复记录。原因有两层一是网络请求卡在某页没有超时返回进程死等二是脚本没有断点续采机制重启后重新从第一页开始抓重复数据自然越来越多。前者是导火索后者是放大问题的条件。解决请求层加timeout10配合 sleep 随机等待避免连续快速请求脚本层参考 3.3 节的save_answers写入前先查已有 answer_id重启后自动跳过已采数据。如果重复数据已经堆进去了用一条 SQL 清理DELETE FROM answer WHERE rowid NOT IN ( SELECT MAX(rowid) FROM answer GROUP BY answer_id );5.2 返回了 HTML 但解析出来的列表是空的现象请求状态码正常响应内容也打印得出来但解析完data数组是空的或者某个字段大面积缺失。这一般发生在隔了几个月再运行源码的时候属于爬虫类项目最常见的“翻车现场”。原因知乎前端的接口返回结构发生调整JSON 字段路径变了或者登录态过期服务端返回的是登录跳转页而不是数据。解决先在脚本里把每页响应原文落盘成 html 文件代码里加一行open(debug.html, w).write(resp.text)再用编辑器搜一下“data”关键词肉眼确认真实字段位置。对比一下头部请求里的 Cookie 是否过期重新复制一次。经验法则任何解析类项目响应落盘永远比空想快。5.3 zip 伪加密与解压报错先验证文件再怀疑代码现象解压源码 zip 时提示需要密码或者解压到一半提示“文件损坏”但压缩包明明是从可信来源下载的。这个报错往往导致用户直接怀疑压缩包有问题去重新下载但重新下回来还是一样。原因可能是 zip 伪加密也就是压缩包文件头里的“加密标志位”被置位但数据本身没有用 AES 或 ZipCrypto 真正加密也可能是下载不完整。伪加密在网络上流传的源码包里偶尔会出现是标志位被修改导致的“假锁”。解决先区分两种原因。在 Linux 上用unzip -t测试完整性如果显示文件列表能读出来但解压要密码多半是伪加密用 Python 的 zipfile 模块读一下文件列表import zipfile with zipfile.ZipFile(zhihu_analysis_system.zip) as zf: print(zf.namelist())如果namelist()能正常列出全部文件名说明压缩包结构完整问题只在加密标志位。常见处理是用 7-Zip 打开看右侧是否显示加密图标但文件列表可见若是把文件头第 7 字节的加密标志位从01改回00即可正常解压。这一步属于 zip 伪加密的通用修复思路不需要第三方工具。5.4 图表中文全部变方块matplotlib 的字体玄学现象整套系统跑通数据表格也导出来了但生成的图片里所有中文标签全是小方块英文和数字正常。第一次遇到的人常以为是数据编码坏了其实是 matplotlib 默认字体里没有中文字形。原因matplotlib 的默认字体是 DejaVu Sans不支持中文。Windows 和 Linux 上解决方案稍有不同。解决在绘图脚本头部加两行配置import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Noto Sans CJK SC, WenQuanYi Zen Hei] plt.rcParams[axes.unicode_minus] Falsefont.sans-serif配置成列表matplotlib 会按顺序在系统里查找第一个可用的字体axes.unicode_minusFalse解决负号显示成方框的问题。如果在 Linux 服务器上跑SimHei 不存在需要先安装文泉驿或 Noto Sans CJK 字体包再重启 Python 进程。画图前用matplotlib.font_manager查一下当前可用字体比一次一次试错快得多。6. 用三张图验证分析结果再把结论落地成报告6.1 第一张图回答字符数与赞同数的散点python 数据分析与可视化里最容易被低估的一步是“先画散点”。在 clean DataFrame 上直接plt.scatter(char_len, voteup_count)先不加任何聚合用alpha0.3降低重叠度最多取前 1000 条数据画否则点太多会糊成一团。看完散点后你会自然发现超长文和超短文各占两端中间存在一个相对集中的区域。import matplotlib.pyplot as plt sample df.nlargest(1000, voteup_count) plt.scatter(sample[char_len], sample[voteup_count], alpha0.3) plt.xlabel(回答字符数) plt.ylabel(赞同数) plt.xscale(log) plt.show()nlargest(1000, voteup_count)取赞同数最高的前 1000 条避免低质量灌水回答稀释图面xscale(log)做对数轴是因为字符数分布跨度大从几十到几万线性轴下大部分点都挤在左边。这张图回答的问题是“篇幅和互动的关系”通常扫一眼就有结论。6.2 第二张图发布时段与互动热力图第二张图聚焦“什么时候发更容易被看到”。使用pivot_table把星期和小时两个维度聚合成互动总数再用热力图展示。这张图的洞察是分话题的不同话题的活跃时间段差异明显不能拿一个结论套所有内容。heat_data df.pivot_table( indexweekday, columnshour, valuesvoteup_count, aggfuncmean ) import seaborn as sns sns.heatmap(heat_data, cmapYlOrRd, xticklabelsTrue) plt.xlabel(发布小时) plt.ylabel(星期) plt.title(各时段发布的平均赞同数) plt.show()aggfuncmean用平均赞同数而不是总数原因是不同时段的回答数量差异很大总数会偏向“回答多”的时间段平均值才能反映“单条内容表现”。热力图里颜色越深的格子就是这段时间发布内容表现最好的时段。如果系统依赖清单里没有 seaborn用plt.imshow(heat_data.values, aspectauto)也能达到同等的观察效果但刻度标签需要自己处理。6.3 第三张图回答正文的词频 TOP30 条形图前两张图回答“多少字、何时发”的问题第三张图回答“聊什么”。基于清洗后的content_text字段做 jieba 分词过滤停用词和单字词后统计词频取 TOP30 画横向条形图。横向条形图比纵向更适合长标签的展示词不会重叠在一起。from collections import Counter import jieba stopwords {我们, 你们, 这个, 那个, 就是, 什么, 可以, 一个} counter Counter() for text in df[content_text].dropna().head(500): words [w for w in jieba.lcut(text) if len(w) 1 and w not in stopwords] counter.update(words) top_words counter.most_common(30) words, counts zip(*top_words) plt.barh(range(len(words)), counts, tick_labelwords) plt.gca().invert_yaxis() plt.show()head(500)限制参与统计的回答数量控制计算时间len(w) 1过滤单字invert_yaxis()让词频最高的词排在最上面符合阅读习惯。这张图需要抽样核验后一步说明。6.4 数据导出与抽检复核分析结论出来后把清洗后的数据和研究结果导出方便后续写报告。常见做法是导出一个 Excel 工作簿多个 sheet 分别放明细数据和聚合结果with pd.ExcelWriter(data/zhihu_analysis.xlsx, engineopenpyxl) as writer: df.to_excel(writer, sheet_name回答明细, indexFalse) top_words_df.to_excel(writer, sheet_name词频TOP30, indexFalse) heat_data.to_excel(writer, sheet_name时段热力)导出后不要立刻写结论先做一步人工抽检从词频 TOP30 里挑 10 个词回数据库里随机找 5 条包含这些词的回答原文确认分词没有把语义拆碎确认“点赞密度”高的回答不是搬运或标题党。这一步是分析结论的质检闸门我见过太多图表能自洽但经不起人工复核的案例。抽样通过后三张图加一个 Excel 文件就是这套系统交付的最完整形态。我现在接手任何类似的源码包第一件事永远是先拿一个配置里最小的 question_id 跑通全链路看采集、清洗、画图三步是否都正常确认无误再放大数据范围。这个习惯帮我省了至少十次半夜查日志的时间你按这个顺序推进这套知乎数据分析与处理系统基本不会卡住你超过一下午。希望帮到你。本文还有配套的精品资源点击获取
返回列表