Python爬虫与数据分析实战:六个真实案例走通全流程
2026/9/15 17:55:37 网站建设 项目流程

简介:这是一份基于Python爬虫与数据分析的实战项目合集,包含完整源码、数据集和详细文档,适合计算机相关专业学生用于课程设计、毕业设计或项目初期演示,也适合爬虫与数据分析入门者进阶学习。包内共17个文件,涵盖Jupyter Notebook分析笔记、CSV数据集、Python脚本、YAML配置、字体、图片及Markdown说明等,压缩包仅7.07MB,结构清晰便于按需使用。项目覆盖多个真实场景:Titanic乘客生存预测与特征分析、星巴克门店分布数据探索、微信好友信息可视化、京东图书信息爬取,以及基于Selenium的斗鱼直播间数据采集等,代码均经过运行验证,可直接复用或修改扩展。目前已有116人学习,适合希望快速获取完整可运行案例、提升爬虫与数据分析实战能力的读者。

1. 爬虫加数据分析项目包为什么值得把六个案例一起看

不少人学过爬虫,认为自己能拿到数据就算会了。真正进入工作后才发现,抓下来的数据几乎没有一份是干净的:字段缺失、类型错乱、同一种信息在不同页面表示完全不同。反过来看,很多人做数据分析时也常常被数据源卡住,辛辛苦苦调模型,结果输入数据本身就埋了雷。这套基于Python爬虫与数据分析的实战项目,把泰坦尼克乘客记录、星巴克门店、微信好友信息、京东图书、斗鱼直播间和拉勾网职位数据放进同一个资源包,对着六个真实场景把“采集—清洗—分析—可视化—建模”完整跑了一遍。里面既有适合刚起步的Requests静态抓取,也有Selenium处理动态页面的完整脚本,还有Pandas特征工程和Pyecharts可视化代码。对于正在准备课程设计、毕业设计或刚换到数据方向的开发者来说,等于拿到了一批可以直接换模板复用的代码。

2. 静态网页到动态页面:Requests、Selenium与反爬参数配置

抓取是整个项目的起点,但很多人在第一步就停住了:目标网站到底是直接把数据放在HTML里,还是靠JavaScript动态渲染。京东图书和斗鱼直播间正好代表了这两种典型场景。

2.1 为什么京东图书用Requests,斗鱼必须上Selenium

京东的图书搜索结果页在早期版本中服务端渲染占多数,页面返回的HTML里能直接看到商品标题、链接和价格,所以用Requests加一个解析库就够了。斗鱼直播间列表不同,房间标题、主播名、在线热度等核心字段由页面里的异步请求或JavaScript模板动态生成,直接用requests.get拿到的只是一段带空容器的骨架代码。

判断方法不复杂。用浏览器打开目标页面,右键“查看网页源代码”,如果能在源代码里搜到你要的字段,就属于静态渲染,可以走Requests;如果源代码里只有js文件名和初始化配置,就必须用Selenium这类真实浏览器工具。

2.2 第一版爬虫:Requests加 Session保持连接

以抓取京东图书为例,我当时是按照下面这个结构写的:

import requests from bs4 import BeautifulSoup import random import time base_url = "https://search.jd.com/Search?keyword=Python&enc=utf-8" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Accept": "text/html,application/xhtml+xml", "Referer": "https://www.jd.com/" } sess = requests.Session() sess.headers.update(headers) resp = sess.get(base_url, timeout=10) resp.encoding = resp.apparent_encoding # 防止中文乱码 soup = BeautifulSoup(resp.text, "lxml") items = soup.select(".p-name a") for a in items[:10]: print(a.get_text(strip=True))

这里必须说明三个参数的习惯。第一,timeout=10不只是防止卡死,也是反爬中的“礼貌要求”,避免对目标服务器造成长连接压力。第二,requests.Session()能复用底层TCP连接,保持Cookie,后续翻页请求不用重新做身份校验。第三,resp.apparent_encoding是根据页面字节内容推断编码,比直接设置utf-8更稳,京东这类站点的响应头编码声明经常和实际不一致。

京东搜索页实际还有一部分接口走异步,所以这个脚本只能拿到第一屏的内容。项目里的处理方式是把URL中的页码参数循环拼接,配合下面这段随机延时。

links = [] def fetch_page(page): url = f"{base_url}&page={page}" resp = sess.get(url, timeout=10) soup = BeautifulSoup(resp.text, "lxml") for a in soup.select(".p-name a"): title = a.get_text(strip=True) if title: links.append(title) time.sleep(random.uniform(0.8, 2.5))

延时区间定在0.8到2.5秒,而不是固定2秒,是为了防止爬取频率形成等差数列。固定间隔反而容易被服务端识别为脚本特征。并发场景下,不同线程共用一个Session要格外小心,requests的Session本身不是线程安全的,高并发时需要每个线程单独创建Session。

