{T}

在前面几讲中,已学习了爬虫技术的三个基础环节:下载数据、提取数据以及保存数据。

本文通过一个综合的实战案例将之前的内容串联起来,巩固 Python 爬虫技术。

任务描述

业务背景:为策划国产口碑电视剧的周边特卖活动,需要对已有电视剧的名称、演员和评分进行分析,以预判一部电视剧的评分走向。在预测与分析之前,首先需要收集目前国产电视剧的相关数据,构建一个国产电视剧和评分的数据集。

任务说明

收集国产电视剧的数据,越全越好,至少收集评分、电视剧名称、主演信息三个信息。之后将数据存储在一个 csv 表中,表头如下:

  • title,代表电视剧的名称;
  • rating,代表电视剧的评分;
  • stars,代表电视剧的主演。

csv 文件名为 tv_rating.csv。

初步分析 — 选择抓取的网站

在基于 Python 技术构建数据集的方式中,首先要做的事情是选择要抓取的网站。选择标准主要看网站是否具备所需信息,以及是否方便抓取。

本次抓取的电视剧信息中,还有一个重要的考量因素是是否方便抓取多个页面,毕竟一个网页一般无法包含全部电视剧信息。下载 HTML 页面的环节往往需要下载多个页面才能获取完整的数据。

通过在搜索引擎搜索有电视剧列表的网站,筛选出两个候选:

上述两个网站都有国产电视剧专区,下面逐一分析哪个更适合此次抓取任务。

豆瓣电视剧主页分析

打开豆瓣电视剧主页,看到如下的列表。

通过观察上述页面,可以发现一个比较明确的问题:虽然有电视剧名和评分,但缺乏主演信息

再看它的翻页方式。拖到底部,可以看到一个加载更多的按钮,点击之后可以在底部加载更多内容,没有传统的第一页、第二页这样的跳转链接。

总结下来,豆瓣的电视剧主页存在两个明显问题:

  • 在电视剧列表中缺乏主演信息;
  • 无法通过 URL 来下载第二页和之后的内容,因为加载更多内容是在第一页中动态生成的,而动态生成的内容很难通过 Python 获取。

虽然这两个问题通过一些技巧也能解决,但会增加很多工作量,因此暂不考虑抓取豆瓣。

电视剧网主页分析

打开电视剧网国产电视剧的主页,可以看到页面如下所示。

通过观察以上页面,可以发现该网页虽然弹窗广告比较多,但所需字段:标题、评分和主演信息在页面上都有显示,只要显示就说明可以通过爬虫获取。

再来考察它的加载方式。拉到底部,可以看到该网页提供的是传统的翻页操作。如下所示:

点击第二页,发现跳转到了一个新的页面,该页面的 URL 与一开始访问的差不多,只是其中有一个参数 page 的值变为了 2。

如下所示,把有区分的地方加粗并加了下划线。

这说明可以通过不断改变 URL 中的 page 参数的值来访问第二页之后的内容。这样在后续写代码时,只需写针对一个页面的抓取代码,然后用一个循环不断执行该方法,并每次叠加 page 的值,就能将全部电视剧的内容抓取下来。

电视剧网的页面更符合本次抓取任务的需求,后续将该网页作为抓取目标。

数据获取 — 下载所需要的网页

下面开始抓取电视剧网的网页。出于数据集总量的考虑,通过与负责分析的同事沟通,本次抓取只需抓前 100 页的内容即可。

准备工作目录

首先,在课程目录中(也可以在桌面上)新建文件夹 chapter10 作为本次实战的主目录。之后打开 VS Code,选择文件 → 打开目录,选择 chapter10 这个目录并打开。

打开后,新建 Notebook 并保存为 chapter10.ipynb,本文的代码将在这个 notebook 文件中书写。

因为本次要下载的文件比较多(100 个 html),全部文件放在同一个文件夹显然不是一个好的选择,所以 chapter10 中再建一个文件夹,用来保存下载的 HTML 文件。

由于已经打开了 VS Code,直接在 VS Code 左边的文件夹视图区右键 → 新建文件夹,输入文件夹名为 htmls,如下图所示。

建立完后,VS Code 文件区是这个样子:有一个 notebook 文件,名为 chapter10,还有一个 htmls 的文件夹。

单个网页下载

