Python数据分析高薪实战第四天 python数据采集下载和提取保存

25 篇文章 25 订阅

06 获取数据:公开数据集与 DIY 数据集

首先恭喜你完成了第一部分的学习。现在你已经基本掌握了 Python 的基础并能够使用 Python 完成一些相对完整的功能的开发,是时候开始进入数据分析的世界了。

这一章我们将会围绕数据分析的第一步:数据获取展开。为什么说数据获取是数据分析的第一步呢,显而易见:数据分析,得先有数据,才能分析。

在大厂的数据部门,要获取数据可能会很容易,毕竟公司本身就已经沉淀了非常多的数据。而作为一个普通个体要获取能够操练分析技巧的数据并不是一件容易的事,毕竟数据首先要有一定规模,并且来源是真实场景等条件都满足的前提下才具备分析的价值。

那作为个人,要怎么样才能够获取用于分析的数据集呢?

获取现成的数据集

获取数据集的第一种方式,就是获取行业上已经有人整理好的数据集。目前大数据行业持续火爆,数据本身已经变成了一种产业,自然也包含数据集。这些有人已经整理过的数据集,我们统称为现成的数据集。

现成的数据集大概有两种:比赛数据集和行业数据集。

比赛数据集

高水平的数据分析大赛毫无疑问是大数据行业火爆的一大有力证明。现如今,数据分析比赛已经不再简单的是数据分析师、数据科学家们互相切磋的比赛,而演变成了各路公司将自己公司遇到的数据难题抛出来悬赏各路英雄来解决的平台,充满了来自现实世界的挑战。

主流数据分析大赛的比赛题目往往就是赞助商公司面临的实际问题,而数据集也往往来自赞助商公司的真实数据,经过一定脱敏之后开放给所有参赛的数据分析师。比赛中拿到最好结果的团队可以收到不菲的大赛奖金,另一方面其贡献的解决方案可以帮助公司找到后续业务发展的方向,最后,比赛过程中公司贡献的数据集又为数据分析爱好者和初学者们提供了绝佳的学习材料,可谓是一举三得。

作为数据初学者的我们,自然可以去数据分析大赛上找一些现成的数据集来练手用。目前数据分析比赛蓬勃发展,呈现越来越多的趋势。目前数据分析大赛认可度比较高的比赛一个是国际上的 kaggle,一个是国内的天池。

  • kaggle 可以说是所有数据分析大赛的鼻祖,也是目前世界范围内规模最大的数据分析比赛,但存在两个问题:一是全英文网站,二是国内访问速度较慢。整体来说对新手并不是很友好。

  • 天池是国内目前影响力最大的比赛,整体平台的配置、数据集的丰富度都有保障,并且还有一系列新手赛帮助入门。

这里以天池平台为例,示范如何获得比赛的数据集。

(1)访问天池官网:https://tianchi.aliyun.com/,并使用淘宝账户注册、登录。

(2)选择天池大赛 - 学习赛,进入学习赛题列表。

Drawing 0.png

(3)下滑列表,选择二手车交易价格预测比赛,标题为:“零基础入门金融风险 - 贷款违约预测”。

(4)进入比赛详情页后,点击报名参赛。

Drawing 1.png

(5)点击左侧的赛题与数据,进入数据集的页面,这个页面的上方是数据集的下载链接,下面则是数据集的描述。

Drawing 2.png

因为分析比赛的数据集都会分为训练集和测试集,我们现阶段不用关心这个,直接看训练集(train.csv) 即可。

行业数据集

除了比赛用的数据集之外,个人还可以从一些行业公开的网站上获得用于分析的数据。在这里列举三个比较常用的,你可以简单参考。

(1)清博智能:http://www.gsdata.cn/

清博智能是一个聚焦新媒体行业的大数据服务网站,提供了大量新媒体渠道的优质榜单,比如微信、头条、抖音……。只需要登录便可查看,同时支持下载为 Excel 格式。

Drawing 3.png

(2)房天下房价指数:https://fdc.fang.com/index/

顾名思义,这里提供的是房价相关的数据集,但数据均值以表格的形式提供的,没有 Excel 的形式。

(3)移动观象台:http://mi.talkingdata.com/app-rank.html

移动观象台提供了热门手机 App 的排行数据,手机 App 排行一直都是数据分析的热点。很多公司都希望通过对榜单进行分析来抓住用户的最新的兴趣以及来调整自己的业务方向。不过遗憾的是,它和房天下一样,移动观象台仅提供了网页访问,不可以下载 Excel 或者 CSV 格式文件。

存在的问题

回过头去看,无论是比赛数据集,还是行业公开的数据集,都有比较明显的短板。

  • 比赛数据集:数据集都是脱敏的,往往只能发现一些数据背后的隐藏关系,适合拿来测试一些数据挖掘算法,对于初级的数据分析帮助不大。

  • 行业公开数据集:绝大多数行业公开数据集都只能提供网页浏览或者 PDF,基本没有 Excel 可下载,所以只能看,很难在此基础上做自己的分析,而且免费用户能看的都比较有限。

简单来说,虽然个人可以从数据分析比赛和部分行业数据网站访问数据,但这两个渠道都存在一些问题,不能完全满足我们做数据分析的需要。我们还有什么方式可以获取到数据进行分析呢? 首先我们可以先想一下,什么地方的数据最多?答案就是:互联网本身。

从广袤的互联网中构建数据集

互联网包含成千上万个网站,而每个网站又包含数不清的帖子、评论、影评等。综合来说,互联网拥有着取之不尽,用之不竭的数据。如果我们可以直接从互联网根据需要拿数据进行分析,那简直不要太美。

一方面,来自互联网的分析数据都是真实用户产生的,分析的结论自然天生就具备极高的可信度。另一方面,来自互联网的数据大多都具备一定的规模,非常适合拿来实验各种各样的数据分析技巧,是学习数据分析的不二之选。

那现在问题来了,互联网的数据,基本都是通过一个个不同的网页的形式呈现。这种类型的数据如果进行数据分析呢? 我们知道,主流的数据分析往往都是基于表格,比如 Excel 或者CSV 文件。那有没有办法把互联网上的一个个网页变为能够被分析的表格呢?答案是肯定的。

通过Python 爬虫这门神奇的技术,就可以做到这件事情。接下来,我会介绍爬虫的基础。如何实现爬虫会在后续课时一一阐释。

什么是爬虫

爬虫是一类程序的名称,也有人称之为网络爬虫。爬虫程序简单理解就是下载网页并按照一定的规则提取网页中的信息,而 Python 则是市面上最适合用来开发爬虫程序的语言。

我们通过一个例子来说明爬虫到底可以干什么。

以某电视剧网站为例,我们看到的网页是这样的。

Drawing 4.png

但我们希望能够整理出一个电视剧的表格,比如下面这样:

image.png

一种方法是,我们看着网页,把电视剧和主演一个一个抄到 Excel 里。但这样比较麻烦,而且电视剧有几十页,根本不可能抄得完。

另一种方式就是 Python 爬虫,我们使用爬虫将网页中我们想要的内容(电视剧名、演员名)提取出来存放在 Python 的列表中。因为整个过程是用代码实现的,所以不管最终有多少页,我们使用一个循环就可以轻而易举获得所有电视剧的信息,最后再把保存了结果的列表存为 Excel 或者CSV 格式即可。效率相比人肉抄写提升百倍。

那现在问题来了。爬虫这么逆天的工具,背后的原理和流程是怎么样的呢?

爬虫的主要流程

本质上,爬虫的原理类似于我们拿来上网的浏览器,比如 Chrome、Edge 这些。我们首先来说一下浏览器的工作原理,以 Chrome 为例:

Drawing 5.png

如图所示,浏览器的流程大致分为四个步骤:

  1. 用户输入网址,告诉浏览器想看的网页;

  2. 浏览器根据网址,去找网址对应的服务器请求网页内容;

  3. 网址对应的服务器将网页内容返回给浏览器;

  4. 浏览器将收到的网页内容画在窗口中展示给用户。

了解了浏览器的工作内容,我们来看一下爬虫的工作流程:

Drawing 6.png

可以看到,爬虫的工作流程和浏览器非常相似,第二步和第三步与浏览器完全相同。爬虫的工作主要包括以下步骤:

  1. 用户在代码中指定要抓取的网页的网址;

  2. 请求网址对应的服务器;

  3. 服务器返回网页内容;

  4. 根据用户指定的规则提取感兴趣的内容(比如之前的例子,我们仅对电视剧名字和演员名感兴趣)。