2.3 动态页面用Selenium处理斗鱼直播间

斗鱼直播间的房间数据是典型的动态渲染,项目里用了Selenium来做浏览器自动化:

from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By import time options = Options() options.add_argument("--headless") options.add_argument("--disable-gpu") options.add_argument("--no-sandbox") # 去掉自动化控制提示,避免早期被识别 options.add_experimental_option("excludeSwitches", ["enable-automation"]) driver = webdriver.Chrome(options=options) driver.get("https://www.douyu.com/directory/all") time.sleep(3) # 等待JS渲染完成 cards = driver.find_elements(By.CSS_SELECTOR, ".DyListCover-intro") for card in cards[:10]: title = card.text.strip() if title: print(title) driver.quit()

--headless让浏览器在后台运行,适合部署在服务器上。--disable-gpu在部分Linux环境下可以减少渲染报错,尤其在缺少GPU驱动的机器上。time.sleep(3)是动态页面爬虫里的基本“等待成本”,可以换成显式等待(WebDriverWait),但代价是代码复杂度上升,demo项目里保留sleep更直观。还要注意,如果页面元素选择器很弱,Selenium很容易抓到空文本,所以代码里加了if title判断。

2.4 爬虫并发设计:线程池参数表

项目里拉勾网数据文件较大,抓取时通常不止单线程。对比Java里常见的HttpClient+Jsoup组合,Python最大的优势是用ThreadPoolExecutor几行就能完成并发,但并发设计不好会直接把目标站打挂。下面是一个最简版本:

from concurrent.futures import ThreadPoolExecutor, as_completed import random import time urls = [f"https://search.jd.com/Search?keyword=Python&page={i}" for i in range(1, 11)] def fetch_one(url): resp = sess.get(url, timeout=10) time.sleep(random.uniform(0.3, 0.8)) return resp.status_code, url with ThreadPoolExecutor(max_workers=3) as pool: future_map = {pool.submit(fetch_one, u): u for u in urls} for future in as_completed(future_map): code, url = future.result() print(code, url)

这段代码里max_workers=3是经过权衡的值。对于搜索类页面,三到五个并发已经能压出足够的吞吐量,调高到十以上,带来的提升会被目标限流抵消,反而丢请求。动态页面和静态页面的并发策略也应不同:Selenium多开浏览器非常耗内存,如果机器只有8G内存,最多两三个driver实例;Requests是轻量请求,可以开到五到八个线程。下面这张表是这个项目里个人习惯的参数选择:

页面类型工具并发数每次请求间隔失败重试次数
静态图书Requests51秒2
动态直播间Selenium22秒3
AJAX职位接口Requests80.5秒3

重试逻辑一般用计数器加time.sleep实现,连续失败三次以上直接写日志停止,防止封IP连带影响后面的数据抓取。这里不推荐任何人使用所谓“代理池”或隐藏服务,正经的业务爬虫最好的策略是控制频率、尊重robots.txt,把精力放在解析和数据质量上。

3. 用Pandas处理Titanic和拉勾数据:缺失值、薪资区间与特征工程

抓下来只是拿到原材料,真正决定模型上限的是清洗这一步。项目里的拉勾数据集和泰坦尼克数据集分别代表两类常见问题:文本字段需要正则拆分,结构化字段存在缺失和不一致。

3.1 拉勾网数据里的薪资区间解析

拉勾网职位数据的salary字段长这样:"15k-25k""20k-40k""面议"。如果不做处理直接进模型,每个值都是无意义的字符串。项目里的做法是提取数字区间,再转成中位数:

import pandas as pd import re df = pd.read_csv("lagou_DT.csv") def parse_salary(s): if not isinstance(s, str): return None nums = re.findall(r"\d+", s) if len(nums) >= 2: low = int(nums[0]) high = int(nums[1]) return (low + high) / 2 return None df["salary_mid"] = df["salary"].apply(parse_salary)

正则表达式\d+一次匹配一个连续数字,findall返回所有匹配项。常见坑是"15k以上"这种单端区间只有一段数字,直接取nums[0]就会丢失信息,稳妥做法是业务侧规定统一口径:单端区间乘以1.2或补贴中位数。更极端的情况是区间端点单位不同,比如"1万-15k",还要先统一单位再计算,现实中这种脏数据非常多。

除了薪资,城市字段也存在常见格式不一致,"上海市""上海"同时存在,用replace统一:

df["city"] = df["city"].astype(str).str.replace("市", "") df["publish_time"] = pd.to_datetime(df["publish_time"], format="mixed")

astype(str)是为了保证字段是字符串,避免数字被误判成月份。pd.to_datetimeformat="mixed"参数是Pandas 2.0以后支持的,能同时兼容"2024-01-01""2024/01/01"两种写法,老项目里没写过这个参数,通常会报错或者解析失败。