在 07 讲中讲过,下载网页有两种方式:一种是使用 urllib3 直接下载,另一种是动态网页,需要使用 selenium 这样的模拟浏览器的技术下载。所以网页下载的第一步,首先判断下载的网页需要使用哪种方式。

首先测试是否可以直接使用 urllib3 直接下载电视剧网的网页。回顾 07 讲中把 html 下载和保存到文件写成了两个函数:download_content 和 save_to_file,这里直接拿来使用。

打开 chapter10.ipynb,新建 Cell,将 07 讲的两个函数先搬过来。

code
import urllib3
# 第一个函数,用来下载网页,返回网页内容
# 参数 url 代表所要下载的网页网址。
# 整体代码和之前类似
def download_content(url):
    http = urllib3.PoolManager()
    response = http.request("GET", url)
    response_data = response.data
    html_content = response_data.decode()
    return html_content
# 第二个函数,将字符串内容保存到文件中
# 第一个参数为所要保存的文件名,第二个参数为要保存的字符串内容的变量
def save_to_file(filename, content):
    fo = open(filename,"w", encoding="utf-8")
    fo.write(content)
    fo.close()

按 shift + enter 运行,之后就可以使用这两个函数来下载网页了。

现在下载第一个网页,来测试 urllib3 的方案是否可行。

新建 Cell,输入以下代码:

code
# 将找到的电视剧网的网址存储在变量 url 中
url = "http://dianshiju.tv/search.php?page=1&searchtype=5&order=commend&tid=1&area=&year=&letter=&state=&money=&ver=&jq=&yuyan=%E5%9B%BD%E8%AF%AD"
# 将 url 对应的网页下载下来,并把内容存储在 html_content 变量中
html_content = download_content(url)
# 将 html_content 变量中的内容存储在 htmls 文件夹中,文件名为 tv1.html 代表第一页
save_to_file("htmls/tv1.html", html_content)

下载网页的步骤前文已介绍,这里不再赘述。

执行代码,之后可以看到 htmls 文件夹下多了 tv1.html,说明已经下载成功。

接下来点击 tv1.html 打开,来查看是否包含电视剧信息。回顾上文中发的截图,第一个电视剧的名称是《决胜法庭》。

在 VS Code 中搜索“决胜”,发现确实存在《决胜法庭》这个电视剧的信息。在下方还可以看到其评分:3.7。如下图所示。

说明电视剧网的内容不是动态生成的,可以用 urllib3 进行下载。

批量下载网页

现在第一个网页已经下载成功。目标是下载 100 页的内容,所以剩余 99 页可以通过一个循环来下载。在前面的分析中,下载第二页和之后的内容只需修改 URL 中的 page 值即可。

另外,在通过循环批量下载内容时,还有一个重要的注意事项:一般会在每次下载之后等待几百毫秒的时间,再进行下一次下载,这样可以避免短时间内对网站发起大量下载请求,浪费网站的带宽资源。

本次实战中,每次下载之后等待一秒再进行下一次下载。在 Python 中,通过 time 模块的 sleep 方法来使程序暂停固定的时间。

新建 Cell,输入如下的代码:

code
import time
# 第一页已经下载完毕,循环下载第2页到第100页的内容
for i in range(2, 101):
    # 每次循环,使用 replace 函数将 url 字符串中 page=1 的部分替换为 page + i,i为循环变量。
    # 比如 i=3 时,将 page=1 替换为 page=3,这样来下载第三页的内容
    # 将替换后的 url 存储在 a_url 变量中
    a_url = url.replace("page=1", "page=" + str(i))
    # 打印出即将下载的 URL
    print("begin download:", a_url)
    # 下载当前页面的 url 的内容
    html_content = download_content(a_url)
    # 因为通过循环来下载,保存文件名也需要使用 i 来代表现在保存的是第几个网页。
    # 比如 i = 3 时,下载的是 page=3 的网页,最后则存储在 htmls/tv3.html
    # 将本次循环要保存的文件名存储在 file_name 变量中
    file_name = "htmls/tv" + str(i) + ".html"
    # 将 html 文件的内容存储在 file_name 对应的文件夹里
    save_to_file(file_name,html_content)
    # 暂停一秒钟再继续下一次循环
    time.sleep(1)

