news 2026/10/1 13:25:20

Python知乎数据分析系统实战:爬虫、NLP、用户画像与验证码识别全链路解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python知乎数据分析系统实战:爬虫、NLP、用户画像与验证码识别全链路解析

简介:这是一套面向数据分析初学者与进阶开发者的知乎数据挖掘实战源码,围绕用户、问题与专栏信息的采集、清洗、建模与可视化展开,可帮助读者理解从爬虫到用户画像的完整链路。资源包共23个文件,以8个Python脚本为核心,覆盖问题与关注者抓取、验证码识别、KMeans聚类、专栏处理等模块;另有11张png图片用于词云与结果展示,并附CNN.pdf说明文档、yml配置与README,压缩包约2.52MB,结构清晰便于按模块查阅。项目融合requests、beautifulsoup4、jieba与sklearn,实现分词、词频统计、用户兴趣与行为分析,并通过多线程优化爬取效率。目前已有148人学习下载,适合作为NLP与机器学习入门练手、课程设计或二次开发的参考素材。

1. 从一份知乎数据源码包说起:它能跑出什么,又卡在哪

知乎的数据分析项目在网上一抓一大把,但真正能跑通的没几个。这份基于 Python 的知乎数据分析与处理系统,把爬取、NLP 处理、用户画像、可视化串成了一条完整链路,还额外塞进了验证码识别和 KMeans 聚类两个硬骨头模块。拿到手第一件事不是急着pip install,而是先搞清楚它到底能解决什么问题——如果你需要批量抓取知乎用户关注者、问题列表、专栏文章,然后做分词词频统计、生成用户兴趣画像,这份源码基本覆盖了从采集到呈现的全流程。适合有 Python 基础、想拿一个真实场景练手数据分析的从业者,也适合需要快速搭建社交平台数据洞察原型的产品或运营同学。但它不是开箱即用的 SaaS 工具,环境配置、Cookie 维护、验证码模型训练这几道坎,每一道都能让你卡上半天。

2. 拆开源码包:文件结构与模块职责

2.1 核心脚本清单与功能映射

拿到一个源码包,我习惯先看目录结构再动手。这份资源的文件组织不算复杂,但每个脚本的职责边界需要先理清楚,否则跑起来报错都不知道该改哪个文件。

文件/目录职责关键依赖
get_questions_v2.py抓取知乎问题列表requests, BeautifulSoup
get_questions_info_v2.py抓取问题详情与回答信息requests, BeautifulSoup
get_follower_url_v2.py获取用户关注者链接列表requests
zhuanlan_process.py专栏文章数据处理jieba, requests
kmeans_clustering.py用户兴趣聚类分析sklearn
crop_captcha.py验证码图片裁剪预处理PIL/Pillow
captcha_predict.py验证码预测推理训练好的 CNN 模型
cnn_captcha.pyCNN 验证码识别模型训练深度学习框架
_config.yml项目配置文件—
images/验证码样本图片与示意图—
CNN.pdfCNN 原理参考文档—
README.md项目说明—

从这张表能看出,项目分三条线:数据采集线(get_questions_v2、get_questions_info_v2、get_follower_url_v2)、数据处理线(zhuanlan_process、kmeans_clustering)、验证码对抗线(crop_captcha、cnn_captcha、captcha_predict)。三条线可以独立跑,也可以串起来用。常见做法是先用采集线拿数据,再用处理线做分析,验证码模块只在采集被拦截时启用。

2.2 环境搭建与依赖安装

项目依赖在 README 里列了 requests、beautifulsoup4、jieba、sklearn 四个核心库,但实际跑起来还需要 Pillow(验证码图片处理)、numpy(数值计算)、pandas(数据整理)这几个隐性依赖。我一般会先建虚拟环境再装,避免污染全局。

# 创建虚拟环境(Python 3.8+ 推荐) python -m venv zhihu_env # 激活虚拟环境 # Windows: zhihu_env\Scripts\activate # macOS/Linux: source zhihu_env/bin/activate # 安装核心依赖 pip install requests beautifulsoup4 jieba scikit-learn # 安装隐性依赖(验证码模块和数据处理需要) pip install Pillow numpy pandas matplotlib

