快速入门 Python 爬虫:BeautifulSoup 和 Selenium | Jim Zhang's blog
快速入门 Python 爬虫:BeautifulSoup 和 Selenium2023-09-21

这篇文章本来是 Jim 在 2022 年 9 月份面试清华大学研究生会清研学术讲师团时写的稿子,主要围绕 Jim 设计的一套《Python 数据分析》课程其中的《网络数据获取技术》这一讲进行的讲解。但是经历了一年的课程讲授,以及后来与同学们的答疑讨论,Jim 越来越觉得这篇文章应该重写,重写的目的是为新手从 HTTP 底层到 Python 实践全链条地讲解 Python 爬虫。

你看到的网页是以什么方式呈现的?

我们举个例子,假如我们在 Chrome 浏览器的地址栏内键入 https://www.baidu.com,右键点击检查(快捷键 Ctrl+Shift+I for Windows, Command+Shift+I for MacOS,对于 Safari 浏览器,检查/审查元素需要在偏好设置里开启),会出现一个让人不明所以的窗格,里面有几个标签页,比如 Network, Resources 等,其实每个标签页都指的是不同内容,比如 Network 标签页指的是网页瀑布,Resources 指的是网页加载附带的文件(如 JavaScript 脚本、CSS 样式表以及图片、字体等),而我们看到的网页,其实包含的内容在 Elements(元素)页。

点开 Elements 页,我们能看到一大堆被 <balabala></balabala> 的文本,这是以 HTML(Hypertext Markup Language,超文本标记语言)的形式呈现的。

而在右边,

HTML 简述

我们前面已经提到,网页内容是以 HTML 代码来储存的,那么我们不了解 HTML 代码的基本格式是无法来进行网络数据获取的。所以接下来我们来看一下 HTML 代码/文件的基本架构。

(去图书馆找个 HTML 书,对着写一番)

举个例子:

<!--玩原神玩的.html-->
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
        <title>原神,启动!</title>
    </head>
    <body>
        <h1>上海米哈游天命科技有限公司</h1>
        <p>你说得对,但是《原神》是由米哈游自主研发的一款全新开放世界冒险游戏。游戏发生在一个被称作【提瓦特】的幻想世界,在这里,被神选中的人将被授予【神之眼】,导引元素之力。你将扮演一位名为【旅行者】的神秘角色,在自由的旅行中邂逅性格各异、能力独特的同伴们,和他们一起击败强敌,找回失散的亲人——同时逐步发掘【原神】的真相。。。</p>
    </body>
    <script>
        console.log("卧石山里灵活的狗!");
    </script>
</html>

HTTP 请求