执行上述程序,可以看到程序每隔一秒钟输出一行信息,如下所示。大概执行 100 秒后,程序执行结束,部分日志如下所示。

code
begin download: http://dianshiju.tv/search.php?page=2&searchtype=5&order=commend&tid=1&area=&year=&letter=&state=&money=&ver=&jq=&yuyan=%E5%9B%BD%E8%AF%AD
begin download: http://dianshiju.tv/search.php?page=3&searchtype=5&order=commend&tid=1&area=&year=&letter=&state=&money=&ver=&jq=&yuyan=%E5%9B%BD%E8%AF%AD
begin download: http://dianshiju.tv/search.php?page=4&searchtype=5&order=commend&tid=1&area=&year=&letter=&state=&money=&ver=&jq=&yuyan=%E5%9B%BD%E8%AF%AD
begin download: http://dianshiju.tv/search.php?page=5&searchtype=5&order=commend&tid=1&area=&year=&letter=&state=&money=&ver=&jq=&yuyan=%E5%9B%BD%E8%AF%AD
begin download: http://dianshiju.tv/search.php?page=6&searchtype=5&order=commend&tid=1&area=&year=&letter=&state=&money=&ver=&jq=&yuyan=%E5%9B%BD%E8%AF%AD
begin download: http://dianshiju.tv/search.php?page=7&searchtype=5&order=commend&tid=1&area=&year=&letter=&state=&money=&ver=&jq=&yuyan=%E5%9B%BD%E8%AF%AD
begin download: http://dianshiju.tv/search.php?page=8&searchtype=5&order=commend&tid=1&area=&year=&letter=&state=&money=&ver=&jq=&yuyan=%E5%9B%BD%E8%AF%AD

执行完毕后,侧边栏打开 htmls 文件夹,可以看到 100 个 html 文件已经保存成功。

下载出文件之后,还需要查看这些 HTML 文件,确认是否包含预期内容。以第 100 页为例。

将上文提到的 URL 中的 page 改为 100,在浏览器中访问,第 100 页的电视剧如下所示。

第一个电视剧名为《疯人院也疯狂》,之后打开下载到对应的 html 文件:tv100.html,搜索“疯人院”,发现可以搜索到内容(如下图所示),所以大概率下载的 html 都对应了电视剧网站中的每一页的内容。

至此,所需的数据都已下载到本地,接下来编写代码去提取所需的信息。

数据提取 — 过滤出需要的信息

初步分析

从之前学习的数据抓取一讲可知,要使用 BeautifulSoup 提取数据,第一步首先应该分析所需内容周围的标签结构,然后根据标签的层级关系设计如何提取信息。

从第一页的 html 开始,打开 htmls/tv1.html,搜索“决胜法庭”,定位到第一个电视剧的标签部分。可以看到在一个 class 为 myui-vodlist__box 的 div 标签内部,基本包含了要拿的所有信息,如下图所示。

结合上图的分析,数据提取的思路大致如下:

  • 获取所有 class=myui-vodlist__box 的 div 标签对象。
  • 针对每一个标签对象,都尝试:
    • 查找 h4 标签,并获得标签内的文本 → 电视剧名称;
    • 查找 class = "pic-tag pic-tag-top" 的 span 标签,获得标签内文本 → 评分;
    • 查找 p 标签,并获得标签内文本 → 演员信息。

因为在外层标签内部,h4 标签和 p 标签都只有一个,所以不需要更多的过滤条件。

提取单个 HTML 的所有电视剧信息

下面开始编写从单个 html 文件抽取电视剧信息的代码,单个文件处理写完后扩展到多个就比较容易。

此处用到在第 9 讲整理的、直接从文件名创建 BeautifulSoup 对象的函数:create_doc_from_filename。

新建 Cell,先将这个函数搬过来。

code
from bs4 import BeautifulSoup
# 输入参数为要分析的 html 文件名,返回值为对应的 BeautifulSoup 对象
def create_doc_from_filename(filename):
    fo = open(filename, "r", encoding='utf-8')
    html_content = fo.read()
    fo.close()
    doc = BeautifulSoup(html_content)
    return doc

之后根据初步分析中分析的步骤,实现内容的抓取。

新建 Cell,输入如下代码:

code
# 用 tv1.html 的内容创建 BeautifulSoup 对象
doc = create_doc_from_filename("htmls/tv1.html")
# 查找 class="myui-vodlist__box" 的所有 div 标签
# 并以列表形式存储在 box_list 中
box_list = doc.find_all("div", class_="myui-vodlist__box")
# 使用遍历循环遍历 box_list 中的所有标签对象
for box in box_list:
    # 根据上述分析的思路,分别获取包含标题、评分、和演员信息的标签
    # 因为 find_all 总是返回列表,所以需要通过下标 0 来取第一个
    title_label = box.find_all("h4")[0]
    rating_label = box.find_all("span", class_="pic-tag pic-tag-top")[0]
    stars_label = box.find_all("p")[0]
    # 分别获取标签内部包含的文本,存储在 title, rating, stars 变量中
    title = title_label.text
    rating = rating_label.text
    stars = stars_label.text
    # 将获取的三个变量打印出来,看看是否正确。
    print(title, rating, stars)

执行之后,输出如下(截取了部分日志)。可以看到所需信息有了,但好像带了很多不必要的空格。

code
决胜法庭
												3.7分

主演于和伟,张佳宁,韩栋,王耀庆,连奕名,胡静,杜源,王艺禅,王全有

刘老根第三部
												4.0分

主演赵本山,范伟,李静,闫学晶,王小宝

我的僵尸王爷
												4.6分

主演内详

新世界2020
												3.4分

主演孙红雷,张鲁一,尹昉,万茜,李纯,胡静,秦汉,赵峥,张瑶,张晔子

有空格的原因是 html 页面中标签内部可能本身就有空格,但 Python 的字符串有一个 strip 方法,可以去掉字符串前后的所有空格。

修改一下代码,如下所示。

code
# 用 tv1.html 的内容创建 BeautifulSoup 对象
doc = create_doc_from_filename("htmls/tv1.html")
# 查找 class="myui-vodlist__box" 的所有 div 标签
# 并以列表形式存储在 box_list 中
box_list = doc.find_all("div", class_="myui-vodlist__box")
# 使用遍历循环遍历 box_list 中的所有标签对象
for box in box_list:
    # 根据上述分析的思路,分别获取包含标题、评分、和演员信息的标签
    # 因为 find_all 总是返回列表,所以需要通过下标 0 来取第一个
    title_label = box.find_all("h4")[0]
    rating_label = box.find_all("span", class_="pic-tag pic-tag-top")[0]
    stars_label = box.find_all("p")[0]
    # 分别获取标签内部包含的文本,存储在 title, rating, stars 变量中
    # 【修改部分】并使用 strip 去除前后空格
    title = title_label.text.strip()
    rating = rating_label.text.strip()
    stars = stars_label.text.strip()
    # 将获取的三个变量打印出来,看看是否正确。
    print(title, rating, stars)

再次执行代码,部分输出如下,可以看到这一次的数据已经很接近需要的样式了。

code
决胜法庭 3.7分 主演于和伟,张佳宁,韩栋,王耀庆,连奕名,胡静,杜源,王艺禅,王全有
刘老根第三部 4.0分 主演赵本山,范伟,李静,闫学晶,王小宝
我的僵尸王爷 4.6分 主演内详
新世界2020 3.4分 主演孙红雷,张鲁一,尹昉,万茜,李纯,胡静,秦汉,赵峥,张瑶,张晔子
三生三世枕上书 4.4分 主演迪丽热巴,高伟光,陈楚河,郭品超,刘雨欣,刘芮麟,王骁,李东恒,袁雨萱,黄俊捷
北灵少年志之大主宰 4.0分 主演王源,欧阳娜娜,骆明劼,马月,徐浩,王奕婷
尖锋之烈焰青春 1.0分 主演宋轶,李佳航,牛骏峰,孙洪涛,万沛鑫,侍宣如,赵圆瑗,吴谨言,钱志,一真
幸福院之老有所安 3.0分 主演刘佩琦
鬼吹灯之牧野诡事 1.0分 主演王大陆  金晨  王栎鑫
秘果 3.6分 主演陈飞宇/欧阳娜娜
择天记 5.0分 主演鹿晗/娜扎/吴倩/曾舜晞
欢乐颂2 1.0分 主演刘涛/蒋欣/王子文/杨紫
关东大先生 4.0分 主演赵本山,范伟,小沈阳,刘流