这里有个细节:scikit-learn的包名和导入名不一致,安装用scikit-learn,代码里写from sklearn import ...,新手经常在这里翻车。另外 Python 版本建议 3.8 到 3.10,3.11 以上部分老版本 sklearn 可能编译报错。装完之后用pip list确认一下版本,特别是scikit-learn和numpy的兼容性,版本差太远会在kmeans_clustering.py里报AttributeError。

2.3 配置文件_config.yml的关键参数

_config.yml是整个项目的控制中枢,采集频率、请求头、存储路径这些都在这里改。虽然文件不大,但改错一个参数就可能导致采集被封或者数据写不进去。

# _config.yml 典型配置项(根据项目结构推断) headers: User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" Cookie: "你的知乎登录Cookie" crawl: delay: 2 # 每次请求间隔秒数 max_retries: 3 # 失败重试次数 timeout: 10 # 请求超时时间 storage: output_dir: "./data" # 数据输出目录 format: "csv" # 输出格式 captcha: model_path: "./model/cnn_model.h5" # 验证码模型路径 threshold: 0.8 # 置信度阈值

delay这个参数最容易被忽视。设成 0.5 秒以下,跑不了几十个请求就会触发知乎的频率限制,轻则返回 403,重则账号被临时限制。我一般设 2 到 3 秒,虽然慢但稳。Cookie需要从浏览器登录知乎后手动复制,这个没有自动获取的方案,而且有效期通常只有几天,过期了要重新换。

3. 数据采集与 NLP 处理:从请求到词频统计

3.1 问题列表抓取的请求构造与分页逻辑

get_questions_v2.py的核心逻辑是构造知乎的问题列表请求,解析返回的 HTML 或 JSON 数据,提取问题标题、关注数、回答数等字段。知乎的页面结构改版频繁,所以这个脚本能不能跑通,取决于你拿到的版本是否还匹配当前的页面结构。

import requests from bs4 import BeautifulSoup import time import yaml # 读取配置 with open('_config.yml', 'r', encoding='utf-8') as f: config = yaml.safe_load(f) headers = config['headers'] delay = config['crawl']['delay'] def get_questions(keyword, max_pages=5): """根据关键词抓取知乎问题列表""" questions = [] for page in range(max_pages): # 构造搜索 URL,offset 控制分页 url = f"https://www.zhihu.com/search?type=content&q={keyword}&offset={page * 10}" try: resp = requests.get(url, headers=headers, timeout=config['crawl']['timeout']) if resp.status_code != 200: print(f"第 {page+1} 页返回状态码 {resp.status_code},跳过") continue soup = BeautifulSoup(resp.text, 'html.parser') # 提取问题卡片,class 名需根据实际页面调整 items = soup.find_all('div', class_='SearchResult-Card') for item in items: title_tag = item.find('h2') if title_tag: questions.append({ 'title': title_tag.get_text(strip=True), 'page': page + 1 }) except requests.RequestException as e: print(f"第 {page+1} 页请求异常: {e}") time.sleep(delay) # 强制间隔,避免触发频率限制 return questions if __name__ == '__main__': result = get_questions('Python数据分析') print(f"共抓取 {len(result)} 个问题")

这段代码的关键点有三个:一是headers里必须带 Cookie,否则知乎会返回登录页而不是搜索结果;二是time.sleep(delay)不能省,这是避免被封的第一道防线;三是class_='SearchResult-Card'这个选择器需要根据实际页面验证,知乎前端改版后类名可能变化,跑之前先用浏览器开发者工具确认一下。如果返回的resp.text里找不到预期标签,大概率是页面结构变了或者被重定向到了登录页。

3.2 中文分词与词频统计的 jieba 实践

拿到问题标题或专栏文章文本后,zhuanlan_process.py用 jieba 做分词和词频统计。中文分词看着简单,实际有几个参数直接影响结果质量。