从上面的例子可以看出,我们要实现一个爬虫程序,主要要实现三大模块。

  • 数据请求:可以像浏览器一样,根据一个网址去下载对应的网页内容。

  • 网页分析:根据规则,从网页繁多的文字、图片中筛选出感兴趣的内容。

  • 数据保存:抓取到的感兴趣的内容保存到CSV、Excel 文件中,为后续的分析环节做好准备。

在接下来的三讲中,我将会逐一带你学习这三个模块的实现,然后会通过一个实战来将所学的知识都串起来,一步步地从互联网中构建一个完整的数据集。

爬虫的注意事项

爬虫的功能十分强大,如武侠小说写的那样,越是强大的武器越要讲究正确地使用,滥用往往会导致很多不好的事情发生。

爬虫也是一样,一方面,我们可以通过爬虫来直接抓取互联网上的网页信息来构建我们的数据集。但另一方面,网站数据的所有权毕竟还是网站自身。虽然爬虫本质和浏览器的角色一样,但爬虫可以做到短时间就爬取大量的网页和数据,所以在开发与使用爬虫技术的时候,我们一定要注意以下两点:

  • 适当降低抓取网页的频率,以免给相关的网站服务器产生负担;

  • 抓取到的数据仅作自己分析使用,切忌传播或销售,否则可能有违法的风险。

小结

现在我们来总结一下,本章对于个人获取数据集的方式做了一个概要的介绍,主要分为两大类。

(1)获取现成的数据集

现成的数据集又分为比赛用数据集以及行业公开的数据集。比赛用的数据集往往都以CSV 的形式提供,非常适合拿来做程序化的分析,缺点是数据往往经过了脱敏和清洗,比较适合拿来测试机器挖掘算法,常规分析可能无法分析出有价值的结论。而行业数据集虽然比较贴近于真实世界,但是往往不提供 CSV/Excel ,只能提供网页访问,很难用于分析。换句话说,只能看,不能玩。

(2)通过爬虫技术自己构建数据集

现成的数据集固然方便,但却各有各的限制。当遇到不能满足分析需要的情况是,最终极的解决方案还是通过爬虫技术,从广袤的互联网世界中去构建数据集。

爬虫的工作原理类似浏览器,通过下载网页内容并根据规则提取其中的部分内容,最后保存CSV/Excel 文件中,即可实现将无限的网页转换为可分析的数据集的能力。

要实现爬虫,最关键的是三个步骤,也是接下来的几讲会展开的内容:

  • 下载网页;

  • 分析提取网页里的内容;

  • 将提取到的内容保存到文件。

等你学完了爬虫技术,你最想抓取的网站是什么呢?


07 下载网页:如何使用模拟请求下载真实的网页?

在上一讲中,我初步介绍了爬虫的原理以及要实现爬虫的三个主要步骤:下载网页-分析网页-保存数据。

今天我们就从第一步开始实操,如何使用 Python 下载网页。

网页是什么

上一讲我有介绍浏览器画网页的流程,是浏览器将用户输入的网址告诉网站的服务器,然后网站的服务器将网址对应的网页返回给浏览器,由浏览器将网页画出来。

这里所说的网页,一般都是一个后缀名为 html 的文件

网页文件和我们平时打交道的文件没什么不同,平时我们知道 Word 文件,后缀名为 .doc, 通过 Word 可以打开。图片文件后缀名为 .jpg,通过 Photoshop 可以打开;而网页则是后缀名为 .html,通过浏览器可以打开的文件。

网页文件本质也是一种文本文件,为了能够让文字和图片呈现各种各样不同的样式,网页文件通过一种叫作 HTML 语法的标记规则对原始文本进行了标记。

手动下载网页

我们以煎蛋网为例体会一下网页的实质,使用浏览器打开这个链接http://jandan.net/可以看到如下界面。可以看到,第一条新闻的标题前缀是:大吐槽。网页内容可能会随时间变化,这里你只需要注意第一条新闻的前几个字(暗号)即可,下同。

Drawing 0.png

在空白区域点击右键,另存为,并在保存类型中选择:仅 HTML。

Drawing 1.png

接下来回到桌面,可以看到网页已经被保存到桌面了,后缀名是 html,这个就是我们所说的网页文件。

网页内容初探

我们右键刚下载的文件,选择用 VS Code 打开,打开后的文件内容如下图所示。

Drawing 2.png

这就是网页文件的实际内容(未被浏览器画出来之前)。现在先不用管看不懂的代码,还记得我们看到的第一条新闻吗?“大吐槽………………”。(你的暗号)

我们在 VS Code 中通过 CTRL + F 调出搜索面板,搜索“大吐槽”(暗号)。可以看到成功找到了这条新闻,虽然被很多不认识的代码包围,但这也可以确定,我们看到的煎蛋网的主页确实就是这个 html 文件

Drawing 3.png

html 文件里有文字,也有很多我们看不懂的标记和代码,浏览器正是通过这些标记来将文本画成丰富多彩的网页。

现在,我们已经确认了 html 文件中包含我们所需要的信息,接下来我们将会学习如何使用Python 代码来下载 html 文件。

如何实现下载普通网页

Python 以系统类的形式提供了下载网页的功能,放在 urllib3 这个模块中。这里面有比较多的类,我们并不需要逐一都学会,只需要记住主要的用法即可。

(1)获取网页内容

还是以煎蛋网为例,首页的网址是:http://jandan.net/。在我写作这一讲的时候,排在第一的新闻是:“引发普通感冒的鼻病毒会将新冠病毒排挤出细胞!”。

煎蛋又更新了新的新闻(如下图),你记住你当时的第一条新闻题目即可。我们待会儿会在我们下载的网页中搜索这个标题来验证我们下载的正确性。

Drawing 4.png

在学习课程的工作目录下新建一个文件夹 chapter07(我的如下图),如果之前没有固定存储途径可以在桌面新建文件夹 chapter07,用于存放本讲的相关代码与文件。

Drawing 5.png

之后打开 VS Code。选择文件->打开文件夹,选择刚才新建的 chapter07 文件夹(今天之所以要建文件夹是方便查看下载的文件)。

打开文件夹后,按CTRL + P,调出命令菜单,输入>Jup, 在命令菜单中选择:Create New Blank Notebook,如下图所示。

Drawing 6.png

之前按 CTRL + S 保存,在弹出的对话框中选择保存位置为刚才创建的文件夹,文件名保存为 chapter07.ipynb, 如下图所示。

Drawing 7.png

保存完毕后如图所示:

Drawing 8.png

在默认创建的 Cell 中,输入如下代码:

# 导入 urllib3 模块的所有类与对象
import urllib3

# 将要下载的网址保存在 url 变量中,英文一般用 url 表示网址的意思
url = “http://jandan.net/p/date/2021/03/23”

# 创建一个 PoolManager 对象,命名为 http
http = urllib3.PoolManager()

# 调用 http 对象的 request 方法,第一个参数传一个字符串 “GET”
# 第二个参数则是要下载的网址,也就是我们的 url 变量
# request 方法会返回一个 HTTPResponse 类的对象,我们命名为 response
response = http.request(“GET”, url)

# 获取 response 对象的 data 属性,存储在变量 response_data 中
response_data = response.data

# 调用 response_data 对象的 decode 方法,获得网页的内容,存储在 html_content
# 变量中
html_content = response_data.decode()

# 打印 html_content
print(html_content)

上述代码就完成了一个完成的网页下载的功能。其中有几个额外要注意的点:

  • 我们创建 PoolManager的时候,写的是 urllib3.PoolManager,这里是因为我们导入了 urllib3 的所有类与函数。所以在调用这个模块的所有函数和类的前面都需要加模块名,并用点符号连接。

  • response 对象的 data 属性也是一个对象,是一个 bytes 类型的对象。通过调用 decode 方法,可以转化成我们熟悉的字符串。

执行上述代码,可以看到打印出了非常多的内容,而且很像我们第一部分手动保存的网页,这说明目前 html_content 变量中保存的就是我们要下载的网页内容。

Drawing 9.png

(2)将网页保存到文件

现在 html_content 已经是我们想要的网页内容,对于完成下载只差最后一步,就是将其保存成文件。其实这一步已经和保存网页无关的,而是我们如何把一个字符串保存成一个文件

Python 中,读取文件和保存文件都是通过文件对象来完成的。接下来,我们通过实际的例子来学习这个技术。

新建 Cell,输入以下代码:

# 调用 open 函数,三个参数都是字符串类型,第一个参数为要操作的文件名
# 第二个参数代表模式,w 表示写入文件,r 表示读取文件
# 第三个参数表示编码格式,一般有中文的认准 utf-8 就好
# open 函数返回一个文件对象,我们存储在 fo 变量中
fo = open("jiandan.html","w", encoding="utf-8")
# 调用文件对象的 write 方法,将我们上面存储着网页内容的字符春变量,html_content 
# 作为参数
fo.write(html_content)
# 关闭文件对象
fo.close()

执行完上述代码后,可以在 VS Code 的左侧边栏中看到,chapter07 文件夹下多了一个 jiandan.html 的文件,这个就是我们用刚才 Python 代码保存的文件。

打开就可以看到熟悉的网页内容了,如下图所示。

Drawing 10.png

接下来,我们验证一下我们下载的这个网页是不是我们通过浏览器看到的。还记得第一条新闻的标题吗?——“引发普通感冒的鼻病毒会将新冠病毒排挤出细胞”(暗号)。

我们在 jiandan.html 文件中搜索这句话,可以成功搜索到,这说明这就是我们看到的网页内容。

Drawing 11.png

至此,我们正式完成了通过代码下载网页的功能,回过头去看,其实并没多少行代码,是不是逐渐开始感受到 Python 的强大了呢。

(3)让我们的代码更加通用

刚才我们在两个 cell 中分别实现了将网页保存成一个字符串,以及将字符串保存为一个文件。如果我们要抓取新的网页,要么直接修改之前的代码,要么就需要拷贝一份代码出来。

这两种方式都不是很好,基于我们之前学习的内容,对于有一定通用度的代码我们可以将其改写为函数,来方便后续使用。

改写之后的代码如下:

# 第一个函数,用来下载网页,返回网页内容
# 参数 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()
url = "http://jandan.net/"
# 调用 download_content 函数,传入 url,并将返回值存储在html_content 
# 变量中
html_content = download_content(url)
# 调用 save_to_file 函数,文件名指定为 jiandan.html, 然后将上一步获得的
# html_content 变量作为第二个参数传入
save_to_file("jiandan.html", html_content)

这样改写之后,我们在抓取新的网页的时候就可以使用 download_content 函数和 save_to_file 函数快速完成了,不再需要去写里面复杂的实现。

如何实现下载动态网页

urllib3 很强大,但是却不能一劳永逸地解决网页下载问题。对于煎蛋这类普通网页,urllib3 可以表现更好,但是有一种类型的网页,它的数据是动态加载的,就是先出现网页,然后延迟加载的数据,那 urllib3 可能就有点力不从心了。

我们以豆瓣的电视剧网页为例,网址为:https://movie.douban.com/tv

Drawing 12.png

现在,我们来使用刚才定义的两个函数来下载一下这个网页。

url = "https://movie.douban.com/tv"
html_content = download_content(url)
save_to_file("douban_tv.html", html_content)

代码很简单,就是把豆瓣电视剧的网页下载到 douban_tv.html 这个文件中。执行代码,可以在 VS Code 左边的文件夹视图中看到成功生成了douban_tv.html 这个文件,这说明网页已经下载成功。如下图所示:

Drawing 13.png

现在我们在 VS Code 中打开这个网页,搜索上图中出现的电视剧:“山河令”。这次却神奇的搜不到了,事实上,你会发现我们在网页看到的电视剧名字都搜不到。

Drawing 14.png

为什么我们明明下载到了网页但是却搜不到电视剧呢?造成这个现象的原因是豆瓣电视剧网页中的电视剧列表的部分是动态加载的,所以我们用 urllib3 去直接下载,只能下载到一个壳网页,没有里面的列表内容。这种网页内部的数据是动态加载的网页,我们统一称之为动态网页。

动态网页应该怎么抓取呢?回过头去想,一个网页不管再怎么动态,最终都是要展示给用户看的,所以浏览器应该是最知道网页内容是什么的角色。如果我们可以使用代码控制浏览器来帮我们下载网页,应该就可以解决动态网页的抓取问题。

接下来我们就介绍使用 Python 来控制浏览器的利器:selenium。

安装 selenium

selenium 不属于 Python 的系统库,所以要使用这个库需要先进行安装。

我们安装 Python 的库一般通过 Anaconda 的命令行。既然是模拟浏览器,我们的电脑首先要先有浏览器。这里我们以 Chrome 为例。所以在一切开始之前,你需要确保你电脑上安装了 Chrome。

在课前准备环节,我们已经安装了 Anaconda 套件,现在我们去开始菜单(或者在桌面状态下按 Win 键)找到 Anaconda 3 文件夹,并点击文件夹中的 Anaconda Prompt 程序。Mac 用终端即可。

Drawing 15.png

启动之后会出现一个黑乎乎的界面,这个就是 Anaconda 的命令行。

Drawing 16.png

在这个命令行,我们可以输入 conda install xxx 来安装 Python 的扩展库。

比如在这个例子中,我们输入 conda install selenium,回车。界面会变得如下所示,询问我们是否要确认安装,输入 y 继续回车就可以。

Drawing 17.png

安装完毕后命令行窗口会回到待输入命令的状态,此时就可以关闭了。

回到 chaper07.ipynb,新建 Cell,输入以下的测试代码:

# 从 selenium 库中导入 webdriver 类
from selenium import webdriver
# 创建一个 Chrome 浏览器的对象
brow = webdriver.Chrome()
# 使用 Chrome 对象打开 url(就是刚才豆瓣电视剧的 url)
brow.get(url)

运行代码,会自动打开一个 Chrome 浏览器的窗口,并展示 url 对应的网页。同时还会有一个提示,说明这个浏览器窗口是在被程序控制的,如下图所示。

Drawing 18.png

如果代码运行出错,提示找不到 chromedriver。那说明你安装的 selenium 版本缺少 chromedriver, 可以按如下方式操作:

  1. 重新按照刚才的方法打开 Anaconda Prompt,输入 pip install --upgrade --force-reinstall chromedriver-binary-auto 回车执行安装。

  2. 在上面的代码增加一行 import chromedriver_binary 添加完毕后如下所示。

# 从 selenium 库中导入 webdriver 类
from selenium import webdriver
# 导入 chromedriver
import chromedriver_binary
# 创建一个 Chrome 浏览器的对象
brow = webdriver.Chrome()
# 使用 Chrome 对象打开 url(就是刚才豆瓣电视剧的 url)
brow.get(url)
使用 selenium 下载动态网页

如果刚才的代码已经运行成功并打开了 Chrome 的界面的话,那我们离最后的下载动态网页已经不远了。在我们通过 Chrome 对象打开了一个网页之后,只需要访问 Chrome 对象的 page_source 属性即可拿到网页的内容。

代码如下所示:

# 从 selenium 库中导入 webdriver 类
from selenium import webdriver
# 创建一个 Chrome 浏览器的对象
brow = webdriver.Chrome()
# 使用 Chrome 对象打开 url(就是刚才豆瓣电视剧的 url)
brow.get(url)
# 访问 Chrome 对象的 page_source 属性,并存储在 html_content 变量中
html_content = brow.page_source
# 调用我们之前定义的 save_to_file 函数,这次我们保存为 double_tv1.html
# 要保存的内容就是 html_content
save_to_file("douban_tv1.html", html_content)

运行代码,可以看到 Chrome 被打开并加载网页,等电视剧列表都加载完之后,在 VS Code 中可以看到 double_tv1.html 也被成功创建。

Drawing 19.png

这个时候我们去这个文件搜索山河令,发现已经有结果了,在这个 html 文件中已经有了所有电视剧的信息。

Drawing 20.png

至此,我们也实现了对于动态内容网页的下载功能。

小结

这一讲里,我们首先对网页有了个比较初步的介绍,说明了网页本质上是一种被 html 语法规则标记的文本文件,并通过在浏览器的空白区域点击右键-另存为,我们可以将网页保存下来。

之后,我们通过 urllib3 来下载普通网页,并将整个过程写成了两个比较通用的函数:download_content 和 save_to_file。

而对于部分包含动态内容的网页,比如豆瓣电视剧的主页、电视剧列表是动态请求的。我们通过 selenium 模块操作浏览器的形式实现了动态网页的下载。

课后练习:

煎蛋的内容是分页存储的,规则是:第一页是 jiandan.net ,第二页是 jiandan.net/page/2,第三页是 jiandan.net/page/3 ....

请你编写代码,下载煎蛋最近五页的内容。


答案:

# i 从 1 到 5循环,
for i in range(16):
    url = "http://jiandan.net"
    # 第一页不需要加/page/x 后缀,所以判断大于 1 才加
    if i > 1:
        url = url + "/page/" + str(i)
    html_content = download_content(url)
    save_to_file("jiandan_"+str(i) + ".html", html_content)

08 提取数据:如何从网页中提取感兴趣的内容?

上一讲中,我们学习了如何通过 Python 下载到网页的内容,并保存为 html 文件。但保存下来的 html 文件中还包括很多我们不需要的内容(那些复杂的代码标签等),如何才能拿到我们感兴趣的内容呢? 比如电视剧的名字或者新闻标题等。

今天我就介绍下,如何通过 Python 从网页中获取我们感兴趣的数据。

HTML 基础

在上一讲中,我们知道网页本质上是一种由 HTML 规则标记的文本文件。如果我们要从 HTML 中过滤我们需要的信息,首先第一步就是需要了解 HTML 标记规则的基础知识。

HTML 标签基础

HTML 页面其实是由很多基本的元素组合而成,这种元素一般叫作标签。形式如下:

<标签名> 网页内容 </标签名>

主要分为三个部分。

  • 开始标记:就是上面的 <标签名> 部分,代表下来的内容是一个标签元素。

  • 标签内容:就是上面的网页内容部分,指的是在这个标签内部的网页内容,标签内部的网页内容的样式会受到标签的影响。

  • 结束标记:也就是上面的 </标签名> 部分,代表这个标签已经结束了。

HTML 正是通过各种各样不同的标签来把普通的文字内容变成五彩斑斓的网页。

标签是怎么对文本的格式产生影响呢?下面用一个例子来说明,为了便于理解,这里的标签不是真实的 HTML 标签,不过工作原理是差不多的。

比如说,我们希望展示展示一行文字:“今天星期三,天气晴”,但为了醒目希望将其颜色变为红色。那我们可以增加红色的标签,如:

<red>今天星期三,天气晴</red>

<red> 就是开始标记,代表接下来是一个 red 标签,“今天星期三,天气晴”则是 red 标签内部的网页内容,会被 red 标签所影响(文字变为红色)。</red> 为结束标记,则代表之后的文字就不是红色了。

另外,我们想强调天气晴,希望把这三个字加粗,所以我们可以把这三个字放在粗体的标签里。

<red>今天星期三,<bold>天气晴</bold></red>

bold 标签在 red 标签包含的内容里。这说明了标签的一个重要的特型,就是可以嵌套存在。简单地说就是一个标签包含的内容里可以包含其他的标签。在上面的例子中,我们把 bold 标签叫作 red 标签的子标签,因为它是被 red 标签包住的。

现在,我们希望将这句话居中显示,那我们可以把整段内容放在用来居中的标签中。为了方便查看,我们可以将开始标记、标签内容和结束标记分别放在一个新的行。

如下所示:

<center>
  <red>
    今天星期三,
    <bold>
      天气晴
    </bold>
  </red>
</center>

HTML 中,是否换行只是为了标签的美观,并不影响结果的正确性。

现在,我们可以回过头去看之前我们下载的煎蛋网的主页。

用 VS Code 打开 chapter07 的文件夹,并打开 jiandan.html。并搜索“普通感冒”(由于更新有延迟,你还用你 07 课时的暗号,即你 07 课时看到的第一行标题) 定位到新闻的区域。如下图所示。

Drawing 0.png

可以看到,来自真实世界的网页基本都是一个个的标签互相嵌套而已(红框部分画出了一部分)。下一节,我们将学习 HTML 标签的几个主要的组成部分。

HTML 标签的属性

看我们的 jiandan.html,你会发现标签和我之前介绍的不太一样,具体就是开始标签的尖括号内除了标签名,还多了一些内容。这个部分的内容叫作:标签的属性。

具体的形式如下所示:

Drawing 1.png

一个标签里面可能会有一个或者多个属性,每个属性的形式是都是属性名 = 属性值这样的形式。

为什么光用标签还不够,还需要标签的属性呢?举个例子:比如我们希望给网页中的文字设定不同的字号(字体大小)。我们总不能每个字号都弄一个标签。而是会选择设计一个“整合”的字体标签,然后把字号作为字体标签的属性来实现。比如这是一段 18 号大小的字:

<font size="18">这是一段18号大小的字</font>
常见标签属性

HTML 标签的属性是学习爬虫的重点之一,我们本讲的核心任务就是从一大堆标签中选取出我们想要的内容,主要的方法就是通过标签名搭配标签属性。有的属性是某个标签特有的,比如 font 标签的 size 属性。有的属性是所有标签都有的,最常见的就是 id 和 class 这两个属性。

(1)id 属性

id 顾名思义,一般代表标签的“名字”。 类似我们的身份证号。比如下图中, div 标签的 id 为 wrapper, 下面一个 div 标签的 id 则叫 header。虽然 HTML 的标准定的比较宽松,比如不是每个标签都会有 id 属性,而且 id 的值可能也会有重复,但 id 属性仍然是我们在抓取的内容期间比较重要的参考。

Drawing 2.png

(2)class 属性

class 属性,代表标签的类别。类别是做什么的呢? HTML 有一套机制可以创建一组样式,并给这组样式起一个名字,比如 style_a。然后只要有标签需要用到这组样式,只需要将 class 属性的值写为 style_a ,那这组样式就自然对这个标签生效了。我们编写爬虫不需要关心这套流程是怎么实现的,只需要知道 class 是所有标签都有的属性即可。

如下图所示:箭头所指的 div 标签的 class 为 logo。代表这个页面的某个地方定义了 logo 这个样式组。

Drawing 3.png

至此,我们已经学完了 HTML 的基本要素,在这里总结一下:

  1. HTML 是由一个个标签组成的;

  2. 标签可以嵌套,也就是每个标签内部可以有其他标签,里面的标签也叫作外面标签的子标签;

  3. 在标签的开始标记中,可以指定标签的属性;

  4. id 和 class 属性是最广泛的使用的属性,每个标签都有这两个属性。

使用 BeautifulSoup 提取内容

BeautifulSoup 是一个 Python 库,用于分析 HTML。它和它的名字一样,用起来非常“香”。今天我们会通过使用 BeautifulSoup 去从上一节课我们下载到的 html 文件:jiandan.html 中提取所有新闻的标题。

首先,我们在工作目录新建 chapter08,然后把我们在 chapter07 目录中下载的 jiandan.html 拷贝过来。

打开 VS Code,并点击文件 → 打开文件夹,打开刚才创建的 chapter08 文件夹。打开了之后应该就可以在 VS Code 中看到 jiandan.html。

然后新建一个 Notebook,保存为 chapter08.ipynb。接下来我们将在这个 notebook 中对 jiandan.html 进行内容提取。

Drawing 4.png

因为煎蛋网每天都在更新,如果你希望使用和本讲完全一样的 jiandan.html ,可以直接去这里下载

安装 BeautifulSoup

和之前安装 selenium 的方式类似,我们去开始菜单 → Anaconda ,选择 Anaconda Prompt,出现命令行界面。苹果系统(Mac )搜索终端即可。

在命名行界面,输入如下的安装命令(BeautifulSoup 的 Python 包名为 bs4, 简写)

conda install bs4

回车执行,之后在询问会否确认时输入 y 回车,等待安装完毕,关闭即可。

Drawing 5.png

读取 html 文件到 Python

数据提取的第一步,我们首先需要将 html 文件加载到 Python 的变量中。在上一讲中,我们学习了通过文件对象来把 Python 变量写进文件里,这里我们来尝试用类似的代码来将文件中的内容读出来。

代码如下,可以看到和写入文件的代码非常类似。

# 打开 jiandan.html,第二个参数 r,代表 read
# open 函数返回一个文件对象,保存在变量 fo 中
fo = open("jiandan.html","r",encoding="utf-8")
# 调用文件对象的 read 函数,该函数将文件的内容读取到 Python 中
# read 函数的返回值就会文件内容,我们保存在 html_content 中
html_content = fo.read()
# 关闭文件对象
fo.close()
# 打印 html_content 变量,看内容是否被正确加载
print(html_content)

按 shift + enter 执行之后,输出结果如下所示(打印的 html 文本太长,这里仅截图示意)。

Drawing 6.png

创建 BeautifulSoup 对象

现在,我们已经将文件的内容读了出来,但是目前还是以一个超大的字符串的形式存储在变量中。想要对其进行有效的分析,第一步我们先要构造一个 BeautifulSoup 的对象。代码如下:

# 从 bs4 模块中导入 BeautifulSoup 类
from bs4 import BeautifulSoup
# 创建一个 BeautifulSoup 的对象,并将刚才我们存储网页内容的变量作为参数
doc = BeautifulSoup(html_content)
# 打印 doc 对象的 title 属性。
print(doc.title)

执行上述代码之后,BeautifulSoup 对象就被创建并存在变量 doc 中,为了测试是否创建成功,我们打印了 doc 对象的 title 属性,输出如下。

<title>
煎蛋 - 地球上没有新鲜事
</title>
BeautifulSoup 对象的基本使用

刚刚我们打印了 doc 对象的 title 属性,来测试对象是否创建成功。从打印的内容来看,想必你已经猜到了,doc 对象的 title 属性,就对应了网页的 title 标签。但是刚才我们打印的内容中,title 标签也被打印出来了。

那是否可以只取标签里面的内容呢?

(1)get_text 方法

在 BeautifulSoup 中,我们可以通过标签对象的 get_text 方法来获得标签中的内容。

# 将 title 属性保存在 title_label 的变量中
title_label = doc.title
# 调用 get_text 方法,并把返回值打印出来
print(title_label.get_text())

输出如下:

煎蛋 - 地球上没有新鲜事

可以看到,这次我们成功将标签内的文字提取了出来。

(2)find_all 方法

BeautifulSoup 对象另一个常用方法是 find_all, 用来在 html 文档中查到特定标签名以及特定属性值的标签,下面我举例来说明。

在上面,我们学习 html 标签的 class 属性的时候,截了一张这样的图:

Drawing 7.png

这个截图同样也是来自 jiandan.html 这个文件中,而目前我们的 doc 对象已经包含了这些内容。现在我们来尝试提取 class 等于 logo 的这个 div 标签,代码如下。

# find_all 是 BeautifulSoup 对象的常用方法,用于查找特定的标签
# 接受三个参数,第一个是要查找的标签名,第二个则是要匹配的属性名
# 比如这里,我们查找的是 class=logo 标签,则直接按如下写法即可。
# 因为 class 是 Python 的关键字,所以这里需要使用 class_ 以示区分
# 最终,find_all 函数返回所有满足条件的标签列表
logo_label = doc.find_all("div", class_="logo")
print(logo_label)

输出结果为:

[<div class="logo">
<h1><a href="/" onclick="ga('send', 'pageview','/header/logo');">煎蛋</a></h1>
</div>]

可以看到,我们截图中的内容,箭头所指的 div 标签被成功的过滤了出来。

(3)获取标签对象的属性

通过标签对象的 attrs 属性,我们可以获取标签对象的属性的值,attrs 是一个字典,获取属性的值的方法如下:

# 取 logo_label 列表的第一个标签对象
label = logo_label[0]
# 打印这个对象的 class 属性
print(label.attrs["class"])

输出:

['logo']

过滤出煎蛋的新闻列表

现在,我们来使用之前的 doc 对象来过滤出新闻的标题列表。

(1)观察 html,提取初步的新闻标签列表

在使用 BeautifulSoup 进行内容过滤的第一步,首先要查看我们想要的内容所在的标签。我们在 VS Code 中打开 jiandan.html,搜索“普通感冒”(暗号,你在 07 课时看到的第一行标题关键词)到达新闻标题内容区域,如下图所示。

Drawing 8.png

通过观察截图中的标签内容,很容易发现似乎每个新闻标题,都有一个对应的 div 标签,并且它的 class 是 indexs。沿着这个思路,我们可以先考虑使用 find_all 将其过滤出来。通过如下代码。

# 查找 class 为 indexs 的所有 div 标签,存储在 index_labels 中
index_labels = doc.find_all("div", class_="indexs")
# 打印符合条件的 div 标签的数量
print(len(index_labels))

输出:

24

说明找到了 24 个符合条件的 div 标签。一个网页的新闻也是 20 多条,说明有希望。

(2)提取单个标签的新闻标题

因为 index_labels 是一个标签对象的列表,距离我们要过滤的新闻标题还有距离。下一步,我们就来分析如何从这些标签对象中抽取新闻标题。

首先,我们先取第一个来看一下。

first_object = index_labels[0]
print(first_object)

输出如下:

<div class="indexs">
<span class="comment-link" title="1小时 ago">8</span>
<h2><a href="http://jandan.net/p/108693" target="_blank">引发普通感冒的鼻病毒会将新冠病毒排挤出细胞!</a></h2>
<div class="time_s"><a href="http://jandan.net/p/author/majer">majer</a> · <strong><a href="http://jandan.net/p/tag/%e6%96%b0%e5%86%a0%e7%97%85%e6%af%92" rel="tag">新冠病毒</a></strong></div>
换而言之,如果你感冒了,在那段期间就不会被新冠病毒感染 </div>

现在我们的问题就转化为,如何从上述 html 中抽取出新闻的标题。

通过查看上述内容可以发现,我们的标题在第三行,在一个 a 标签内部。那是不是我们只需要过滤出这个 a 标签,再使用 get_text 拿到里面的内容就好了?

你猜得没错。那要怎么过滤出第三行的 a 标签呢?可以看到这个 a 标签里有 target="_blank" 这样一个属性,我们就可以使用 find_all 将其过滤出来了。

# 从刚才的 first_object 标签对象中过滤出所有 target=_blank 的 a 标签
a_labels = first_object.find_all("a",target="_blank")
# 取第一个标签对象
my_label = a_labels[0]
# 打印该标签对象内的文字内容
print(my_label.get_text())

输出如下:

引发普通感冒的鼻病毒会将新冠病毒排挤出细胞!

你看,新闻的标题被成功抽取了出来!

(3)提取新闻标题的列表

目前,我们已经实现了从第一个标签对象中提取新闻标题,但我们的列表中有 24 个标签对象。要怎么做呢?聪明的你应该想到了,我们只需要把“从标签对象抽取标题”这段逻辑写成一个函数,然后通过一个循环对列表的每个标签对象都调用这个函数即可。

代码如下:

# 从第一次 find_all 获取的标签对象中抽取标题
def get_title(label_object):
    # 从刚才的参数传入的标签对象中过滤出所有 target=_blank 的 a 标签
    a_labels = label_object.find_all("a",target="_blank")
    # 取第一个标签对象
    my_label = a_labels[0]
    # 将标签的文字内容作为返回值返回
    return my_label.get_text()
# 用循环针对每个 index_labels 列表中的标签对象
# 都将其作为参数传入 get_title 函数
# 并打印返回值
for i in range(0,len(index_labels)):
    title = get_title(index_labels[i])
    print(title)

执行后输出:

引发普通感冒的鼻病毒会将新冠病毒排挤出细胞!
无厘头研究:植入虚假的记忆再抹去它们
什么是仇恨犯罪?
突发:LHCb发现了违背标准模型的现象
今日带货 20210324
舌战裸猿:IBM搞出了可以打辩论赛的AI
大吐槽:「我没醉,醉的是世界」
今年世界总发电量的0.6%被用于挖比特币
接种疫苗后还是感染新冠?不要为此惊讶
今日带货:蛋友家的血橙
科学家首次在野外检测到抗多药的超级真菌
未在iPhone12盒中搭配充电器,苹果被巴西消协罚200万美元
工程师将解决城市陷坑的问题
今日带货:粉面专场
科学家在碟子里培育出了泪腺,并让它哭泣
疯狂实验进行时:把志愿者禁闭在黑暗的空间里40天
今日带货 20210321
我们已向外星人发送了哪些消息?
脑力小体操:石头+剪刀 VS 石头+布
发霉啦:今天,我终于向母亲摊牌了
普渡大学的经济学家计算出世界各地幸福的价格
人类首次观察到木星上极光黎明风暴的成形过程
为女儿出头,母亲编辑假裸照败坏高中啦啦队队员的名誉
今日带货:淘宝京东蛋友推荐

可以看到,jiandan.html 中的所有新闻标题被我们成功过滤了出来,现在是不是感受到了 BeautifulSoup 的神奇之处了呢。

至此,我们就完成了使用 BeautifulSoup 对网页进行内容的提取。

小结

总结一下今天所学习到的内容。

(1)HTML 基础

  • HTML 页面是由一个个 HTML 标签组成的。

  • HTML 标签分为开始标记、结束标记以及中间的内容三部分。每个标签内部可以在嵌套其他的标签,也可以是普通的文本。

  • HTML 标签的开始标记中可以写标签的属性,id 和 class 是所有标签都有的,最常用的属性。

