Python-2023-12 爬虫之豆瓣¶
内容梗概¶
这是"Web 分析与爬虫"章节的线下实战课(爬虫 + 可视化是一个大作业的前半部分)。老师先在白板上梳理了爬虫要面对的三种网站类型——静态网站、动态网站、数据接口,以及写爬虫的两个核心步骤和三个典型困难(User-Agent 伪装、登录 Cookie、IP 封禁)。随后进入实战:用 Jupyter Notebook 从零开始爬取豆瓣剧情/悬疑类电影榜单。直接请求页面发现列表数据是 JS 动态渲染的(BeautifulSoup 抓不到),于是改用开发者工具的 Fetch/XHR 筛选器找到背后的数据接口,直接请求接口拿 JSON,再通过分析瀑布流分页的 URL 规律(start 参数),用循环爬完全部 248 部电影的标题和链接。结尾留时间让学生照逻辑自己实操。
知识点详解¶
三种网站类型(约 01:58–13:00)¶
老师自总结的分类(非官方),"静态/动态/数据"都是对浏览器而言:
- 静态网站:服务器直接返回渲染好的 HTML。浏览器里肉眼看到的和爬虫抓到的一致——"浏览器里怎么操作,爬虫里就怎么操作"。
- 动态网站(约 06:51):服务器返回 HTML/CSS/JS,其中 JavaScript 会根据用户操作**动态生成新的 HTML**,关键信息往往由 JS 生成(如百度首页的搜索框)。难点:爬虫拿到的 HTML ≠ 浏览器显示结果,因为爬虫没有 JS 渲染环境。解决方案:借助第三方 JS 渲染工具,如 Selenium、PhantomJS,替你把 JS 渲染出最终 HTML(本课暂不涉及)。
- 数据接口(约 09:57):前后端分离技术普及后越来越常见。前端(浏览器)通过 HTTP 与后端交换 JSON 数据。爬虫的最终目的是拿数据,所以只要分析前端与后端的通信过程,直接请求数据接口拿到 JSON、转成字典存下来即可,比解析 HTML 更省事。本次项目就是"数据接口 + 静态网站"两种模式结合的例子。
爬虫的两个步骤与三个困难(约 13:54–24:40)¶
- 两个核心步骤:① 用程序模拟 HTTP 请求与服务器通信,拿到返回结果(不能直接说"拿到数据",因为返回的可能不是直接可用的数据);② 分析返回结果,拿到目标数据。
- 困难一:如何拿到与浏览器尽可能一致的返回结果——服务器通过 HTTP 请求头的 User-Agent 字段判断访问来源并给出不同响应。
- robots.txt(约 17:09):以 bilibili 为例,robots.txt 里对不同 spider/bot 规定了允许访问的范围(如只允许访问根目录),体现"不同程序访问同一网站会得到不同对待"。
- User-Agent 指"用户通过什么软件访问网站"(用户代理),与上网用的"代理"不是一回事。
- 困难二(进阶,了解即可):网站需要登录怎么办——登录信息保存在浏览器的 Cookie 里,可用工具导出 Cookie,让爬虫发请求时带上,伪装成已登录用户。规模化做法是**账号池(account pool)**。Cookie 很重要:XSS 漏洞、WiFi 钓鱼的目的往往就是窃取他人 Cookie 冒用身份。
- 困难三(进阶,了解即可):IP 被封怎么办——网站读访问日志发现某个 IP 高频抓取就会封禁;对策是**代理 + IP 池**,每次切换不同 IP(伪分布式)。老师调侃互联网上 80% 以上的流量可能是爬虫贡献的。
- 本课只用 User-Agent 伪装这一种,前后两个进阶问题先回避。
分析返回结果所需知识(约 24:46–29:20)¶
- 需要了解 HTML 解析、如何在 HTML 页面中定位关键元素——即 CSS 选择器(Web 开发中学过;课件里也有)。
- 若是动态网页,还需第三方工具先把 JS 渲染成 HTML 再解析。
- 借助浏览器自带的开发者工具来分析页面结构。
实战准备:Jupyter Notebook(约 29:24–32:40)¶
- 原视频例子是知乎,但知乎接口已封,改用豆瓣:目标是获取剧情/悬疑类电影榜单。
- 用 Jupyter Notebook 写爬虫,因为方便与后续数据分析对接。
- 用法:建项目文件夹(如桌面 demo)→ 在该文件夹的终端里运行
jupyter notebook。隐含知识:在哪个文件夹运行该命令,Notebook 的根目录就是哪个文件夹(Anaconda 已装则直接可用)。
实战第一步:requests 请求与 UA 伪装(约 32:40–38:00)¶
- 用 requests 库(网上搜爬虫第一时间被推荐的库):
requests.get(url)发 GET 请求,print(r.text)打印响应文本。 - 第一次直接请求豆瓣榜单页:返回**空内容**——被 User-Agent 识别拦截了。
- 伪装成浏览器:F12 开发者工具 → 网络标签页 → Ctrl+R 刷新重发所有请求 → 选中第一个请求 → 在请求标头中复制
User-Agent的值。 - 代码:构造一个**字典**
h = {"User-Agent": "..."},请求时传参requests.get(url, headers=h)——headers是 get 函数的参数名。再次请求即拿到真实 HTML 页面。
实战第二步:BeautifulSoup 解析与动态渲染的发现(约 38:00–48:00)¶
- 页面结构分析:右键"检查",发现每部电影在
movie-list-item结构中,内部规律一致(movie-content → movie-info → movie-name)。 - BeautifulSoup(第三方库,包名 bs4,Anaconda 环境自带):
from bs4 import BeautifulSoup,soup = BeautifulSoup(r.text, "html.parser")——用自带的 html.parser 解析引擎把 HTML 文本解析成结构化对象 soup。 - CSS 选择器定位:BeautifulSoup 的
.select()方法支持大部分 CSS 选择器。不会手写选择器可在开发者工具中右键元素 → 复制 → Copy Selector,直接粘贴。 - 粗略解读选择器(约 43:48):
#content选中 id 为 content 的元素;空格表示逐级向下;.xxx表示 class(一个元素可有多个 class)。 - 调试过程(约 45:47):复制来的选择器选中的是第一个元素且结果为空,逐步删改选择器调试。
- 发现大问题(约 46:49):能找到外层容器,但容器内部是空的,找不到真实电影数据——回忆三种类型,判断这是**动态渲染页面**:列表数据是浏览器通过 JS 向后端请求后再写入页面的。不急着上 JS 渲染工具,先排查它有没有数据接口。
实战第三步:找到数据接口(约 48:49–55:00)¶
- 排查方法:开发者工具"网络"标签页的**筛选器**,选中 Fetch/XHR——前端用 JS 向后端请求数据发起的请求类型(Fetch 是新版 JS 的函数,XHR 是老版 XMLHttpRequest 的缩写)。
- 观察各请求的响应大小:7.5KB 的请求很可疑 → 点开看"响应",再用"预览"标签页看格式化后的内容——是一个列表,列表里每部电影是一个字典(含 title、url、封面等)。JSON 与 Python 字典、JS 数组与 Python 列表结构高度相似,可直接转换处理。
- 结论:不用请求页面解析 HTML 了,直接请求数据接口。
- 接口 URL 形如
movie.douban.com/j/chart/top_list?type=10&interval_id=...&action=: - GET 请求的 URL 参数规则(约 53:01):问号
?之后是参数,参数间用&分隔,前面是参数名后面是值(type=10 可能是电影类型),有点像 Python 调用函数。 - 代码改造:删掉 BeautifulSoup 部分;requests 的 response 如果含 JSON,直接用
r.json()方法转成 Python 字典/列表(自己爬时应去查 requests 文档确认)。思路变为:冒充浏览器向数据接口发请求 → 拿到的 JSON 转成字典 → 打印。成功拿到前 20 部电影。
实战第四步:瀑布流分页与循环爬取(约 55:32–1:06:40)¶
- 页面显示共 248 部电影,但接口一次只返回 20 部。往下滚动页面时网络面板会多出新请求——**瀑布流**机制:JS 检测到滚动条到底部就自动发起新请求加载更多数据。
- 对比 4 个连续请求的 URL:只有
start参数变化(0、20、40、60),limit=20不变——start 即偏移量。 - 循环爬取(约 58:07):设
offset = 0,用字符串的format把 URL 中 start 值替换为 offset,每次爬完offset += 20,外层套while True:循环。 - 数据拆解:每个电影字典取
title和url存入自建字典movie_info,再result.append(movie_info)汇总。url 正好对应该电影的详情页(为下节课抓剧情简介埋下接口)。 - 防封礼仪(约 1:02:11):
import time,每轮time.sleep(3)停顿 3 秒,避免高频请求被 ban IP;同时在循环里 print 日志("正在请求第 offset 条"),否则停顿期间像卡死。 - 现场 bug(约 1:03:37):循环不结束——网页拖到 247 部后再拖就没有了,接口返回空列表,但代码没判断这种情况。修复:
if len(movie_list) == 0: break,列表为空就跳出循环。 - 最终预期结果:result 列表含 248 个字典,每个含电影名和豆瓣链接。剩余时间让学生照着逻辑自己爬。
示例与演示¶
- 完整爬取脚本(Jupyter Notebook 中逐步演进):
requests.get(url)直接请求榜单页 → 空响应;- 加
headers字典伪装 UA → 拿到 HTML; - BeautifulSoup + Copy Selector 尝试解析 → 发现动态渲染、列表为空;
- Fetch/XHR 筛选器定位数据接口 → 复制接口 URL 直接请求;
r.json()转字典 → 发现只有 20 条;- 分析瀑布流 URL 规律,
offset+format+while循环 +time.sleep(3)+ 日志 print; - 空列表 break 修复死循环 → 爬全 248 部电影的 title 和 url。
重点与难点¶
- 爬虫抓到的 HTML ≠ 浏览器看到的页面:动态渲染页面必须先判断数据来自哪里——优先找数据接口(Fetch/XHR),最后才考虑 Selenium 等渲染工具。
- **User-Agent 伪装**是最基本也是最常用的反反爬手段:从开发者工具复制真实浏览器的 UA,放进 headers 字典传给 requests。
- 接口类爬虫的两大技能:用开发者工具"网络 + Fetch/XHR + 预览"找接口;肉眼对比连续请求的 URL 找出**分页参数规律**(如 start/offset)。
- 循环爬取必须处理**终止条件**(空列表 break)和**请求频率**(time.sleep),否则死循环或被封 IP。
- 三个进阶问题(Cookie 登录、账号池、IP 池)本课只建立概念,不展开。
关联内容¶
- 本课是爬虫+可视化大作业的第一部分;续接《Python-2023-13 关键词提取与可视化》(继续访问每部电影的 url 抓剧情简介,再做 TF-IDF 关键词提取与词云)。
- 前置知识:requests/HTTP 基础、字典与列表操作、字符串 format、循环与 break、CSS 选择器(Web 章节)、JSON(FastAPI 实战课中前后端 JSON 通信)。
- "数据接口"的理解直接得益于前两课 FastAPI 实战:自己写过前后端 JSON 通信,就能认出别人网站的数据接口。
- 课程章节归属:"Web 分析与爬虫"章节;动态渲染工具 Selenium/PhantomJS 留作自学方向。