import jieba import jieba.analyse from collections import Counter def analyze_text(text_list, top_n=50): """对文本列表做分词和词频统计""" # 加载自定义词典(如果有领域专有名词) # jieba.load_userdict('user_dict.txt') all_words = [] for text in text_list: # 精确模式分词,适合文本分析 words = jieba.lcut(text) # 过滤停用词和单字 stop_words = {'的', '了', '是', '在', '我', '有', '和', '就', '不', '人', '都', '一', '一个'} filtered = [w for w in words if len(w) > 1 and w not in stop_words] all_words.extend(filtered) # 词频统计 word_freq = Counter(all_words) return word_freq.most_common(top_n) def extract_keywords(text, top_k=10): """基于 TF-IDF 提取关键词""" # 允许词性:名词、动词、形容词 keywords = jieba.analyse.extract_tags(text, topK=top_k, withWeight=True) return keywords

jieba.lcut用的是精确模式,适合文本分析场景;如果要做搜索引擎分词可以用cut_for_search。停用词表我一般会单独维护一个文件,代码里硬编码只适合快速验证。extract_tags底层是 TF-IDF,withWeight=True会返回权重值,方便后续排序或过滤。注意 jieba 首次加载词典会慢一两秒,之后就走缓存了,不要在循环里反复初始化。

3.3 KMeans 聚类在用户画像中的参数选择

kmeans_clustering.py是用户画像的核心,思路是把用户的行为特征向量化后用 KMeans 聚成若干群体。KMeans 看着简单,但 K 值怎么选、特征怎么构造,直接决定聚类结果有没有业务意义。

from sklearn.cluster import KMeans from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics import silhouette_score import numpy as np def cluster_users(texts, k_range=range(2, 10)): """对用户文本做 TF-IDF 向量化后 KMeans 聚类""" # 1. 文本向量化 vectorizer = TfidfVectorizer(max_features=500, min_df=2) X = vectorizer.fit_transform(texts) # 2. 遍历 K 值,用轮廓系数选最优 best_k, best_score = 2, -1 for k in k_range: km = KMeans(n_clusters=k, random_state=42, n_init=10) labels = km.fit_predict(X) score = silhouette_score(X, labels) print(f"K={k}, 轮廓系数={score:.4f}") if score > best_score: best_k, best_score = k, score # 3. 用最优 K 重新聚类 final_km = KMeans(n_clusters=best_k, random_state=42, n_init=10) final_labels = final_km.fit_predict(X) return final_labels, best_k, vectorizer

n_init=10表示用 10 组不同初始质心跑,取最优结果,这个参数在 sklearn 1.4 之后默认值变了,显式写出来更稳。max_features=500控制向量维度,太大容易过拟合,太小会丢信息。轮廓系数越接近 1 越好,但实际数据上能到 0.3 以上就算不错了。如果所有 K 值的轮廓系数都低于 0.2,说明特征区分度不够,得回去检查文本预处理是不是没做好。

4. 验证码识别模块:从裁剪到 CNN 推理

4.1 验证码图片的裁剪与预处理流程

知乎的验证码通常是点选或滑动类型,crop_captcha.py负责把原始截图裁剪成模型能吃的尺寸。这一步的精度直接影响后续识别率,裁歪了再好的模型也白搭。

from PIL import Image import os def crop_captcha(input_path, output_path, box=None): """裁剪验证码图片到指定区域""" img = Image.open(input_path) # 如果没有指定裁剪框,按默认比例裁剪 if box is None: w, h = img.size # 常见做法是裁掉边缘留中间区域 box = (int(w*0.1), int(h*0.1), int(w*0.9), int(h*0.9)) cropped = img.crop(box) # 统一尺寸,CNN 输入需要固定大小 resized = cropped.resize((120, 40), Image.LANCZOS) # 转灰度,减少计算量 gray = resized.convert('L') gray.save(output_path) return output_path def batch_crop(input_dir, output_dir): """批量裁剪 images 目录下的验证码样本""" os.makedirs(output_dir, exist_ok=True) count = 0 for fname in os.listdir(input_dir): if fname.lower().endswith(('.png', '.jpg')): src = os.path.join(input_dir, fname) dst = os.path.join(output_dir, f'cropped_{fname}') crop_captcha(src, dst) count += 1 print(f"共处理 {count} 张图片")