3.2 Titanic缺失值处理和字段映射

Titanic数据集是经典的分类问题,但原始CSV里有很多空值。项目里titanic.ipynb的处理顺序很值得参考:

第一步,先查看缺失率,但同时要看字段语义:

df_t = pd.read_csv("titanic.csv") missing_rate = df_t.isnull().mean().round(4) print(missing_rate)

第二步,把字段分为“数值连续型”和“类别型”,分别用不同策略填充:

df_t["Age"] = df_t["Age"].fillna(df_t["Age"].median()) df_t["Embarked"] = df_t["Embarked"].fillna(df_t["Embarked"].mode()[0]) df_t["CabinKnown"] = df_t["Cabin"].notna().astype(int)

为什么年龄用中位数而不是平均数?年龄分布存在右偏,少数高龄乘客会把均值拉高,用中位数更能代表中间水平。登船港口是离散类别,众数只填补了缺失的极少数样本,用均值反而会产生一个不存在的“港口”。船舱字段Cabin的缺失率极高,直接删除会损失信息,项目里把它变成一个二分类特征CabinKnown,表示“是否知道船舱号”,这在Kaggle上是很常见的实用技巧。

第三步是特征工程:

df_t = pd.get_dummies(df_t, columns=["Sex", "Embarked"], drop_first=True)

drop_first=True会把性别男性和登船港口S作为基准列剔除,避免线性模型产生多重共线性。这里有一个必修的概念:get_dummies生成的是数值0和1,不是True/False,送到sklearn里不会出现类型兼容问题。

3.3 清洗后字段对应表

整理一张表出来,方便做其他项目时对照:

原始字段类型清洗动作转换结果
Age连续数值中位数填充float64
Fare连续数值不做处理float64
Cabin字符串判断是否非空0/1整数
Embarked字符串众数填充 + get_dummiesEmbarked_Q、Embarked_S
Sex字符串get_dummiesSex_male
Salary字符串正则提取薪资中位数

清洗后的数据要注意检查两点:一是所有字段类型是否统一为数值,二是isnull().sum()是否归零。这里的核心原则是“填充动作必须有业务依据”,不能让缺失值被随意替换成0。如果发现年龄缺失样本明显偏老或偏年轻,还要考虑是否缺失机制本身与合作不同。

4. 数据可视化和模型验证:Pyecharts绘图乱码与Sklearn评估

数据清洗完之后,项目里有两条输出线:一条是给非技术角色看的结果展示,对应Pyecharts可视化;另一条是给技术验证用的模型评估,对应Titanic预测。

4.1 微信好友数据用Pyecharts画省分布

微信好友分析部分对应friend_pyecharts.ipynb,核心输入是friend.csv,里面有好友昵称、省份、城市、签名等字段。展示时一般先做透视:

import pandas as pd from pyecharts.charts import Bar from pyecharts import options as opts friend_df = pd.read_csv("friend.csv") province_data = friend_df["province"].value_counts().head(15) province_df = province_data.reset_index() bar = ( Bar() .add_xaxis(province_df["index"].tolist()) .add_yaxis("好友数", province_df["province"].tolist()) .set_global_opts( title_opts=opts.TitleOpts(title="微信好友省份分布"), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=45)), ) ) bar.render("wechat_province.html")

这段代码里province_data.reset_index()把原来Series的索引变成一列普通数据,否则xaxis无法直接对齐。rotate=45是为了处理省份名过长叠字。Pyecharts默认渲染出单独的HTML文件,如果要在Jupyter Notebook里直接展示,需要加一行bar.render_notebook(),两种方式生成的图表交互效果一样。

中文乱码是这个模块最经常遇到的问题。Pyecharts底层字体对中文支持不稳定,项目压缩包里特意包含了SimHei.ttf,就是用来解决这个问题的。常见做法是先注册字体文件:

from pyecharts.globals import CurrentConfig, NotebookType import os CurrentConfig.ONLINE_HOST = "localhost:8888"

还可以直接设置全局字体路径,但最简单可靠的办法是确认系统存在中文字体,再把图表中的name改为中文前先用matplotlib测试一遍字体是否生效:

import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["SimHei"]

4.2 Titanic建模与混淆矩阵

Titanic部分用逻辑回归做基准模型,代码不复杂,关键是评估方式:

import pandas as pd from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, confusion_matrix cols = ["Age", "Fare", "CabinKnown", "Sex_male", "Embarked_Q", "Embarked_S"] X = df_t[cols] y = df_t["Survived"] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) lr = LogisticRegression(max_iter=500, C=0.5) lr.fit(X_train, y_train) pred = lr.predict(X_test) print("准确率:", accuracy_score(y_test, pred)) print(confusion_matrix(y_test, pred))

