Python-2023-13 关键词提取与可视化¶
内容梗概¶
这节课完成爬虫大作业的剩余两部分:在上节课爬到的豆瓣电影 title 和 url 基础上,逐个访问电影详情页、用 BeautifulSoup 提取剧情简介并存为 txt 文件;然后用 jieba 的 TF-IDF 算法提取所有简介的关键词并汇总排序;最后用 wordcloud 画词云做可视化。课上还穿插了两个重要思想:一是这个"抓取—分析—可视化"三段式框架可以任意替换模块,扩展后就是舆情系统的基本逻辑;二是模块化设计意识——每个模块的输出应该持久化存储到本地。结尾明确了作业的基本要求和进阶要求。
知识点详解¶
作业框架回顾与扩展思路(约 00:00–02:40)¶
- 作业三部分:① 抓豆瓣电影的剧情介绍;② 用 TF-IDF(最基本的算法)对简介做关键词提取;③ 把关键词画成**词云**可视化。老师会提供实验报告供参考。
- 三个模块可任意替换:抓豆瓣可以换成抓微博(GitHub 上有现成代码);关键词提取可以换成**情感计算**(用机器学习/深度学习模型判断内容的情感倾向,基础的三维度、好一点的五维度,都有现成算法);词云可以换成其他图表。
- 把框架复杂化就是一个小型系统:分布式爬虫、更复杂的情感计算、分主题的主题提取、随时间变化的倾向分析,配上河流图、散点图等更丰富的可视化——市面上大多数舆情系统的基本逻辑就是这样。
- 进阶例子:分析微博正文与评论的情感倾向是否冲突——统计显示假新闻的正文情感倾向往往与评论对立,可作为虚假新闻检测的一个维度,这已经是研究生科研课题的方向。本课只讲最基础的框架。
Jupyter Notebook 启动与环境变量问题(约 02:40–04:20)¶
- 启动方式:建好分支文件夹(如 0x03),在终端里进入该目录运行
jupyter notebook。 - 常见报错:运行后一堆红字提示找不到 jupyter——没设置环境变量,读不到 Anaconda 的二进制文件。两种解决方式:① 手动添加环境变量(回顾前面章节);② 直接用 Anaconda 自带的命令行(环境变量已配好),
cd到目标目录再运行。 - 老师强调学习态度:视频只是参考,遇到和视频对不上的地方不要停止思考,要自己想办法解决。
抓取剧情简介(约 04:20–33:00)¶
- 上节课回顾:通过豆瓣数据接口拿到 JSON(注意把接口里的 offset 改成 0 才能看到第一条),遍历后把每部电影的
title和url存入movie_info,汇总到 result 列表;但还没有简介信息。 - 思路:对每部电影再用
requests.get访问它的详情页 url,拿到 response(变量名用 r)后用 r.text 取页面文本(即 HTML 源码),交给 BeautifulSoup 解析: soup = BeautifulSoup(r.text, "html.parser")——把 HTML 传给 BeautifulSoup,用自带的 html.parser 解析引擎;soup.select("CSS选择器")定位元素——右键"检查"找到剧情简介所在元素 id 为link-report-intra。- select 的返回值是列表(约 15:38):列表包含 CSS 选择器选中的所有元素。因为 HTML 中 id 必须唯一,所以按 id 选择只会返回一个元素,取列表的 0 号元素即可(
div = soup.select(...)[0])。 - get_text()(约 17:21):提取元素内的纯文本、扔掉所有 HTML 标签,得到干净的简介文字。查文档是必备技能,老师鼓励大家自己看 BeautifulSoup 文档。
- 更精确的定位(约 19:49):简介区有两个 span——
short(默认显示的短简介)和all hidden(点"展开全部"后的完整简介),直接 get_text 会把两份都拿到。用开发者工具的**复制 Selector** 功能拿到all hidden的选择器,只取完整版。 - 报错与异常处理(约 21:51–27:30):正式爬取时报
list index out of range——select 没找到span.all hidden元素,列表为空却取 0 号元素。原因:有些电影简介本身很短,没有"展开全部",页面上只有 short 甚至只有一个普通 span。 - 排错方法:先加日志输出("正在处理电影 {title},URL: {url}"),找到报错的那部电影,打开页面检查它的真实结构,再写处理逻辑。
- 分流逻辑:先获取
all hidden元素列表span_list,如果列表为空(长度为 0)就退而取父元素,统一存入同一个变量;父元素每个页面都有,保证列表一定非空,再取 0 号元素e.get_text()就不会出错。 - 持久化存储(约 20:43):把简介存入以电影名命名的 txt 文件:
with open(title + ".txt", "w", encoding="utf-8") as f: f.write(intro)。 - Jupyter 操作提醒:想只看一条结果就在循环里加
break(注意两层循环要两个 break),不要点"终止内核"——那会把后台 kernel 关掉,后续操作全废;中断用 Ctrl+C 或 Kernel Interrupt,必要时 Restart Kernel。 - 整理:爬完手动把 txt 归入 movies 文件夹;更好的做法是在写文件时就
open("movies/" + title + ".txt", ...)(while 循环开始前先建好文件夹)。
模块化设计思想(约 33:43–35:00)¶
- 为什么强调把简介写进 txt 而不是直接在内存里传给下一步?每个模块的输出最好持久化存储(写进文件/数据库/JSON/XML)。
- 好处:做第二步、第三步时不用反复爬取(否则中断后重爬容易被豆瓣封);模块间用"上一个模块的输出作为下一个模块的输入"解耦。这是做作业、工作、科研合作写单独模块时都要有的意识——交付的东西最好是离线可操作的结果。
TF-IDF 关键词提取(约 34:59–48:30)¶
- 用 **jieba 分词**自带的 TF-IDF 模块(jieba 文档里有"基于 TF-IDF 算法的关键词抽取"示例,可直接参考)。Anaconda 不自带 jieba,需
pip install jieba——可以在 Anaconda 里 new 一个 Terminal 装,或在终端初始化虚拟环境后装。 - 遍历文件:用
glob匹配 movies 目录下所有.txt(呼应之前"遍历目录"的作业);读文件与写文件对应:open(path, "r", encoding="utf-8"),f.read()无参数读全部。 - 文本清洗(约 37:04):用字符串的
replace方法依次去掉"豆瓣"(版权信息残留)、英文空格、中文空格(比英文空格宽,要单独替换)、换行符(分词对标点足够,换行无影响)。 - 提取:
jieba.analyse.extract_tags(content, topK=20, withWeight=True): topK:取重要程度前多少名的词,默认 20,可不写;withWeight:默认 False 只返回关键词列表(按重要程度排序);设为 True 返回**(关键词, 权重)元组的列表**——因为画词云需要权重值。- 权重汇总(约 46:00):每篇简介单独提取,同一关键词在不同简介中权重不同,需要累加。做法:定义空字典
result,遍历所有关键词元组,if key not in result: result[key] = value否则result[key] += value。 - 排序(约 48:14):字典乱序,用内置函数
sorted: - 先用
result.items()把字典还原成 (key, value) 列表; sorted(..., key=lambda item: item[1], reverse=True)——key参数指定排序依据,用**匿名函数 lambda** 取每个元素的一号元素(权重);reverse=True倒序让权重高的在前;- 结果可再转回字典。
- 结果解读:悬疑/剧情片高频词如探员、警局、妻子、女儿、改编——主角多为家庭关系、多涉及案件、常由小说改编。
停用词与用户词典(约 54:09–1:00:30)¶
- "本片"、人名(杰克、约翰、夏洛克)等词对分析主题没有意义,是"污染",引入 jieba 的两个词典机制:
- 用户词典
jieba.load_userdict("user_words.txt"):告诉分词工具哪些词不要拆开(每行一个词)。例:外国人名"羽生结弦"容易被拆成"羽生/结弦",放进自定义词典就能保住;中文分词博大精深,组合情况复杂。 - 停用词
jieba.analyse.set_stop_words("stopwords.txt"):分析时忽略的词——"的、得、地"、他/她/它等高频指代词,以及"本片"、主角名、"电影"等不想出现在结果里的词。 - 词典文件放在当前目录(demo 文件夹),迭代修改:跑一遍 → 看结果里还有哪些污染词 → 加进 stopwords → 再跑,直到结果干净。
词云可视化(约 1:00:42–1:06:30)¶
- 用 wordcloud 库(不是 Anaconda 自带,需
pip install wordcloud)。 - 必须设置中文字体(约 1:03:46):WordCloud 默认用英文字体,中文会显示成方块(英文字体只需设计 26 个字母,中文核心汉字有 3500 个)。参数
font_path指定字体路径——Windows 字体在系统字体目录下(课上用仿宋),Mac 需自行查找系统字体路径。 - 创建词云:
WordCloud(font_path=..., width=..., height=..., max_words=80),可设置图片宽高和最多展示的词数。 - 把汇总排序后的字典扔进去生成词云(
generate_from_frequencies(result)),可存为图片文件;用 matplotlib 直接展示——Jupyter Notebook 能实时在单元格下方显示图,不必去文件管理器双击打开,这是 Notebook 很好用的特性。 - 生成的词云直观印证结论:探员、警局、家庭关系、改编等词占比大;残留的污染词(人名等)继续通过停用词词典迭代去除。
作业要求(约 1:06:33–1:10:40)¶
- 提交内容:代码仓库(git 分支)里包含词云图、Jupyter Notebook(可以传 GitHub)、爬下来的 movies 文件夹(不用爬太多,老师爬了 135 个,至少有这个过程)。
- 基本要求:照着视频把三步功能完整复现一遍即可。
- 进阶要求(替换任意模块):
- 简单:换一个豆瓣电影类型抓(动画、悬疑、凶杀等,别和老师抓的重复);
- 中等:改抓微博——GitHub 上有很多现成代码,考验是把别人的代码跑起来打通(鼓励找现成模块不算抄袭,但跑不通等于没做);
- 更进阶:爬虫用 Scrapy 框架重写;情感计算跑个模型。第二步做得足够复杂的话,可视化做几个简单图表即可。
- 老师寄语:学完这门课后看到网上任何 Python 代码都能跑起来,就算出师了。
示例与演示¶
- 抓取模块改造:上节课的接口请求代码 → 去掉 result 列表 → 对每部电影
requests.get(url)→ BeautifulSoup 解析 → select 定位link-report-intra→ 精确到span.all hidden→ get_text 取纯文本 → 空列表分流处理 →with open写 txt。 - 排错演示:
list index out of range→ 加日志定位到《窃听风暴》→ 页面检查发现没有"展开全部" → 写空列表判断。 - 关键词提取:glob 遍历 movies/*.txt → read + replace 清洗 →
extract_tags(content, topK=20, withWeight=True)→ 字典累加权重 →sorted(result.items(), key=lambda item: item[1], reverse=True)排序。 - 词典配置:新建 user_words.txt 与 stopwords.txt,load_userdict / set_stop_words 载入,迭代加词(本片、杰克、约翰、夏洛克、电影……)。
- 词云:WordCloud 设置 font_path(仿宋)、width/height、max_words=80,喂入结果字典,matplotlib 实时出图。
重点与难点¶
- **select 返回列表**及"id 唯一所以取 0 号元素"的逻辑容易绕;
list index out of range的排错套路(加日志 → 定位页面 → 分析结构 → 异常分流)是通用技能。 - 页面结构不统一(有的电影没有"展开全部")是真实爬虫的常态,必须做空值判断。
- 模块输出持久化:中间结果落盘,避免重复爬取被封,也方便分阶段开发——老师特别强调的工程意识。
- TF-IDF 提取时
withWeight=True才能拿到画词云所需的权重;多文档汇总要用字典累加。 sorted+lambda+reverse对字典按值排序的组合写法是重点语法。- 中文处理的两个坑:中文空格与英文空格不同要分别清洗;WordCloud 必须指定中文字体 font_path,否则全是方块。
- 环境变量问题(jupyter 命令找不到)再次出现,要求能独立解决。
关联内容¶
- 直接续接《Python-2023-12 爬虫之豆瓣》:用其爬到的电影 title/url 作为输入。
- 前置章节知识:BeautifulSoup 与 CSS 选择器(爬虫章节)、文件读写与 with open、glob 遍历目录(此前的作业)、字典操作与 sorted/lambda(函数进阶相关内容)、第三方库安装与虚拟环境、Jupyter Notebook 使用。
- 词云可视化呼应课程可视化部分;扩展方向提到的 Scrapy 在爬虫章节视频中讲过。
- 本课是爬虫+可视化大作业的收尾课,作业框架(抓取—分析—可视化)被点明可扩展为舆情系统/科研方向。