Image.LANCZOS是高质量缩放算法,比默认的 NEAREST 慢但边缘更平滑。转灰度是为了减少通道数,CNN 输入从 3 通道变 1 通道,训练和推理都快不少。images/目录里那些captcha39.png、captcha40.png就是样本图,pic1.png到pic9.png可能是示意图或中间结果,跑批量裁剪时注意过滤。

4.2 CNN 模型训练与 captcha_predict 推理链路

cnn_captcha.py负责训练卷积神经网络,captcha_predict.py加载训练好的模型做推理。这两个脚本的衔接点是模型文件路径,_config.yml里的model_path必须和训练时保存的路径一致。

# cnn_captcha.py 训练部分核心逻辑 import numpy as np from PIL import Image import os def load_dataset(data_dir, img_size=(120, 40)): """加载验证码图片和标签""" images, labels = [], [] for fname in os.listdir(data_dir): if not fname.lower().endswith('.png'): continue # 文件名格式假设为 cropped_captcha39.png,标签从文件名提取 label_str = fname.replace('cropped_', '').replace('.png', '') # 这里需要根据实际标签规则做映射,示例用哈希简化 label = hash(label_str) % 10 # 假设 10 分类 img = Image.open(os.path.join(data_dir, fname)).convert('L') img = img.resize(img_size) images.append(np.array(img) / 255.0) # 归一化到 0-1 labels.append(label) return np.array(images).reshape(-1, *img_size, 1), np.array(labels) # captcha_predict.py 推理部分 def predict_captcha(model, img_path, img_size=(120, 40)): """对单张验证码图片做预测""" img = Image.open(img_path).convert('L').resize(img_size) arr = np.array(img) / 255.0 arr = arr.reshape(1, *img_size, 1) pred = model.predict(arr, verbose=0) return np.argmax(pred), np.max(pred) # 返回类别和置信度

标签提取逻辑需要根据实际文件名规则调整,示例里用hash只是占位,真实场景应该维护一个文件名到标签的映射表。归一化除以 255 是标准操作,但要注意训练和推理必须用同样的预处理,否则精度会崩。CNN.pdf应该是模型结构的参考文档,训练前翻一翻能帮你理解网络层数、卷积核大小这些设计选择。

4.3 多线程采集的效率提升与线程安全

项目提到多线程优化,这在采集大量用户关注者时确实能提速,但线程安全是个坑。多个线程同时写同一个文件或列表,不加锁就会丢数据或报错。

import threading from queue import Queue class SafeCrawler: def __init__(self, thread_num=5): self.queue = Queue() self.lock = threading.Lock() self.results = [] self.thread_num = thread_num def worker(self): while not self.queue.empty(): url = self.queue.get() try: # 这里调用实际的采集函数 data = self.fetch(url) with self.lock: # 加锁写入共享列表 self.results.append(data) except Exception as e: print(f"采集失败 {url}: {e}") finally: self.queue.task_done() def fetch(self, url): """实际的采集逻辑,带重试""" import requests, time for attempt in range(3): try: resp = requests.get(url, timeout=10) if resp.status_code == 200: return resp.text except Exception: time.sleep(1) return None def run(self, urls): for url in urls: self.queue.put(url) threads = [] for _ in range(self.thread_num): t = threading.Thread(target=self.worker) t.start() threads.append(t) for t in threads: t.join() return self.results

thread_num不要设太大,5 到 8 个线程足够了,再多反而容易触发对方的频率限制。self.lock保护的是共享列表的写入操作,读操作一般不需要锁。queue.Queue本身是线程安全的,所以从队列取任务不用额外加锁。如果采集过程中频繁遇到 403,先把线程数降到 3 试试,大概率是并发太高被识别了。

5. 避坑与常见问题排查

5.1 Cookie 过期导致采集全部返回登录页

现象:脚本跑起来不报错,但抓到的数据全是空的,或者resp.text里出现登录表单的 HTML。

原因:知乎的 Cookie 有效期通常只有几天,过期后所有请求都会被重定向到登录页。代码里没有检测重定向的逻辑,所以看起来像是页面结构变了。

解决:在请求后加一个判断,检查返回内容里是否包含登录页的特征字符串(比如"登录"或"signin"),如果命中就打印警告并停止采集。同时把 Cookie 更新流程写进 README,每次跑之前先手动换一次。

5.2 验证码模型路径不匹配导致推理报错