提取多个 HTML 的内容

通过上述代码,已经可以将 tv1.html 文件中的所有电视剧信息打印出来。但这次一共有一百个 html 文件,如何实现处理多个 html 文件呢?

因为这一百个 HTML 文件虽然电视剧内容不一样,但标签结构基本一样(在电视剧网翻页时,可以看到每一页的样子都一样)。所以只需将上面的代码放在循环中循环运行,每次循环处理不同的 html 文件即可。

为了让代码更加清晰,先将上面的处理单个文件的代码改写为函数,参数就是要处理的 html 文件名,函数命名为 get_tv_from_html。

新建 Cell,输入如下代码。

code
# 从参数指定的 html 文件中获取电视剧的相关信息
def get_tv_from_html(html_file_name):
    doc = create_doc_from_filename(html_file_name)
    box_list = doc.find_all("div", class_="myui-vodlist__box")
    for box in box_list:
        title_label = box.find_all("h4")[0]
        rating_label = box.find_all("span", class_="pic-tag pic-tag-top")[0]
        stars_label = box.find_all("p")[0]
        title = title_label.text.strip()
        rating = rating_label.text.strip()
        stars = stars_label.text.strip()
        # 将获取的三个变量打印出来,看看是否正确。
        print(title, rating, stars)
# 试试用新写的函数处理一下 tv2.html
get_tv_from_html("htmls/tv2.html")

输出节选如下。

code
醉玲珑 5.0分 主演 刘诗诗  陈伟霆  徐海乔  韩雪
河神 1.8分 主演李现  张铭恩  王紫璇CiCi  陈芋米
少林问道 4.0分 主演 周一围  郭京飞  郭晓婷  是安
鬼吹灯之黄皮子坟 3.0分 主演阮经天  徐璐  郝好  刘潮  尚铁龙
槑头槑脑第二季 3.0分 主演宋晓峰  霍云龙  程野
槑头槑脑 2.0分 主演宋晓峰  霍云龙 唐艺兮 程野 唐娜
学生兵 2.0分 主演张铎  孙艺宁  张光北  孙绍龙
绝密543 3.6分 主演王聪  陈维涵  顾宇峰  王超
深海利剑 3.0分 主演王强  高旻睿  刘璐  王阳
浪花一朵朵 5.0分 主演谭松韵  熊梓淇  黄圣池  张峻宁
镇魂街[真人版] 2.0分 主演 汪东城 安悦溪 侯明昊
黑土热血 1.0分 主演于毅,刘威葳,张芷溪,王劲松,曹克难,谭皓,侯煜,袁苑,齐千郡,山鹰,王今心
建军大业电视剧 4.0分 主演黄海冰,郭广平,周惠林,李飞
颤抖吧,阿部 5.0分 主演郑业成 安悦溪 王燕阳

可以看到,成功用编写的函数从 tv2.html 中提取了电视剧的信息。这样要实现从一百个文件中抽取信息,只需写一个循环,每次传给 get_tv_from_html 函数不同的文件名即可。

但现在还有一个问题:处理 html 之后,只是把信息打印了出来,打印在屏幕上显然不满足构建数据集的要求。

接下来,将所有信息保存为 csv 文件。

数据保存 — 将数据保存为 csv

回顾第 09 讲学习的内容,要将数据保存为 csv 的记录,首先需要将每一行数据保存为字典,然后以一个字典列表的形式传递给 csv 模块的 DictWriter。

(1)准备保存到 csv 的函数

为了让后续代码更简洁,先将把字典列表保存到 csv 文件的操作写成一个函数。

新建 Cell,输入如下代码:

code
# 导入 csv 模块
import csv
# 输入有三个参数:要保存的字典列表,csv 文件名,和表头
def write_dict_list_to_csv(dict_list, filename, headers):
    # 当要处理的网页比较复杂时,增加 encoding 参数可以兼容部分特殊符号
    fo = open(filename, "w", newline='', encoding='utf_8_sig')
    writer = csv.DictWriter(fo, headers)
    writer.writeheader()
    writer.writerows(dict_list)
    fo.close()

实现的代码在第 09 讲也讲过,这里不再赘述。

(2)创建保存所有电视剧字典的列表

