PY-10 进阶应用 爬虫与数据分析
10 · 进阶应用:爬虫 + 数据分析
📅 预计 120 分钟 | 🎯 爬虫与数据分析实战
✍️ 本讲配套练习:本地python grader.py 10,或网页练习
🖥 你已在本项目spider-demo/里跑通过两个真实爬虫(豆瓣 Top250 + B站热门),这里把它们讲透。
💡 一句话懂本讲:爬虫 = 派人去取情报,数据分析 = 把情报挑拣、归类、算出观点。 前半部分让机器替你上网抓数据,后半部分让抓回来的数据替你说话——这俩加起来,就是期末大作业的两根柱子。
本讲练习速览
练习 ex01 ~ ex20 从易到难,全部是纯数据处理题:数据都已经放进内存,你在浏览器里写完直接跑,不需要联网。题目用的数据形态和你亲手写的 spider-demo/ 完全一致,练完就等于把爬虫后处理的功夫练熟了。
| 题号 | 考点 | 对应讲义 |
|---|---|---|
| ex01~ex03 | 嵌套取字段 / 排序 TOP / 平均播放 | 10.3 解析 |
| ex04~ex06 | CSV 拆行 / 正则提取 / 清洗数字 | 10.3 / 10.5 |
| ex07~ex09 | 多字段提取 / 多条件排序 / 分区统计 | 10.5 变换与分组 |
| ex10~ex11 | 带引号 CSV / 抓全部标题 | 10.3 解析进阶 |
| ex12~ex14 | 去空去重 / 单位换算 / 剔异常值 | 10.5 清洗 |
| ex15~ex16 | 筛选聚合 / 中位数众数 | 10.5 统计 |
| ex17~ex18 | 格式化输出 / 文本提取数字 | 10.5 输出与正则 |
| ex19~ex20 | 综合:CSV 流水线 / 报告生成 | 10.5 综合实战 |
10.1 网络爬虫是什么
把网站想象成一座藏有情报的仓库,爬虫就是你派出去的情报员,流程永远三步:
- 发请求:情报员走到仓库门口,递上证件(User-Agent),问"开门让我看看"。
- 解析:门开了,情报员在一堆文件里翻出你要的那几条,抄到本子上。
- 存储:把本子带回驻地,整理成 CSV / JSON 归档,方便后续分析。
你已亲手跑过的两个案例,恰好覆盖爬虫的两条主流路线:
| 案例 | 路线 | 核心 | 文件 |
|---|---|---|---|
| 豆瓣 Top250 | 解析 HTML | requests + BeautifulSoup + CSS 选择器 |
spider-demo/douban_top250.py |
| B站热门 | 调 JSON 接口 | requests 直接拿 JSON |
spider-demo/bilibili_hot.py |
判断走哪条路的诀窍:打开浏览器 F12 → Network(网络) 看请求,如果有返回 JSON 的接口就走 B,没有就走 A。JSON 接口永远更省事——因为它本来就是数据结构,不用像 HTML 那样拿选择器去"抠"。
10.2 第一步:发请求
import requests
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."}
resp = requests.get(url, headers=HEADERS, timeout=10)
print(resp.status_code) # 输入 url → 输出 200(= 成功)
print(resp.text) # 输入 url → 输出 网页/JSON 原文
⭐ 四要点:
- User-Agent:假装自己是浏览器。网站看到"我是程序"会直接赶人(反爬)。
- status_code:先检查是不是 200,防爬失败却无感知。
- timeout:必带,防网络卡死让你的程序挂住不动。
- Windows 中文输出:加
sys.stdout.reconfigure(encoding='utf-8'),否则终端 GBK 会乱码——这是你踩过的坑。
⚠️ 常见错误(发请求)
- 不写 User-Agent:被 403/418 拒绝,还以为自己代码写错了。
- 不查 status_code:拿到的是 404 错误页,还硬着头皮往下解析,解析出一堆空。
- 不写 timeout:某次请求卡住,整个程序无限等待。
- 中文乱码:忘了强制 UTF-8,标题全变
????。
10.3 第二步:解析
路线 A:HTML 解析(BeautifulSoup)
HTML 像一本目录很混乱的书,CSS 选择器就是书签,指哪翻哪:
from bs4 import BeautifulSoup
soup = BeautifulSoup(resp.text, "html.parser")
items = soup.select(".grid_view .item") # 选所有 class 为 item 的块
for it in items:
title = it.select_one(".title").text # 块内再找 .title,取文字
print(title)
常用选择器速记:标签(如 p)、.类名(如 .title)、#id(如 #main)、外层 内层(后代)、.a .b(嵌套取)。
路线 B:JSON 接口(更常用、更省事)
JSON 长得就像快递单:最外层是包裹,里面一层套一层,每个格子里都贴着标签(键)。取数据不用翻找,直接按标签取:
data = resp.json()["data"]["list"] # B站热门视频都在这里
for v in data:
print(v["title"], v["stat"]["view"]) # 标题、播放量
B站热门接口返回的一个视频 v,长这样(本讲练习都用这个结构):
{
"title": "我们拍到了水下风暴", # 标题
"owner": {"name": "影视飓风"}, # UP主(嵌套字典)
"tname": "数码", # 分区
"stat": {"view": 3135749, "like": 225790, "danmaku": 14126}, # 播放/点赞/弹幕
}
取嵌套字段的规律:有多少层,就写多少个中括号。v["owner"]["name"] 是"UP主的名字",v["stat"]["view"] 是"播放量"。漏一层、错一层都会 KeyError。
🔗 本讲练习 ex01(取字段)、ex07(一次取多个字段)就是练这个。
解析进阶:字符串处理三件套
爬下来的数据不全是干净的结构,经常是"夹在文本里"的,这时用字符串和正则:
import re
line = "播放: 3135749,点赞: 225790"
nums = re.findall(r"\d+", line) # 抓所有连续数字
print(nums) # ['3135749', '225790']
html = "<title>A</title><title>B</title>"
print(re.findall(r"<title>(.*?)</title>", html)) # ['A', 'B']
CSV 行也常带引号,引号里可能还藏逗号(简单 split(",") 会拆错):
row = '"小明,软件工程",85'
fields = []
cur, in_q = "", False
for ch in row:
if ch == '"':
in_q = not in_q # 引号内逗号不算分隔符
elif ch == "," and not in_q:
fields.append(cur); cur = ""
else:
cur += ch
fields.append(cur)
print(fields) # ['小明,软件工程', '85']
🔗 ex04(简单 CSV)、ex10(带引号 CSV)、ex11(抓全部标题)、ex18(文本提取数字)。
⚠️ 常见错误(解析)
- 嵌套层数数错:
v["stat"]["view"]写成v["stat.view"]或v["stat"],直接KeyError。 - 用
split(",")拆带引号的 CSV:字段里有逗号就被拆成两半。 - 正则贪婪匹配:
<title>.*</title>会把两个标题中间的字符一起吞掉;用.*?非贪婪。 - json 解析失败:接口返回的不是 JSON(被反爬返回了 HTML 错误页),
resp.json()直接抛异常。
10.4 第三步:存储
爬完别丢内存里,落盘才叫"存住了"。两种主流格式:
import csv, json
# CSV(Excel 可开;utf-8-sig 防中文乱码)
with open("data.csv", "w", newline="", encoding="utf-8-sig") as f:
w = csv.writer(f)
w.writerow(["标题", "播放量"])
w.writerow([v["title"], v["stat"]["view"]])
# JSON(保真:字段、嵌套、类型都不丢)
with open("data.json", "w", encoding="utf-8") as f:
json.dump(all_data, f, ensure_ascii=False) # ensure_ascii=False 保留中文
⚠️ 常见错误(存储)
- CSV 用
"w"写中文:没加utf-8-sig,Excel 打开乱码。 - JSON 不写
ensure_ascii=False:中文全变成\uXXXX转义。 newline=""忘写:Windows 上写 CSV 每行之间多空一行。
10.5 数据分析:先用手,再上库 ⭐
练习环节里数据都已经在内存里了,这一节教你纯 Python 的整套后处理流程(浏览器里就能跑),是本讲练习的重点。核心就四件事:清洗 → 变换 → 统计 → 输出。
10.5.1 清洗:挑菜摘菜
爬来的数据像刚从菜场买回的菜:有黄叶(空值)、有重复、有带泥的(脏格式)、有烂的(异常值)。清洗就是把好菜挑出来。
# ① 去空去重(保持顺序)—— ex12
titles = ["A", " ", "B", "", "A"]
seen, out = set(), []
for t in titles:
if not t.strip(): # 空串 / 全空白 → 跳过
continue
if t not in seen:
seen.add(t); out.append(t)
print(out) # ['A', 'B']
# ② 类型转换:B站播放量带"万/亿"单位 —— ex13
def to_view(s):
s = s.strip()
if not s:
return 0
if s.endswith("万"):
return round(float(s[:-1]) * 10000)
if s.endswith("亿"):
return round(float(s[:-1]) * 100000000)
return int(float(s))
print(to_view("1.5万")) # 15000
print(to_view("2.3亿")) # 230000000
print(to_view("123")) # 123
# ③ 剔异常值:播放量为负是脏数据,超上限像刷量 —— ex14
def clean_view(v):
return 0 <= v["stat"]["view"] <= 10_000_000
10.5.2 变换:映射 / 筛选 / 聚合
数据加工的三板斧,用列表推导式最顺手:
videos = [
{"title": "A", "stat": {"view": 100, "like": 500}},
{"title": "B", "stat": {"view": 300, "like": 100}},
{"title": "C", "stat": {"view": 200, "like": 500}},
]
views = [v["stat"]["view"] for v in videos] # 映射:每个都取出来
hot = [v["title"] for v in videos if v["stat"]["like"] >= 200] # 筛选:点赞达标的
avg = round(sum(views) / len(views), 1) # 聚合:求和再平均
print(views, hot, avg) # [100, 300, 200] ['A', 'C'] 200.0
🔗 ex15(筛选 + 聚合的组合)、ex03(均值)、ex19/ex20(整条流水线)。
10.5.3 排序与分组
排序:sorted 的 key 指定"按什么排",reverse=True 从高到低。多条件就把多个键打包成元组:
# 按播放量从高到低 —— ex02
top = sorted(videos, key=lambda v: v["stat"]["view"], reverse=True)
print([v["title"] for v in top]) # ['B', 'C', 'A']
# 多条件:先播放降序,播放相同再按点赞降序 —— ex08
top2 = sorted(videos,
key=lambda v: (v["stat"]["view"], v["stat"]["like"]),
reverse=True)
分组统计:遍历一遍,用 dict.get(key, 0) + 1 计数,几行搞定:
cats = ["科技", "美食", "科技"]
count = {}
for c in cats:
count[c] = count.get(c, 0) + 1
print(count) # {'科技': 2, '美食': 1}
🔗 ex08(多条件排序)、ex09(分区统计)、ex19(分组求均值)。
10.5.4 统计:中位数与众数
除了求和、均值、最大最小,分析里常用的还有中位数(排中间的值,抗异常值)和众数(出现最多的值):
def median_and_mode(nums):
a = sorted(nums)
n = len(a)
mid = n // 2
med = a[mid] if n % 2 == 1 else (a[mid - 1] + a[mid]) / 2
counts = {}
for x in nums:
counts[x] = counts.get(x, 0) + 1
mode = nums[0] # 次数相同保留先出现的
for x in nums:
if counts[x] > counts[mode]:
mode = x
return med, mode
print(median_and_mode([3, 1, 2, 2])) # (2.0, 2)
print(median_and_mode([5, 5, 1, 1, 3])) # (3, 5)
🔗 ex16 就练这个。注意:中位数要先排序再取中间。
10.5.5 格式化输出
分析结果要"排得整齐"才好看,f-string 的格式化语法是利器:
def format_row(rank, title, view):
return f"{rank:>3} {view:>8,} {title}" # 右对齐3位、播放量千分位右对齐8位
print(format_row(1, "凡人修仙传", 54517)) # 1 54,517 凡人修仙传
print(format_row(2, "小时候看不懂", 49744)) # 2 49,744 小时候看不懂
f"{x:>8,}" 里 > 右对齐、8 占 8 个宽度、, 自动加千分位。排行榜一下就有了"榜单感"。
🔗 ex17 就练这个;ex20 把整条流水线做成最终报告。
⚠️ 常见错误(数据分析)
- 空列表直接求均值:
len(views)为 0 会ZeroDivisionError,先判空。 - 直接
int()转换脏数据:int("1.5万")直接ValueError,要先去掉单位、用try/except兜底。 in与not in混淆:去重判断写反,全被当重复项丢掉。- 中位数忘了排序:直接取中间下标,结果完全错。
dict.get不写默认值:count[c] += 1遇到新键直接KeyError。
10.6 数据分析:pandas + matplotlib(进阶可选)
数据多了以后,手写容易累,交给专业库。核心三件事:读数据 → 筛选/分组/排序 → 统计/出图。
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv("bili_hot.csv") # DataFrame 数据帧 ⭐
df.head() # 前几行
df["播放"].max() # 最大播放
df["分区"].value_counts() # 各分区数量 ⭐
top = df.sort_values("播放", ascending=False).head(5)
df["分区"].value_counts().plot.bar() # 条形图
plt.title("B站热门视频分区分布")
plt.savefig("analysis.png") # 存图,交作业用
pandas 和手写的对应关系:df["列"] ≈ 列表推导取字段;value_counts() ≈ dict.get 计数;sort_values ≈ sorted(key=...)。先把 10.5 的手艺练熟,pandas 只是更快的手套。
🔗 练习阶段用纯 Python 版(浏览器跑不了 pandas),但代码套路和这节一模一样。
⚠️ 常见错误(pandas)
plt.show()在无界面上卡住:作业里用plt.savefig()存图更稳。- 列名用中文:
df["播放"]与 CSV 表头必须一字不差,多了空格就KeyError。 value_counts想按数值排序:先.sort_values(),别在图上硬看。
10.7 期末大作业套路(直接抄这个框架)
题目方向:选一个主题 → 爬数据 → 分析 → 可视化 → 结论。
① 选题(如"某平台热门内容分析")
② 爬虫脚本:spider.py,抓 N 条数据存 CSV
③ 分析脚本:analyze.py,pandas 统计 + matplotlib 出图
④ 报告:背景 / 方法 / 数据图 / 结论 / 代码附上
评分要点通常包含:工作量(真实数据)+ 创新(分析角度)+ 文档(报告规范)。本讲的 20 道练习覆盖中间"清洗 → 统计 → 出报告"的完整流程。
10.8 合规提醒
- 只爬公开数据,别碰需要登录/授权的数据。
- 控制频率(加
time.sleep(1)),别打爆人家服务器。 - 学习用途为主,爬来的数据别商用、别公开传播敏感信息。
📌 双语术语表(本讲)
| 中文 | English |
|---|---|
| 网络爬虫 | web crawler / scraper |
| 请求 | request |
| 解析 | parse |
| 选择器 | selector |
| 数据帧 | DataFrame |
| 可视化 | visualization |
| 清洗 | cleaning |
| 去重 | deduplication |
| 分组 | group by |
| 中位数 | median |
| 众数 | mode |
| 映射 | mapping |
| 筛选 | filtering |
| 聚合 | aggregation |
| 正则表达式 | regular expression |
| 格式化输出 | formatted output |
| 科学计算 | scientific computing |
| 统计 | statistics |
⭐ 本讲考点清单
- 爬虫三步:请求 → 解析 → 存储
requests+User-Agent+timeout+status_code- BeautifulSoup
select/select_one - JSON 接口优先(F12 Network),嵌套字典逐层取字段(
v["owner"]["name"]) - 字符串/正则解析:
re.findall、带引号 CSV、.*?非贪婪 - 清洗四件套:去空 / 去重 / 类型转换(万/亿单位)/ 剔异常值
- 变换三板斧:映射 / 筛选 / 聚合(列表推导式)
- 统计:求和 / 均值 / 中位数 / 众数 /
dict.get分组计数 - 多条件排序:
sorted(key=元组, reverse=True) - 格式化输出:
f"{x:>8,}" - pandas 读 CSV +
value_counts+sort_values;matplotlib 出图保存 - 大作业框架(爬 → 分析 → 可视化 → 报告)+ 合规
💡 记忆口诀
请求要证件,解析看类型;JSON 走捷径,HTML 用选择器。
清洗四件套:去空去重换格式、异常全剔掉。
变换三板斧:映射筛选再聚合,均值别忘先判空。
排序看 key,分组数 dict,中位先排序,输出用 f-string。
爬完别乱用:公开、慢速、学习用。