现象:captcha_predict.py运行时报FileNotFoundError或ValueError: Unknown file format。

原因:_config.yml里的model_path指向的模型文件不存在,或者训练时保存的格式和推理时加载的格式不一致(比如训练存了.h5,推理按.pb加载)。

解决:先确认model_path指向的文件确实存在,再用os.path.exists做一次前置检查。训练脚本保存模型时统一用.h5格式,推理脚本也按.h5加载,不要混用。

5.3 KMeans 聚类结果每次运行都不一样

现象:同样的数据跑两次kmeans_clustering.py,聚类标签完全不同,用户画像对不上。

原因:KMeans 的初始质心是随机选的,没有固定random_state就会导致每次结果不同。

解决:在KMeans()里显式设置random_state=42,并且n_init设成 10 以上。如果数据量很大,还可以先用MiniBatchKMeans做粗聚类,再用标准 KMeans 精调。

5.4 多线程采集触发频率限制被封 IP

现象:单线程跑得好好的,一开多线程就大量返回 403,甚至整个 IP 被临时封禁。

原因:多线程并发请求间隔太短,对方的频率检测系统直接判定为异常流量。

解决:把线程数降到 3 到 5,并且在每个线程的请求之间加随机延迟(比如time.sleep(random.uniform(1, 3)))。如果已经被封,等 10 到 30 分钟再试,期间不要继续发请求。

5.5 jieba 分词结果包含大量无意义单字

现象:词频统计出来的 Top 50 里全是「的」「了」「是」这类停用词,真正有意义的词被淹没了。

原因:停用词表不完整,或者过滤逻辑只过滤了固定几个词,没有覆盖实际数据里的高频噪声词。

解决:先跑一遍全量分词,把 Top 100 高频词导出来人工过一遍,把噪声词补进停用词表。停用词表建议单独存成stopwords.txt,代码里用set加载,比硬编码在脚本里好维护。

6. 进阶技巧:把零散脚本串成可复用的分析流水线

单个脚本跑通只是第一步,真正省时间的是把它们串成一条流水线。我一般会写一个pipeline.py做调度,按「采集 → 清洗 → 分词 → 聚类 → 可视化」的顺序依次执行,中间结果落盘,任何一步失败都能从断点续跑。

import os import pandas as pd from datetime import datetime def run_pipeline(keyword, output_base='./output'): """完整分析流水线""" run_id = datetime.now().strftime('%Y%m%d_%H%M%S') run_dir = os.path.join(output_base, run_id) os.makedirs(run_dir, exist_ok=True) # Step 1: 采集 print("[1/5] 采集问题列表...") from get_questions_v2 import get_questions questions = get_questions(keyword, max_pages=3) df_q = pd.DataFrame(questions) df_q.to_csv(os.path.join(run_dir, 'questions.csv'), index=False, encoding='utf-8-sig') # Step 2: 分词与词频 print("[2/5] 分词与词频统计...") from zhuanlan_process import analyze_text titles = df_q['title'].tolist() word_freq = analyze_text(titles, top_n=100) df_wf = pd.DataFrame(word_freq, columns=['word', 'count']) df_wf.to_csv(os.path.join(run_dir, 'word_freq.csv'), index=False, encoding='utf-8-sig') # Step 3: 聚类 print("[3/5] 用户兴趣聚类...") from kmeans_clustering import cluster_users labels, best_k, _ = cluster_users(titles) df_q['cluster'] = labels df_q.to_csv(os.path.join(run_dir, 'questions_clustered.csv'), index=False, encoding='utf-8-sig') # Step 4: 可视化 print("[4/5] 生成词云...") try: from wordcloud import WordCloud import matplotlib.pyplot as plt wc = WordCloud(font_path='simhei.ttf', width=800, height=400, background_color='white') wc.generate_from_frequencies(dict(word_freq)) plt.figure(figsize=(10, 5)) plt.imshow(wc, interpolation='bilinear') plt.axis('off') plt.savefig(os.path.join(run_dir, 'wordcloud.png'), dpi=150, bbox_inches='tight') plt.close() except ImportError: print("wordcloud 未安装,跳过可视化。pip install wordcloud 后可启用") # Step 5: 汇总报告 print("[5/5] 生成汇总...") summary = { 'keyword': keyword, 'question_count': len(df_q), 'best_k': best_k, 'top_words': [w for w, _ in word_freq[:10]], 'run_dir': run_dir } pd.DataFrame([summary]).to_csv(os.path.join(run_dir, 'summary.csv'), index=False, encoding='utf-8-sig') print(f"流水线完成,结果保存在 {run_dir}") return summary if __name__ == '__main__': run_pipeline('Python数据分析')