用一个列表来保存所有 html 文件中获取的电视剧信息。

新建 Cell,输入如下代码并运行。

code
all_tv_dict = []

(3)改造 get_tv_from_html 函数

数据提取章节的末尾,实现了 get_tv_from_html 函数,可以方便地处理多个文件,但当时只是把信息打印了出来。现在进行改造,不打印电视剧名、评分和演员信息,而是存储为字典。一个电视剧一个字典,而一个 html 文件中包含多个电视剧,所以 get_tv_from_html 最后会返回一个字典列表。

回到 get_tv_from_html 的 cell,修改代码为如下所示,其中有修改的部分都标注了【新增】。

code
# 从参数指定的 html 文件中获取电视剧的相关信息
def get_tv_from_html(html_file_name):
    doc = create_doc_from_filename(html_file_name)
    box_list = doc.find_all("div", class_="myui-vodlist__box")
    # 【新增】当前处理的文件的字典列表
    tv_list = []
    for box in box_list:
        title_label = box.find_all("h4")[0]
        rating_label = box.find_all("span", class_="pic-tag pic-tag-top")[0]
        stars_label = box.find_all("p")[0]
        title = title_label.text.strip()
        rating = rating_label.text.strip()
        stars = stars_label.text.strip()
        # 【新增】使用字典来保存,字典的 key 和 csv 的表头保持一致
        # 【新增】在任务说明环节,表头为: title, rating, stars
        tv_dict = {}
        tv_dict["title"] = title
        tv_dict["rating"] = rating
        tv_dict["stars"] = stars
        # 【新增】将电视剧的字典添加到当前文件的字典列表中
        tv_list.append(tv_dict)
    # 【新增】返回字典列表
    return tv_list
# 【新增】用最新修改的 get_tv_from_html 处理 tv2.html,并将返回的结果存储在 tv_list 变量中
tv_list = get_tv_from_html("htmls/tv2.html")
# 【新增】打印获取到的列表
print(tv_list)

运行代码,输出节选如下所示。

可以看到,这一次在 get_tv_from_html 中没有再进行输出,而是返回了字典列表。把字典列表打印出来,其中的内容就是所需的电视剧信息。