max_iter=500是逻辑回归收敛的最大迭代次数,数据量小、变量少时默认100也能大概跑通,但偶尔会报“ConvergenceWarning”,所以给足余量。C=0.5是正则化强度的倒数,值越小正则化越强,防止过拟合。这里的混淆矩阵输出是一行两列或两行两列的数字,左上角是正确预测未生还,右下角是正确预测生还,其他位置是误判。

如果只看准确率,在样本不平衡的数据集上会被欺骗。比如Titanic中生还比例约38%,一个全预测未生还的模型准确率也有62%。所以项目里把混淆矩阵和准确率同时打印出来。还要注意特征列Sex_maleEmbarked_Q这些都是get_dummies产生的0/1列,在逻辑回归决策边界里,性别特征的系数往往最大,这说明它贡献了主要区分度。

4.3 星巴克门店数据的分析角度

除了Titanic,项目里的星巴克数据分析很适合用来理解“业务分析”和“算法模型”的区别。Starbucks.ipynb处理的是门店经纬度、营业时间和产品线,更关注分布密度和商圈覆盖,使用Pandas的groupby统计城市门店数,再和人口数据关联。这类分析不追求预测准确率,而是要能在图表里讲出“哪个区域适合开店”的结论,使用的工具是foliumplotly,如果你不熟悉,可以参考项目里store-locations.zip对应的读入方式。

store_df = pd.read_json("store-locations.json") city_grp = store_df.groupby(["city", "ownershipType"]).size().reset_index(name="count") print(city_grp[city_grp["city"].isin(["Shanghai", "Beijing", "New York"])])

这里用groupbyreset_index生成一个新DataFrame,再用city.isin做过滤。和Titanic不同,这段任务没有训练集和测试集,更强调业务聚合结果,属于数据分析而非机器学习建模。两个案例放在一个项目包里,正好提醒我们不要把所有数据问题都硬套模型。

5. 把项目脚本串成流水线并解决页面改版问题

前面几章分开看都是独立任务,真正用于课程或生产环境时,必须考虑调度和可维护性。最后一个实战技巧是把这个包的零散脚本变成一套可以重复执行的流程。

5.1 用Shell脚本把爬虫和分析串成一条任务链

项目内已经包含多个.ipynb.py文件,在服务器上建议提取出主流程脚本,用Shell脚本控制执行顺序和日志:

#!/bin/bash set -e cd /opt/spider_data_analysis echo "=== 1. 拉取京东图书 ===" python3 jd_books_spider.py >> logs/jd_books.log 2>&1 echo "=== 2. 拉取斗鱼直播间数据 ===" python3 douyu_spider.py >> logs/douyu.log 2>&1 echo "=== 3. 清洗拉勾数据 ===" python3 lagou_clean.py echo "=== 4. 生成可视化报告 ===" python3 wechat_chart.py

这里用set -e表示任意一步失败就立刻停止,防止拿着不完整的数据继续往下跑。每条命令尾部的>> logs/x.log 2>&1同时把正常输出和错误日志送到同一个文件,出问题时翻日志非常方便。如果有多台机器,可以把步骤1和步骤2分发到不同机器,但本地项目按顺序执行更稳妥。

5.2 页面改版后的三类检查

爬虫项目最怕页面结构更新,第一反应不是改代码,而是先确认是不是被反爬策略拦截了。打开目标站点的响应快照,按下面顺序排查:

现象可能原因应对方式
请求返回200拿到的却是空列表数据改为异步接口抓包找XHR接口,用Requests直接调JSON接口
返回403或验证码频繁请求触发风控降低线程数,增加随机延时
Selenium能打开但元素找不到页面重渲染、iframe或Shadow DOM打开开发者工具,重新复制selector,检查iframe切换

保存响应内容也很关键。页面改版后,旧代码解析不出字段,这时候还能从前一天的resp.text里离线分析结构,而不是重新请求服务器。

5.3 中间数据的校验

流水线跑完,必须有一步验证数据量。最简单的是统计CSV行数和关键字段的缺失率:

import pandas as pd import sys df = pd.read_csv("lagou_clean.csv") required_cols = ["city", "salary_mid", "company_name"] for c in required_cols: if c not in df.columns: print(f"缺少字段: {c}") sys.exit(1) if df.empty: print("数据为空,请检查上游爬虫") sys.exit(1) if df["salary_mid"].isnull().any(): print("存在薪资缺失,检查正则表达式") sys.exit(1)

这段代码放在整个流水线末尾,能避免“看起来跑完了,实际全是废物数据”的情况。对于从爬虫到分析的全链路项目,这个步骤比模型调到99%准确率更重要。项目包里拿到的授权码不是用来天天验证的,应该理解为“可以放心补全这个流程”的许可——实际使用时,还是建议先把数据校验这一步写好。

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

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

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

立即咨询