(2)BeautifulSoup 的使用

  • 首先需要将 html 文件读到 Python 中,再用存储着文件内容的变量构造 BeautifulSoup 对象。

  • 可以使用 BeautifulSoup 对象的 find_all 方法,找到标签名和属性值都符合条件的标签对象,比如 doc.find_all('div', class_="logo") 代表找到标签名为 div, class 属性为 logo 的标签。

  • 标签对象都有 get_text 方法,用于返回标签内部的文本。

(3)网页数据抓取的分析方法

  • 观察想要过滤内容所在的标签特征,过滤出包含文本的标签,然后调用 get_text 拿到文本内容。

  • 如果层次关系比较复杂,可以分步走,一步一步过滤出最终的内容。比如例子中,我们首先过滤了 class=indexs 的 div 标签,再从其中过滤出包含标题的 a 标签。

课后练习:

在课程代码的基础上,编写代码,提取出新闻的时间内容,如下图红框中部分。

提示:需要用到标签对象的 attrs 属性。

Drawing 9.png


答案:

comments = doc.find_all("span",class_="comment-link")
for i in range(0,len(comments)):
    comment = comments[i]
    print(comment.attrs["title"])

输出

1小时 ago
4小时 ago
8小时 ago
12小时 ago
14小时 ago
23小时 ago
1天 ago
1天 ago
1天 ago
2天 ago
2天 ago
2天 ago
2天 ago
3天 ago
3天 ago
3天 ago
4天 ago
4天 ago
4天 ago
5天 ago
5天 ago
5天 ago
5天 ago
6天 ago

09 保存数据:如何将爬取的数据保存成 CSV 格式

今天我们来学习在数据分析领域最为常见一种文件格式:CSV 文件,然后我们再将上一讲抓取到的数据保存到 CSV 文件中。

什么是 CSV 文件

CSV(Comma-Separated Values) 是一种使用逗号分隔来实现存储表格数据的文本文件。

我们都知道表格有多种形式的存储,比如 Excel 的格式或者数据库的格式。CSV 文件也可以存储表格数据,并且能够被多种软件兼容,比如 Excel 就能直接打开 CSV 文件的表格,很多数据库软件也支持导入 CSV 文件。除了兼容性好之外,CSV 格式还是所有能存储表格的格式中最简单的一种。

下面,我们以一个例子来讲解 CSV 存储表格的原理。

假设有如下员工信息的表格。

图片1.png

要存储类似上面的表格,以往我们只能将其输入到 Excel 中并保存为 xlsx 格式,现在我们来尝试将其以 CSV 的格式保存。

在工作目录下,新建 chapter09 文件夹,作为我们第九讲的实验目录。然后打开文本编辑器,如 Windows 下的记事本,新建空白文件。

输入以下内容,并保存为 info.csv (编码选择 ANSI),文件夹的位置选择刚才创建的 chapter09/ 中。

姓名,年龄,籍贯,部门
小明,22,河北,IT部
小亮,25,广东,IT部
小E,23,四川,财务部

保存成功后,我们用 Excel 打开这个文件,可以看到我们存储的 CSV 文件成功地在 Excel 中展示为表格。

图片2.png

相信看到这里,你也发现 CSV 格式的奥秘了,总结一下:

  • 表格中的一行,对应 CSV 文件中的一行;

  • 一行中不同单元格的内容,在 CSV 文件中用逗号分隔;

  • 务必保证每行的逗号数量是一致的(对应表格中每行的单元格一致)。

在我们后续的数据分析任务中,CSV 文件将会是我们存储数据的主要格式。

Python 的 CSV 模块

学会了 CSV 文件的基本概念,今天我们来学习如何使用 Python 来操作 CSV 文件。因为对于数据分析场景而言,最常见的操作就是读取和写入。

准备知识

在学习使用 Python 操作 CSV 文件之前,我们首先先来充充电,学习一个 Python 非常常见的循环技术:遍历循环。

要逐个打印一个列表的元素,按照以前我们学习的循环知识,方法如下:让循环变量 i 从 0 逐步叠加到列表的长度,然后在循环体中每次取列表的第 i 个数并打印。

这种逐个处理列表中元素的行为,我们称之为遍历。比如我们初始化一个数组,然后让变量 i 从 0 循环到列表的长度。

# 初始化一个数组
arr = [12,5,33,4,1]
# 让变量 i 从 0 循环到列表的长度
for i in range(0,len(arr)):
    # 取列表的第 i 个元素,存储在 item 中
    item = arr[i]

    # 打印 item 变量
    print(item)

输出结果为数组的所有数值:

12
5
33
4
1

事实上,Python 还存在一个更加简便的方法来实现列表的遍历,即不用循环变量,而是直接对列表使用 for..in.. 语句,用法如下:

# 初始化一个数组
arr = [12,5,33,4,1]
# 直接对列表 arr 使用 for..in.., for 后面是循环变量,in 后面是列表变量
# 列表有几个元素,循环就执行多少次。
# 每次循环都从列表中取一个还没处理过的元素存储在循环变量中,然后调用循环体
for item in arr:
    print(item)

输出为:

12
5
33
4
1

可以看到,两种循环输出的结果是一样的,但是直接的遍历循环的代码量更少、更简单。遍历循环可以适用于任何类型的列表,以字符串列表为例:

string_arr = ["hi","hello""你好""aloha"]
for item in string_arr:
    print("本次循环 item 变量的值:", item)

上述列表中有四个字符串,使用遍历循环的方式遍历该列表,循环则会执行四次:第一次执行,item 变量存储的值是 "hi",第二次是 "hello" 以此类推。
上述代码输出为:

本次循环 item 变量的值: hi
本次循环 item 变量的值: hello
本次循环 item 变量的值: 你好
本次循环 item 变量的值: aloha

遍历循环相比较我们之前学习的循环,适用场景更广,比如后续会遇到一些“特殊列表”只能用此方案去访问每个元素。

从 CSV 文件读取内容

现在我们来尝试读取刚才保存的 info.csv 。

打开 VS Code,新建 Notebook,并保存为 chapter09.ipynb。在第一个 cell 中,我们首先导入 csv 模块,输入如下代码:

import csv

要读取 CSV 文件,我们需要用到 CSV 模块中的 DictReader 类,DictReader 可以将每一行以字典的形式读出来,key 就是表头,value 就是对应单元格的内容。

具体用法如下所示,新建 Cell ,输入如下代码。

# 通过 open 函数打开 info.csv ,并将文件对象保存在 fo 中
fo = open("info.csv ")
# 通过打开 CSV 文件的文件对象作为参数来创建 DictReader 类的对象,存在 reader 变量中
reader = CSV.DictReader(fo)
# 调用 reader 对象的 fieldnames 属性,获取 CSV 文件中表格的表头
headers = reader.fieldnames
# 关闭文件
fo.close()
# 打印表头的信息
print(headers)

输出如下:

['姓名', '年龄', '籍贯', '部门']

上面的例子中,我们学习了 DictReader 对象的创建方法以及通过 fileldnames 属性获取了 CSV 表格的表头。

接下来,我们尝试获取表格的实际内容。

新建 cell,输入如下代码:

# 打开 info.csv 
fo = open("info.csv ")
# 创建 DictReader 对象
reader = CSV.DictReader(fo)
# 创建列表,用于存储读到的行
row_list = []
# 使用遍历循环,直接对 reader 对象进行遍历
# 每次执行循环时,row 变量都存储了当前行的内容
for row in reader:
    # 直接将 row 变量添加到行列表中
    row_list.append(row)
# 关闭文件
fo.close()
# 打印第一行的表格数据
print(row_list[0])

打印的结果显示:

{'姓名': '小明', '年龄': '22', '籍贯': '河北', '部门': 'IT部'}

可以看到,我们拿到了第一行的内容,并且是以字典的形式,字典把每个单元格的内容和表头联系了起来,表头是 key,而具体内容就是 value。每行都是这样的一个字典,所有字典都存储在 row_list 列表中。

接下来,我们来演示对于 row_list 列表的常见操作:打印某一行、某一列的值。

print("打印年龄一列的内容:")
# 遍历循环 row_list,d 为循环变量 
for d in row_list:
    # 因为 d 是字典,直接打印 key 为 年龄的值即可。
    print(d["年龄"])
# 打印一个换行
print("")
print("打印第三行的内容:")
d = row_list[2]
print("姓名:", d["姓名"])
print("年龄:",d["年龄"])
print("籍贯:",d["籍贯"])
print("部门:",d["部门"])