code
[{'title': '醉玲珑', 'rating': '5.0分', 'stars': '主演 刘诗诗  陈伟霆  徐海乔  韩雪'}, {'title': '河神', 'rating': '1.8分', 'stars': '主演李现  张铭恩  王紫璇CiCi  陈芋米'}, {'title': '少林问道', 'rating': '4.0分', 'stars': '主演 周一围  郭京飞  郭晓婷  是安'}, {'title': '鬼吹灯之黄皮子坟', 'rating': '3.0分', 'stars': '主演阮经天  徐璐  郝好  刘潮  尚铁龙'}, {'title': '槑头槑脑第二季', 'rating': '3.0分', 'stars': '主演宋晓峰  霍云龙  程野'}, {'title': '槑头槑脑', 'rating': '2.0分', 'stars': '主演宋晓峰  霍云龙 唐艺兮 程野 唐娜'}, {'title': '学生兵', 'rating': '2.0分', 'stars': '主演张铎  孙艺宁  张光北  孙绍龙'}, {'title': '绝密543', 'rating': '3.6分', 'stars': '主演王聪  陈维涵  顾宇峰  王超'}, {'title': '深海利剑', 'rating': '3.0分', 'stars': '主演王强  高旻睿  刘璐  王阳'}, {'title': '浪花一朵朵',

(4)获取所有文件的电视剧信息

目前,通过 get_tv_from_html 函数,已经可以获取单个 html 的电视剧列表。现在通过一个循环去处理所有的 html。对于每一个 html 文件,获取字典列表之后,都把列表添加到总列表 all_tv_dict 中。这样,在循环执行结束后,all_tv_dict 变量中就包含了所有电视剧的信息。

下面实现上述逻辑,新建 Cell,输入以下代码。

code
# 因为是处理 tv1 - tv100 的文件,所以 i 循环从1到101
for i in range(1, 101):
    # 拼出每一次要处理的文件名
    file_name = "htmls/tv" + str(i) + ".html"
    # 调用 get_tv_from_html 处理当次循环的文件
    # 将这个文件中的电视剧列表存储在 dict_list 变量
    dict_list = get_tv_from_html(file_name)
    # 将 dict_list 的内容添加到总列表 all_tv_dict 中
    # 列表的拼接可以直接使用 + 号
    all_tv_dict = all_tv_dict + dict_list
# 打印出总列表的长度,看看一共抓取到了几部电视剧
print(len(all_tv_dict))

按 shift+enter 执行,因为要用 BeautifulSoup 处理一百个文件,这里执行会有点慢,需要耐心等待。执行完毕后输出结果为 3600。

code
3600

这说明一共抓取到了 3600 部电视剧的信息。

(5)保存结果到 csv 文件中

在任务说明中,已经明确了要保存的 csv 文件名为:tv_rating.csv,表头为:title, rating, stars。

此时,所有电视剧的信息都已经存储在 all_tv_dict 总列表中,只需调用存储到 csv 的函数将其保存到 csv 文件即可。

code
# 调用之前准备的 write_dict_list_to_csv 函数
# 第一个参数为要保存的列表,这里就是存储了所有电视剧的总列表 all_tv_dict
# 第二个参数为要保存的文件名
# 第三个参数为要保存的 csv 文件的表头
write_dict_list_to_csv(all_tv_dict, "tv_rating.csv", ["title", "rating", "stars"])

执行之后,没有内容输出,但可以看到在 chapter10 文件夹下已经生成了 tv_rating.csv 文件。

使用 Excel 打开该 csv 文件,可以看到表头已经正确写入,表头对应的内容也已经正确写入。

拉到底部,可以看到最后一行记录是 3061,因为第一行是表头,所以最终保存了 3600 条电视剧的信息,这与之前列表的长度对得上。

至此,一份国产电视剧评分的数据集制作完毕。

小结

本文的实战用到了本模块学习的相关知识,主要包括:

  • 根据数据集的要求选择所要抓取的网页;
  • 实现单个页面抓取;
  • 实现多个页面的抓取;
  • 实现页面内容的提取;
  • 将提取到的内容转化成字典列表,并保存到 csv 文件。

至此,数据获取部分的内容学习完毕。下一模块将学习 Python 数据分析最广泛使用的框架:pandas。

课后习题

在刚才的 csv 中,演员一栏的内容都包含了“主演”两个字,是比较冗余的。编写代码,读取 tv_rating.csv 的内容,然后把每行记录的演员信息中的“主演”两个字删除,删完后新的内容保存至 tv_rating2.csv。


答案

解题思路类似前文,先读取所有字典列表,通过一个循环遍历列表,对于每一个字典,通过 stars 这个 key 拿到演员信息,删除“主演”两个字后再存储回 stars 这个 key 对应的值中。

code
# 整理读取 CSV 的代码为函数
# 输入参数为 CSV 文件名,返回值为读取到的字典列表
def get_dict_list_from_csv(filename):
    fo = open(filename, "r", encoding="utf_8_sig")
    reader = csv.DictReader(fo)
    dict_list = []
    for item in reader:
        dict_list.append(item)
    return dict_list
# 读取 tv_rating.csv 文件,并将返回的字典列表赋值给 tv_list1 变量
tv_list1 = get_dict_list_from_csv("tv_rating.csv")
# 遍历循环 tv_list1
for item in tv_list1:
    # 针对每个字典,将 stars 对应的 value 的“主演”替换为空,也就是删除主演两个字
    # 并将结果存回 item 字典
    item["stars"] = item["stars"].replace("主演","")
# 将修改之后的列表重新写到 tv_rating2.csv 中
write_dict_list_to_csv(tv_list1, "tv_rating2.csv", ["title", "rating", "stars"])

运行之后,可以看到生成了 tv_rating2.csv,打开后如下所示,可以看到“主演”二字都已经删除了。

版本差异(爬虫技术栈 → 当前版本)

本文编写时当前稳定版
requests2.28/2.312.32.x
Scrapy1.x/2.02.11.x(API 稳定)
httpx0.240.28.x
Playwright1.3x1.6x(Python 版)
lxml/BeautifulSoup旧版保持稳定
Python3.8-3.123.14(推荐)

本文讲解的爬虫原理(HTTP、解析、反爬、存储)与核心 API 在最新版本中成立;注意 Python 3.9 及以下已 EOL,新项目使用 3.13/3.14。