这个流水线有几个设计取舍值得说。第一,每一步的中间结果都落盘成 CSV,用utf-8-sig编码是为了 Excel 打开不乱码,这个细节在给非技术同事看数据时特别重要。第二,可视化步骤用try/except ImportError包起来,因为wordcloud不是核心依赖,没装也不应该阻塞整个流程。第三,run_id用时间戳命名,每次跑都是独立目录,不会互相覆盖,想对比不同关键词的结果直接看目录就行。

验证流水线是否正常,最简单的办法是跑一个小规模测试:max_pages=1,关键词用一个冷门词,看输出目录里是不是五个文件都生成了。如果questions.csv是空的,回去查 Cookie;如果word_freq.csv里全是单字,回去补停用词表;如果wordcloud.png没生成,检查simhei.ttf字体文件在不在当前目录。

从那以后我每次拿到新的爬虫类源码包,都强制先跑一遍最小闭环——一个关键词、一页数据、一条完整链路——确认能通再上量。这份知乎数据分析系统也一样,别一上来就改max_pages=100,先让它在最小规模下跑通,再逐步加参数。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 13:24:46

tushare+TensorFlow2.0:用RNN/LSTM预测贵州茅台开盘价

简介:面向金融时序预测与深度学习入门人群,这份资源以贵州茅台历史行情为例,演示如何通过tushare获取真实A股数据,并基于TensorFlow 2.0搭建RNN和LSTM模型预测开盘价,完整覆盖数据抓取、清洗归一化、模型构建、训练评估…

作者头像 李华
网站建设 2026/10/1 13:24:32

制造业AI智能体落地:五道门槛与选型复制指南

去年年底参加一场制造业数字化转型的交流会,茶歇时有位汽车零部件厂的IT负责人对我说了句印象很深的话:朋友圈里别人的AI智能体都能写代码、自动做报表了,我们车间里连设备报工还得靠人工录,这差距是不是已经追不上了?…

作者头像 李华
网站建设 2026/10/1 13:23:43

LSTM温度预测实战:从数据预处理到PyTorch模型训练与避坑指南

简介:一套用于温度预测的Python期末大作业项目,基于LSTM神经网络实现,面向计算机相关专业正在完成课程设计或期末大作业的学生,也适合需要时间序列预测实战经验的开发者。项目经导师指导并以98分评审通过,完整呈现数据…

作者头像 李华
网站建设 2026/10/1 13:23:14

普通人如何走好工程师之路:从自学到站稳脚跟的完整指南

拿到这个标题,我就知道帖主想聊的不只是技术,而是一条完整的成长轨迹。入行多年,我见过太多人把“工程师”理解成单纯的写代码、调接口,结果被现实撞得头破血流。“我的工程师之路,给需要的同学”这个标题,…

作者头像 李华
网站建设 2026/10/1 13:23:06

基于LSTM的电商评论情感分析:从数据清洗到模型部署的完整实战

简介:这份资源是面向计算机相关专业学生与Python实战学习者的深度学习项目包,以LSTM为核心完成电商购物评论的情感分析任务,可直接用于毕业设计、课程设计或期末大作业。项目围绕京东商城购物评论展开,涵盖数据采集、中文分词与停…

作者头像 李华
网站建设 2026/10/1 13:22:33

爬虫与LSTM预测实战:从数据采集到机器学习分析完整链路

简介:这是“爬虫与数据分析实践”完整项目包,集成信息爬取、LSTM时间序列预测与机器学习分析三条主线,覆盖从数据采集、清洗、建模到结果可视化的完整流程,适合计算机、人工智能、自动化、物联网等专业高校学生用于毕设、课设、作…

作者头像 李华