输出如下:

打印年龄一列的内容
22
25
23
打印第三行的内容
姓名: 小E
年龄: 23
籍贯: 四川
部门: 财务部
写入 CSV 文件

在之前的例子中,我们写入 CSV 文件是靠我们人肉写入的,现在我们来试着通过 Python 写入 CSV 文件。

与读取类似,Python 的 CSV 模块提供了 DictWriter 方法,使得我们可以将表格数据以字典的形式存在到 CSV 文件中。

具体用法如下:

# 打开一个文件,假设是 info2.CSV,因为是写入,所以需要指定模式 "w"
# newline='',在写入 CSV 时,需要指定这个参数,这个记住即可。
fo = open("info2.CSV""w", newline='')
# 将表头存储在一个列表中
header = ["姓名""年龄""籍贯""部门"]
# 创建一个 DictWriter 对象,第二个参数就是上面创建的表头
writer = CSV.DictWriter(fo, header)
# 写入表头
writer.writeheader()
# 写入一行记录,以字典的形式,key 需要和表头对应。
writer.writerow({"姓名""小刚""年龄":"28""籍贯":"福建""部门":"行政部"})
# 关闭写入的文件
fo.close()

上述代码的关键点就在于,创建了 DictWriter 后,需要首先调用 writeheader 来写入表头,然后再调用 writerow 来写入行。

执行上述代码之后,并不会有内容输出,但是 chapter09 文件夹下会多出一个 Info2.csv, 用Excel 打开后,如下图所示。

图片3.png

可以看到,我们的表头和记录已经成功写入 CSV 文件中。

DictWriter 除了提供 writerow 方法来将单个字典保存为 CSV 表格中的一行,还提供了 writerows 方法来一次性地保存多行的内容。

你还记得我们之前将我们手工建的 CSV 表格的内容存储在 row_list 变量中吗?现在我们尝试使用 writerow 方法来一次性写入多条记录。

新建 Cell,输入以下代码:

# 新打开一个 info3.CSV 文件
fo = open("info3.CSV""w", newline='')
# 将表头存储在一个列表中
header = ["姓名""年龄""籍贯""部门"]
# 创建一个 DictWriter 对象,第二个参数就是上面创建的表头
writer = CSV.DictWriter(fo, header)
# 将小刚的记录插入到row_list 中
row_list.append({"姓名""小刚""年龄":"28""籍贯":"福建""部门":"行政部"})
# 写表头
writer.writeheader()
# 调用 writerows 方法,一次性写多个字典(一个字典列表)到 CSV 文件中
writer.writerows(row_list)
# 关闭文件
fo.close()

执行完毕后,chapter09/ 下生成了新的 info3.csv,打开后如下图所示,包含了一开始的三条记录,以及我们插入的“小刚”的记录。

图片4.png

实现煎蛋新闻列表保存到 CSV 文件中

接下来,我们来将上一讲中过滤出来的新闻列表写入 CSV 文件中。在上一讲中,我们在课程内容中获取了煎蛋的新闻标题,在课后作业中获取了新闻发布的时间。

我们今天的内容就是将每篇新闻的这两个内容保存到 CSV 中,相当于一个新闻就是一行,每一行有两列,一个是新闻标题,一列是发布时间。对应的表头就是:标题、发布时间。

(1)数据准备

第一步,我们首先将 chapter08 文件夹中的 jiandan.html 拷贝到 chapter09 文件夹中。

第二步,将第 08 讲中的抽取标题的代码整理成几个函数,方便后续调用。这里我们再简单回顾一下上一讲中抓取新闻的步骤:

  1. 打开文件网页,读出内容,并创建对应的 BeautifulSoup 对象;

  2. 找到所有包含新闻的 div 元素列表(class=indexs 的 div);

  3. 从 2 中的 div 元素中抽取出标题;

  4. 从 2 中的 div 元素中抽取出时间。

我们把上述四个操作整理为四个函数。

1. 首先我们来实现创建 BeautifulSoup 对象的函数。

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

函数的具体实现在 chapter08 都讲过,这里不再赘述。按 shift + enter 执行,这样后续我们就能够使用该函数。

2. 实现定位包含新闻的 div 元素的列表函数。

# 输入参数是 BeautifulSoup 对象,返回包含新闻的 div 元素列表
def find_index_labels(doc):
    index_labels = doc.find_all("div", class_="indexs")
    return index_labels

继续按 shift + enter 执行。

3. 实现新闻标题的抽取函数。

这里我们直接复制上一讲中的 get_title 函数即可。

# 从第一次 find_all 获取的标签对象中抽取标题
def get_title(label_object):
    # 从刚才的参数传入的标签对象中过滤出所有 target=_blank 的 a 标签
    a_labels = label_object.find_all("a",target="_blank")
    # 取第一个标签对象
    my_label = a_labels[0]
    # 将标签的文字内容作为返回值返回
    return my_label.get_text()

4. 实现获取新闻发布时间的函数。

# 和 get_title 函数一样,传入 label_object, 返回发布时间
def get_pub_time(label_object):
    # 找到 class=comment-link 的 span 标签
    spans = label_object.find_all("span", class_="comment-link")
    # 取第一个
    span = spans[0]
    # 返回标题属性
    return span["title"]

至此,我们四个基础函数已经准备好了,以上的 Cell 都需要注意按 shift + enter 执行,这样我们接下来才可以使用这些函数。

(2)获取新闻标题与列表

接下来,我们开始使用上面的函数来获得新闻的标题与新闻列表。

新建 Cell,输入如下代码:

# 调用 create_doc_from_filename 函数,创建 BeautifulSoup 对象
doc = create_doc_from_filename("jiandan.html")
# 调用find_index_labels 函数,传入 BeautifulSoup 对象
# 将返回的 div 列表存储在 index_labels 中
index_labels = find_index_labels(doc)
# 使用遍历循环遍历 index_labels 列表,循环变量为 label_object
for label_object in index_labels:
    # 调用 get_title, 传入当前处理的 div 元素对象,获取标题
    title = get_title(label_object)
    # 调用 get_pub_time,传入当前处理的 div 元素对象,获取发布时间
    pub_time = get_pub_time(label_object)
    # 将标题和发布时间打印出来
    print("标题:", title)
    print("发布时间:", pub_time)

上述代码把我们刚才准备的四个函数都串了起来。大概的思路就是首先创建 BeautifulSoup 对象,之后针对该对象查询 class = indexs 的列表,然后使用遍历循环遍历该列表,对于每一个 div 元素,分别调用 get_title 以及 get_pub_time 函数来获得标题与发布时间。

执行上述代码后,输出如下所示。可以看到,我们的新闻标题和时间都已经被成功打印了出来。

标题: 引发普通感冒的鼻病毒会将新冠病毒排挤出细胞!
发布时间: 1小时 ago
标题: 无厘头研究:植入虚假的记忆再抹去它们
发布时间: 4小时 ago
标题: 什么是仇恨犯罪?
发布时间: 8小时 ago
标题: 突发:LHCb发现了违背标准模型的现象
发布时间: 12小时 ago
标题: 今日带货 20210324
发布时间: 14小时 ago
标题: 舌战裸猿:IBM搞出了可以打辩论赛的AI
发布时间: 23小时 ago
标题: 大吐槽:「我没醉,醉的是世界」
发布时间: 1天 ago
标题: 今年世界总发电量的0.6%被用于挖比特币
发布时间: 1天 ago
标题: 接种疫苗后还是感染新冠?不要为此惊讶
发布时间: 1天 ago
标题: 今日带货:蛋友家的血橙
发布时间: 2天 ago
标题: 科学家首次在野外检测到抗多药的超级真菌
发布时间: 2天 ago
标题: 未在iPhone12盒中搭配充电器,苹果被巴西消协罚200万美元
发布时间: 2天 ago
标题: 工程师将解决城市陷坑的问题
发布时间: 2天 ago
标题: 今日带货:粉面专场
发布时间: 3天 ago
标题: 科学家在碟子里培育出了泪腺,并让它哭泣
发布时间: 3天 ago
标题: 疯狂实验进行时:把志愿者禁闭在黑暗的空间里40天
发布时间: 3天 ago
标题: 今日带货 20210321
发布时间: 4天 ago
标题: 我们已向外星人发送了哪些消息?
发布时间: 4天 ago
标题: 脑力小体操:石头+剪刀 VS 石头+布
发布时间: 4天 ago
标题: 发霉啦:今天,我终于向母亲摊牌了
发布时间: 5天 ago
标题: 普渡大学的经济学家计算出世界各地幸福的价格
发布时间: 5天 ago
标题: 人类首次观察到木星上极光黎明风暴的成形过程
发布时间: 5天 ago
标题: 为女儿出头,母亲编辑假裸照败坏高中啦啦队队员的名誉
发布时间: 5天 ago
标题: 今日带货:淘宝京东蛋友推荐
发布时间: 6天 ago
(3)将数据存储为字典的形式