HTTP(Hypertext Transfer Protocol,超文本传输协议)这四个字母大家肯定都见过。只能说,每天见不到这四个字母的只能是根本不上网的人。(因为地址栏里肯定不是 http:// 就是 https:// 啊!)

(TCP/IP 和 UDP)

HTTP 请求有以下七种,常见的请求有 GETPOST 两种。

方法描述是否包含请求体
GET请求指定的页面信息,并返回实体主体。
HEAD类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头
POST向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。
PUT从客户端向服务器传送的数据取代指定的文档的内容。
TRACE对可能经过代理服务器传送到服务器的报文进行追踪。
OPTION决定可以支持的请求方法,或者对服务器的某些资源是否可用。
DELETE请求服务器删除指定的页面。

(HTTP 状态码)

(俺的 nginx 日志)

安装

BeautifulSoup 得名于《爱丽丝梦游仙境》第十章:

Beautiful Soup, so rich and green,
Waiting in a hot tureen!
Who for such dainties would not stoop?
Soup of the evening, beautiful Soup!
Soup of the evening, beautiful Soup!
...
Chapter 10, Alice's Adventures in Wonderland, Lewis Carroll.

意为“化腐朽为神奇”。BeautifulSoupSelenium 的安装很方便,只需要一行命令:

$ pip install BeautifulSoup4 selenium

请注意,不是 pip install BeautifulSoup,而是 pip install BeautifulSoup4,因为前者指的是 BeautifulSoup3

安装完成后,我们可以通过 python 交互式控制台来检验是否成功安装,比如:

Python 3.10.7 (tags/v3.10.7:6cc6b13, Sep  5 2022, 14:08:36) [MSC v.1933 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from bs4 import BeautifulSoup, import selenium
>>>

如果没有报错信息,就代表安装成功啦。

BeautifulSoup

本地 html 文件

首先,我们可以先用一个本地 html 文件做测试,这个文件内容如下:

<!--test.html-->
<html>
    <head>
        <title>这是测试。</title>
    </head>
    <body>
        <h1>Test</h1>
        <p>这是一个测试。</p>
        <p>这是第二个测试。</p>
    </body>
</html>

它对应的 DOM(Document Object Model,文档对象模型)树如下:

Document
└── html
    ├── head
    │   └── title
    └── body
        ├── h1
        ├── p
        └── p

这个树形结构为我们讲述了如下信息:

  • Document 是整个文档的根节点,它的子节点是 html
  • html 是文档的根元素,它的子节点是 headbody 等。
  • head 是文档的头部,它的子节点是 title 等。
  • body 是文档的主体,它的子节点是 h1p 等。
  • titleh1p 等都是文档的叶子节点,它们没有子节点。

然后,我们可以通过 BeautifulSoup 来读取这个文件,代码如下:

from bs4 import BeautifulSoup

with open('test.html', 'r', encoding='utf-8') as f:
    soup = BeautifulSoup(f, 'html.parser')

print(soup.prettify())

输出结果如下:

<html>
 <head>
  <title>
   这是测试。
  </title>
 </head>
 <body>
  <h1>
   Test
  </h1>
  <p>
   这是一个测试。
  </p>
  <p>
   这是第二个测试。
  </p>
 </body>
</html>

这样,我们就可以通过 soup 来访问这个 html 文件了。进一步地,我们可以通过 soup 来访问它的子节点,比如:

print(soup.html.head.title)

输出结果如下:

<title>这是测试。</title>

我们可以看到,soup.html.head.title 就是 title 节点,对于整个文档唯一的节点(title 就是这样),我们可以通过 soup.title 来直接访问它。但是对于非唯一的节点就比较麻烦,比如第一个 p 节点,我们可以通过 soup.body.p 来访问它,但是如果我们想要访问第二个 p 节点,就没办法了。这时候,我们可以通过 find_all 方法来访问它们,比如:

print(soup.find_all('p'))

输出结果如下:

[<p>这是一个测试。</p>, <p>这是第二个测试。</p>]

获取标签中的内容,对每个 tag 使用 text 属性即可,比如:

print(soup.title.text)

输出结果如下:

这是测试。

网页

接下来,我们来看看如何使用 BeautifulSoup 来爬取网页。首先,为了利用 python 帮我们完成发送请求的操作,我们需要导入 requests 库(安装方法我们就不重复介绍了),然后通过 requests 来获取网页的内容。

现在有这样一个网页:https://q-weather.info/weather/54399/history/?date=2022-09-16,其内容大致如下

存储了海淀区的天气信息。现在想获取这个网页数据,我们如下操作:

import requests, pandas as pd
from bs4 import BeautifulSoup

url = 'https://q-weather.info/weather/54399/history/'
params = {'date': '2022-09-16'}

r = requests.get(url, params=params)
soup = BeautifulSoup(r.text, 'html.parser')

这样这个 soup 就存储这个网页的内容了。接下来的任务显而易见:首先获取表格的标题行,然后是数据单元格,最后是把他们存储起来。

table = soup.table
columns = [th.text for th in table.thead.find_all('th')]
data = [[td.text for td in tr.find_all('td')] for tr in table.tbody.find_all('tr')]

df = pd.DataFrame(data, columns=columns)
print(df.head)

输出结果如下:

                        时次  瞬时温度    地面气压 相对湿度     瞬时风向 瞬时风速 1小时极大风速 1小时降水 10分钟平均能见度
0   2022-09-16 01:00 +0800  20.1  1002.5   87           0.0     0.4   0.0       9.3
1   2022-09-16 02:00 +0800  19.5  1002.1   91    45/NE  0.5     0.5   0.0       5.4
2   2022-09-16 03:00 +0800  19.2  1001.9   91  290/WNW  1.4     1.6   0.0       8.1
3   2022-09-16 04:00 +0800  19.6  1001.7   92   25/NNE  0.3     2.1   0.0       4.9
4   2022-09-16 05:00 +0800  18.9  1001.7   93   28/NNE  0.4     1.0   0.0       4.0

这就是 BeautifulSoup 的基本用法了,更多的用法可以参考官方文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc/

Selenium

Selenium 是一个自动化测试工具,它可以模拟浏览器的操作,比如点击、输入、滚动等等。它的主要功能是自动化测试,但是它也可以用来爬取网页。它的优点是可以模拟浏览器的操作,可以模拟人的操作,比如滚动、点击等等,这样就可以爬取一些动态加载的网页了。

安装

前文已经提到了 Selenium 的安装方法,这里就不再重复介绍了。但是除了安装 Selenium 之外,我们还需要安装一个浏览器驱动,这个驱动的作用是让 Selenium 可以控制浏览器。这里我们以 Chrome 浏览器为例,安装 Chrome 浏览器驱动。(Edge 浏览器的话需要额外安装 msedge-selenium-tools

Windows

https://sites.google.com/chromium.org/driver/ 下载对应版本的 Chrome 浏览器驱动,并将其目录添加到 PATH 环境变量中即可。

Mac OS & Linux

在 Mac OS 和 Linux 上安装 Chrome 浏览器驱动比较简单,利用包管理器安装就可以了。

# Mac OS
brew install chromedriver
# Linux (debian-like)
sudo apt-get install chromedriver

使用

Selenium 的使用方法很简单,首先导入 Seleniumwebdriver 模块,然后创建一个 webdriver 对象,这个对象就是浏览器的控制器,然后就可以通过这个对象来控制浏览器了。

from selenium import webdriver

driver = webdriver.Chrome()

注意,这里请确保你的 chromedriver 的版本和你的 Chrome 浏览器的版本是一致的,且 chromedriver 要在 PATH 环境变量中(否则要指定路径)。

然后就是访问网页了,这里我们还是以前面的例子为例,访问 https://q-weather.info/weather/54399/history/?date=2022-09-16

driver.get('https://q-weather.info/weather/54399/history/?date=2022-09-16')

chromedriver 会自动打开一个 Chrome 浏览器窗口,然后访问指定的网页。

Selenium

然后我们就可以通过 Selenium 来获取网页存储的数据了。这里我们可以通过 find_element_by_id 方法来获取指定 id 的元素,然后通过 text 属性来获取元素的文本内容。