前文介绍了爬虫的原理,以及实现爬虫的三个主要步骤:下载网页-分析网页-保存数据。
本文从第一步开始实操,介绍如何使用 Python 下载网页。
网页是什么
前文介绍了浏览器绘制网页的流程:浏览器将用户输入的网址告知网站的服务器,服务器将网址对应的网页返回给浏览器,由浏览器将网页绘制出来。
这里所说的网页,一般都是一个后缀名为 html 的文件。
网页文件与日常接触的其他文件并无本质区别。Word 文件后缀名为 .doc,通过 Word 打开;图片文件后缀名为 .jpg,通过 Photoshop 打开;而网页则是后缀名为 .html,通过浏览器打开的文件。
网页文件本质上也是一种文本文件。为了让文字和图片呈现各种各样不同的样式,网页文件通过一种名为 HTML 语法的标记规则对原始文本进行标记。
手动下载网页
以煎蛋网为例体会网页的实质。使用浏览器打开链接http://jandan.net/可以看到如下界面。第一条新闻的标题前缀是:大吐槽。网页内容可能会随时间变化,只需注意第一条新闻的前几个字(暗号)即可,下同。

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

保存后回到桌面,可以看到网页已被保存,后缀名是 html,这就是所说的网页文件。
网页内容初探
右键刚下载的文件,选择用 VS Code 打开,打开后的文件内容如下图所示。

这就是网页文件的实际内容(未被浏览器绘制出来之前)。先不用管暂不理解的代码,注意到第一条新闻的内容:“大吐槽………………”。(暗号)
在 VS Code 中通过 CTRL + F 调出搜索面板,搜索“大吐槽”(暗号)。可以看到成功找到了这条新闻。虽然它被很多不认识的代码包围,但这可以确定,煎蛋网的主页确实就是这个 html 文件。

html 文件中有文字,也有很多暂时不理解的标记和代码,浏览器正是通过这些标记将文本绘制成丰富多彩的网页。
确认 html 文件中包含所需信息后,接下来学习如何使用 Python 代码来下载 html 文件。
如何实现下载普通网页
Python 以系统类的形式提供了下载网页的功能,位于 urllib3 模块中。该模块包含较多类,无需逐一掌握,只需记住主要的用法即可。
(1)获取网页内容
仍以煎蛋网为例,首页的网址是:http://jandan.net/。本文写作时,排在第一的新闻是:“引发普通感冒的鼻病毒会将新冠病毒排挤出细胞!”。
煎蛋网会更新新闻(如下图),需记住当时的第一条新闻题目。稍后会在下载的网页中搜索该标题,以验证下载结果的正确性。

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

打开 VS Code,选择文件 -> 打开文件夹,选择刚才新建的 chapter07 文件夹(建文件夹是为了方便查看下载的文件)。
打开文件夹后,按 CTRL + P 调出命令菜单,输入 >Jup,在命令菜单中选择:Create New Blank Notebook,如下图所示。

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

保存完毕后如图所示:

在默认创建的 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 变量中保存的就是下载的网页内容。

(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 代码保存的文件。
打开即可看到熟悉的网页内容,如下图所示。

接下来验证下载的网页是否与浏览器中看到的一致。回顾第一条新闻的标题——“引发普通感冒的鼻病毒会将新冠病毒排挤出细胞”(暗号)。
在 jiandan.html 文件中搜索这句话,可以成功搜索到,这说明该文件就是浏览器中看到的网页内容。

至此,已通过代码完成了网页下载功能。回顾整个流程,代码量并不大,这体现了 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

下面使用刚才定义的两个函数来下载这个网页。
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 文件,说明网页已经下载成功。如下图所示:

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

虽然下载到了网页,但搜不到电视剧的原因在于:豆瓣电视剧网页中的电视剧列表部分是动态加载的,用 urllib3 直接下载,只能下载到网页外壳,不包含其中的列表内容。这种网页内部数据动态加载的网页,统称为动态网页。
动态网页应该如何抓取?一个网页无论多么动态,最终都要展示给用户,所以浏览器最了解网页的内容。如果使用代码控制浏览器来下载网页,就可以解决动态网页的抓取问题。
下面介绍使用 Python 控制浏览器的利器:selenium。
安装 selenium
selenium 不属于 Python 的系统库,要使用这个库需要先安装。
安装 Python 库一般通过 Anaconda 的命令行。既然是模拟浏览器,电脑需要先装有浏览器,这里以 Chrome 为例。因此在开始之前,需确保电脑上安装了 Chrome。
在课前准备环节已安装 Anaconda 套件,去开始菜单(或在桌面状态下按 Win 键)找到 Anaconda 3 文件夹,点击文件夹中的 Anaconda Prompt 程序。Mac 用终端即可。

启动之后会出现一个命令行窗口,这就是 Anaconda 的命令行。

在命令行输入 conda install xxx 来安装 Python 的扩展库。
例如,输入 conda install selenium,回车。界面会询问是否确认安装,输入 y 回车即可。

安装完毕后命令行窗口会回到待输入命令的状态,此时可以关闭。
回到 chapter07.ipynb,新建 Cell,输入以下的测试代码:
# 从 selenium 库中导入 webdriver 类
from selenium import webdriver
# 创建一个 Chrome 浏览器的对象
brow = webdriver.Chrome()
# 使用 Chrome 对象打开 url(就是刚才豆瓣电视剧的 url)
brow.get(url)
运行代码,会自动打开一个 Chrome 浏览器的窗口,并展示 url 对应的网页。同时会有一个提示,说明这个浏览器窗口正在被程序控制,如下图所示。

如果代码运行出错,提示找不到 chromedriver,说明安装的 selenium 版本缺少 chromedriver,可以按如下方式操作:
- 重新按照刚才的方法打开 Anaconda Prompt,输入 pip install --upgrade --force-reinstall chromedriver-binary-auto 回车执行安装。
- 在上面的代码增加一行 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 也被成功创建。

此时去这个文件搜索“山河令”,会发现已经有结果了,在这个 html 文件中已经包含了所有电视剧的信息。

至此,实现了对动态内容网页的下载功能。
小结
本文首先对网页做了初步介绍,说明网页是一种被 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(1, 6):
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)
版本差异(爬虫技术栈 → 当前版本)
| 库 | 本文编写时 | 当前稳定版 |
|---|---|---|
requests | 2.28/2.31 | 2.32.x |
Scrapy | 1.x/2.0 | 2.11.x(API 稳定) |
httpx | 0.24 | 0.28.x |
Playwright | 1.3x | 1.6x(Python 版) |
lxml/BeautifulSoup | 旧版 | 保持稳定 |
| Python | 3.8-3.12 | 3.14(推荐) |
本文讲解的爬虫原理(HTTP、解析、反爬、存储)与核心 API 在最新版本中成立;注意 Python 3.9 及以下已 EOL,新项目使用 3.13/3.14。