要存储到 CSV,首先我们需要将我们的数据创建为字典的形式,我们可以在(2)的循环中将标题和时间存储为字典,然后使用一个字典列表来存储每个新闻对应的字典。最后直接使用 DictWriter 的 writerows 方法来将字典列表写入 CSV 文件即可。

我们直接修改刚才打印标题和发布时间的 Cell,删除原本的打印代码,并添加字典相关操作的代码。

添加完后的 Cell ,如下所示。

# 调用 create_doc_from_filename 函数,创建 BeautifulSoup 对象
doc = create_doc_from_filename("jiandan.html")
# 调用find_index_labels 函数,传入 BeautifulSoup 对象
# 将返回的 div 列表存储在 index_labels 中
index_labels = find_index_labels(doc)
# 【新增代码】存储新闻的字典列表
news_dict_list = []
# 使用遍历循环遍历 index_labels 列表,循环变量为 label_object
for label_object in index_labels:
    # 调用 get_title, 传入当前处理的 div 元素对象,获取标题
    title = get_title(label_object)
    # 调用 get_pub_time,传入当前处理的 div 元素对象,获取发布时间
    pub_time = get_pub_time(label_object)
    # 【新增代码】创建单条新闻的字典
    news = {"标题": title, "发布时间": pub_time}
    # 【新增代码】将新闻字典添加到字典列表
    news_dict_list.append(news)
# 【新增代码】打印出字典列表
print(news_dict_list)

通过循环,我们将新闻以字典的形式逐个添加到了字典列表中,然后在最后打印出列表,输出如下所示。

[{'标题': '引发普通感冒的鼻病毒会将新冠病毒排挤出细胞!', '发布时间': '1小时 ago'}, {'标题': '无厘头研究:植入虚假的记忆再抹去它们', '发布时间': '4小时 ago'}, {'标题': '什么是仇恨犯罪?', '发布时间': '8小时 ago'}, {'标题': '突发:LHCb发现了违背标准模型的现象', '发布时间': '12小时 ago'}, {'标题': '今日带货 20210324', '发布时间': '14小时 ago'}, {'标题': '舌战裸猿:IBM搞出了可以打辩论赛的AI', '发布时间': '23小时 ago'}, {'标题': '大吐槽:「我没醉,醉的是世界」', '发布时间': '1天 ago'}, {'标题': '今年世界总发电量的0.6%被用于挖比特币', '发布时间': '1天 ago'}, {'标题': '接种疫苗后还是感染新冠?不要为此惊讶', '发布时间': '1天 ago'}, {'标题': '今日带货:蛋友家的血橙', '发布时间': '2天 ago'}, {'标题': '科学家首次在野外检测到抗多药的超级真菌', '发布时间': '2天 ago'}, {'标题': '未在iPhone12盒中搭配充电器,苹果被巴西消协罚200万美元', '发布时间': '2天 ago'}, {'标题': '工程师将解决城市陷坑的问题', '发布时间': '2天 ago'}, {'标题': '今日带货:粉面专场', '发布时间': '3天 ago'}, {'标题': '科学家在碟子里培育出了泪腺,并让它哭泣', '发布时间': '3天 ago'}, {'标题': '疯狂实验进行时:把志愿者禁闭在黑暗的空间里40天', '发布时间': '3天 ago'}, {'标题': '今日带货 20210321', '发布时间': '4天 ago'}, {'标题': '我们已向外星人发送了哪些消息?', '发布时间': '4天 ago'}, {'标题': '脑力小体操:石头+剪刀 VS 石头+布', '发布时间': '4天 ago'}, {'标题': '发霉啦:今天,我终于向母亲摊牌了', '发布时间': '5天 ago'}, {'标题': '普渡大学的经济学家计算出世界各地幸福的价格', '发布时间': '5天 ago'}, {'标题': '人类首次观察到木星上极光黎明风暴的成形过程', '发布时间': '5天 ago'}, {'标题': '为女儿出头,母亲编辑假裸照败坏高中啦啦队队员的名誉', '发布时间': '5天 ago'}, {'标题': '今日带货:淘宝京东蛋友推荐', '发布时间': '6天 ago'}]
(4)存储到 CSV 文件中

现在,我们已经将网页中抓取到的数据都保存在一个字典列表中:news_dict_list ,接下来就是将这个列表写入到 CSV 文件中即可。

代码如下所示:

# 创建 news.CSV 文件
fo = open("news.CSV""w", newline='', encoding='utf_8_sig')
# 这一次的表头
header = ["标题""发布时间"]
# 使用文件对象和表头初始化 DictWriter 对象
writer = CSV.DictWriter(fo, header)
# 写入表头
writer.writeheader()
# 将上一步计算的字典列表写入 CSV 文件中
writer.writerows(news_dict_list)
# 关闭文件对象
fo.close()

执行之后,在 chapter09 文件夹下会生成 news.CSV 文件,用 Excel 打开后如下图所示。可以看到,我们的数据已经成功以表格的形式呈现了。

图片5.png

小结

至此,本讲的内容我们就学习完毕了,简单的复习一下本讲所学习的内容:

  • 使用 for..in.. 语句可以直接对列表进行遍历循环,每次循环时,循环变量存储的都是当前遍历到的列表元素;

  • 使用 DictReader 可以读取 CSV 文件,主要方式是构建 reader 对象后,直接对 reader 对象用 for..in.. 循环来遍历;

  • 使用 DictWriter 可以写入 CSV 文件,writerrow 方法用于写入一行,writerows 方法用于写入一个列表(对应多行)。

学完了本讲,我们就已经完整了学习了通过 Python 来实现爬虫的网页下载→数据抽取→数据保存三大环节。是不是感觉爬虫技术也没那么难呢?

下一讲,我们将会以一个实战案例的形式,通过爬虫技术来构建一个数据集。

课后练习:

读取刚才保存的 news.csv,添加一列“分级信息”。

分级信息的标准是:前三条的分级信息为“推荐”,后面为“普通”,添加之后保存为新的 news1.csv。


答案如下:

关键点:

  • 将本讲学习的读取 csv 和写入 csv 的操作整理成两个函数,方便后续调用;

  • 遍历读取到的字典列表,为每个元素都增加分级信息的键值对;

  • 通过一个计算变量 i 来判断当前是第几次循环;

  • 用 if 语句根据变量i 的值来决定当前字典的分级信息是推荐还是普通。

# 整理写入 CSV 文件的代码为函数
# 输入参数为:要写入的字典列表、要写入的文件名、表头
def write_dict_list_to_CSV(dict_list, filename, headers):
    fo = open(filename, "w", newline='', encoding='utf_8_sig')
    writer = CSV.DictWriter(fo, headers)
    writer.writeheader()
    writer.writerows(dict_list)
    fo.close()
# 整理读取 CSV 的代码为函数
# 输入参数为 CSV 文件名,返回值为读取到的字典列表
def get_dict_list_from_CSV(filename):
    fo = open(filename, "r")
    reader = CSV.DictReader(fo)
    dict_list = []
    for item in reader:
        dict_list.append(item)
    return dict_list
# 获取 news.CSV 中的新闻数据,将字典列表存储在 news_list
news_list = get_dict_list_from_CSV("news.CSV")
# 表头
header = ["标题","发布时间""分级信息"]
# 遍历循环字典列表,并用变量 i 来统计当前是第几次循环
# 循环体重修改 news_list 中的每个字典,添加 分级信息的内容
i = 0
for item in news_list:
    # 前三次为推荐,后面为普通
    if i<=2:
        item["分级信息"] = "推荐"
    else:
        item["分级信息"] = "普通"
    # 每次循环结束后,变量i 的值+1
    i = i + 1
# 调用上面的函数,将新的字典列表写入 news1.CSV 文件中
write_dict_list_to_CSV(news_list, "news1.CSV", header)

执行后,用 Excel 打开新产生的 news1.csv,如下图所示。可以看到分级信息已经按照题目要求加上了。

图片6.png


  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

办公模板库 素材蛙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值