使用 Python 的 lxml 进行网页抓取:初学者教程
使用 Python 的 lxml 进行网页抓取的分步指南。
Python 拥有许多用于网页抓取的优秀库,lxml 就是其中之一。在本指南中,您将了解为什么应该选择 Python 的 lxml 库进行网页抓取,如何为您的项目做好准备,以及如何通过一个实际示例构建一个健壮的 lxml 抓取器。您还将找到一些通用技巧,以帮助您获得更高的请求成功率。
什么是在 Python 中使用 lxml 进行 Web 抓取?
使用 Python 的 lxml 库进行网页抓取,是指从已下载的 HTML 或 XML 代码中获取并整理数据的过程。简单来说,该工具会选择必要的数据点,并将其提取为可读格式。
Python 的 lxml 不是一个独立的库——它需要其他工具来构建抓取工具。要发出请求并下载页面的 HTML 代码,您需要使用 HTTP 客户端(如 Requests)。下载数据后,lxml 才能定义元素及其属性,并构造数据。
因此,本质上,使用 Python 中的 lxml 进行网络爬虫意味着在 HTTP 客户端获取数据后解析数据。
为什么选择 Python 的 lxml 进行网页抓取?
选择 lxml 有几个原因:
- 可扩展。 lxml 封装了两个 C 库 - libxml2 和 libxalt - 使解析器具有高度可扩展性。它包括速度、本机 Python API 的简单性和 XML 特性等功能。
- 指定 XML 结构。 该库支持三种模式语言,可帮助指定 XML 结构。此外,它还可以完全实现 XPath。它是一种有用的 网页抓取技术 用于识别 XML 文档中的元素。
- 允许遍历数据。 lxml 能够浏览不同的 XML 和 HTML 结构。该库可以遍历子元素、同级元素和其他元素,而 Beautiful Soup 等解析器则无法做到这一点。
- 轻资源。 lxml 的一个优点是它不占用太多内存。这使得该库运行速度很快,适合解析大量数据。
但 lxml 并非适用于所有解析任务。如果目标网页的 HTML 代码编写不佳或存在错误,该库很可能会解析失败。在这种情况下,它会回退到Beautiful Soup。
使用 Python 构建 lxml 解析器的步骤
步骤 1:选择合适的工具
要使用 lxml 构建功能齐全的抓取工具,您还需要其他 Python 依赖项。
首先,选择一个 HTTP 客户端来获取数据。您可以使用 Python 的库,如 Requests、HTTPX 或 aiohttp。选择哪个取决于您的偏好和您心中的网页抓取项目。如果您不熟悉任何一个,请选择 Requests - 它是最古老的一个,并且非常可定制。
如果你想更进一步,通过抓取动态网站(例如电商网站或社交媒体网站)来提升技能,那么仅仅使用 HTTP 客户端和解析器是不够的。你需要一个能够渲染 JavaScript 的工具。在这种情况下,可以使用像Selenium这样的无头浏览器库来处理异步和 AJAX 请求。
第 2 步:确定目标网页
现在您已准备好工具,可以识别目标网页。例如,您可能希望抓取 Google 搜索结果以监控您和竞争对手的 Google 排名,或者从亚马逊等电子商务商店收集产品信息。
我们提供了许多项目供您尝试——我们甚至专门准备了一份关于网络爬虫技巧的指南。此外,您也可以在模拟网站上练习——它们提供了掌握网络爬虫流程所需的所有要素。
步骤 3:查看网页抓取指南
如果你不了解一些网络爬虫的挑战,整个过程可能会变得非常棘手。从验证码提示到 IP 地址封禁,这些障碍都可能阻碍你的项目成功。因此,如果你认真对待你的项目,我建议你使用轮换代理服务器,它会根据需要更改你的 IP 地址和位置。
无论目标是什么(除了网络爬虫沙箱),您都应该知道网站通常会使用名为 robots.txt 的文件来管理机器人流量。它规定了您可以抓取哪些页面、哪些页面不能抓取,以及抓取的频率。总之,网络爬虫需要遵循这些以及其他准则——请花时间熟悉网络爬虫的最佳实践。
此外,浏览器发出的请求包含带有设备信息的标头。你为什么要关心?如果用户代理字符串(一个重要的标头)缺失或格式错误,页面可能不允许你的抓取工具获取数据。大多数 HTTP 客户端都会发送其用户代理标头,因此在抓取真实目标时你需要更改它。但别担心——我稍后会告诉你如何操作。
使用 Python 的 lxml 进行 Web 抓取:分步教程
在本分步教程中,我们将从 IMDb 抓取本月最受欢迎的电影(及其所有信息)。由于 Python 的 lxml 是一个 Web 解析库,我们还将使用 Requests 下载数据。
该平台提供了更多有趣的列表供抓取 - 但请记住检查 robots.txt 并遵守其他网络抓取指南!
硬件需求
- Python 3。 检查您的设备上是否安装了最新版本。否则,请从 Python.org 下载。
- 要求。 填写
pip install requests在您的操作系统终端中。 - lxml。 通过运行添加
pip install lxml. - 代码编辑器。 您可以选择任何代码编辑器。例如,Notepad++、Visual Studio Code 或操作系统的文本编辑器。
导入库
步骤 1.首先,让我们导入必要的库。
import requests
from lxml import etree
步骤 2.然后,定义你要抓取的 URL。
url = "https://www.imdb.com/chart/moviemeter/"
添加指纹(标题)
headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Referer": "https://www.google.com/",
"Sec-Ch-Ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"",
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": "\"Windows\"",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "cross-site",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
}
获取 HTML 内容
步骤 1.定义主要网络爬虫功能。
def request():
第二步,向 IMDb 提出请求,并附上您的指纹。
response = requests.get(url, headers=headers)
步骤三:检查状态码。查找 200——这表示请求成功。
print(response.status_code)
步骤 4.收到响应后,使用lxml 提供的etree.HTML()解析器解析 HTML 内容。
tree = etree.HTML(response.text)
提取电影数据
步骤 1.创建一个新列表来保存你要抓取的电影信息。
movies_list = []
步骤 2.要查找要抓取的元素,请在页面上的任意位置单击鼠标右键,然后选择“检查”。查找<ul>标签,从 HTML 中提取内容以及其中的所有列表项。
list_elem = tree.xpath(".//ul[contains(@class,'ipc-metadata-list')]/li")
让我们仔细看看代码:
- ul – 无序列表标签。
- li – 列表项。此选择器查找 包含一个元素 ipc 元数据列表t 类。您还需要 元件。
- 标签 – 合并选定的列表项并将结果分配给新变量。
- 列表元素 – 所有列表项对象的列表。
步骤 3:现在,您可以遍历列表中的所有项目。
for list_item in list_elem:
步骤 4.创建一个json对象来存储信息。
json = {}
第五步。现在,让我们获取电影信息排名和片名。
1)首先,获取电影信息排名。使用 XPath 查找类名为meter-const-ranking的<div>元素,并将其赋值给变量movie_rank_elem。
movie_rank_elem = list_item.xpath(".//div[contains(@class, 'meter-const-ranking')]")
2) movie_rank_elem本身也是一个 lxml 元素,因此我们可以使用xpath()函数来选择该元素内的内容。获取元素内的文本,由于该函数返回一个包含单个元素的列表,因此只需从列表中取出第一个元素并将其赋值给json字典即可。
json['rank_num'] = movie_rank_elem[0].xpath(".//text()")[0]
3) 接下来我们来提取标题。这里的区别在于,你需要选择一个class 为ipc-title__text的<h3>标签,并将文本直接赋值给字典中的title键。
json['title'] = list_item.xpath(".//h3[@class='ipc-title__text']/text()")[0]
步骤 6.现在,获取影片的发行日期、片长和内容分级信息。由于这些数据点没有特定的类名可以直接选择,因此流程略有不同。您需要选择具有相同标签和类名的元素,最终得到一个仅包含 1 到 3 个项目的列表。
movie_metadata_elems = list_item.xpath(".//span[contains(@class, 'cli-title-metadata-item')]")
注意:由于movie_metadata_elems列表包含 lxml 元素,因此您可以通过索引访问每个元素。
1)发布日期是第一项,因此其索引为 0,长度为 1,内容评级为 2。
注意:并非所有电影都有内容分级,因此请查看片长以确认是否有分级。通过索引访问每个元素后,使用 XPath 获取文本值并将其赋值给字典。
json['release_date'] = movie_metadata_elems[0].xpath(".//text()")[0]
json['length'] = movie_metadata_elems[1].xpath(".//text()")[0]
if len(movie_metadata_elems) > 2:
json['content_rating'] = movie_metadata_elems[2].xpath(".//text()")[0]
else:
json['content_rating']: None
2)要获取每部电影的评分和投票数,使用与之前类似的方法,选择每个元素并获取其中的文本。
注意:并非每部电影都包含这两个元素中的一个或两个。为了解决这个问题,请添加一个“if”语句,以便在将结果添加到字典之前先返回一个列表。
movie_rating_elem = list_item.xpath(".//span[contains(@class, 'ipc-rating-star')]/text()")
if movie_rating_elem:
json['rating'] = movie_rating_elem[0]
movie_vote_count_elem = list_item.xpath(".//span[contains(@class, 'ipc-rating-star--voteCount')]/text()")
if movie_vote_count_elem:
json['vote_count'] = movie_vote_count_elem[1]
json['url'] = "https://www.imdb.com/"+list_item.xpath(".//div[contains(@class, 'ipc-title')]/a/@href")[0]
步骤 7.然后,将json字典添加到movies_list列表中,让脚本继续解析下一个电影的列表项。
movies_list.append(json)
步骤 8.最后,打印输出结果。
print (movies_list)
if __name__ == "__main__":
request()
这是完整的代码:
import requests
from lxml import etree
url = "https://www.imdb.com/chart/moviemeter/"
headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Referer": "https://www.google.com/",
"Sec-Ch-Ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"",
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": "\"Windows\"",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "cross-site",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
}
def request():
response = requests.get(url, headers=headers)
print(response.status_code)
tree = etree.HTML(response.text)
movies_list = []
list_elem = tree.xpath(".//ul[contains(@class,'ipc-metadata-list')]/li")
for list_item in list_elem:
json = {}
movie_rank_elem = list_item.xpath(".//div[contains(@class, 'meter-const-ranking')]")
json['rank_num'] = movie_rank_elem[0].xpath(".//text()")[0]
json['title'] = list_item.xpath(".//h3[@class='ipc-title__text']/text()")[0]
movie_metadata_elems = list_item.xpath(".//span[contains(@class, 'cli-title-metadata-item')]")
json['release_date'] = movie_metadata_elems[0].xpath(".//text()")[0]
json['length'] = movie_metadata_elems[1].xpath(".//text()")[0]
if len(movie_metadata_elems) > 2:
json['content_rating'] = movie_metadata_elems[2].xpath(".//text()")[0]
else:
json['content_rating'] = None
movie_rating_elem = list_item.xpath(".//span[contains(@class, 'ipc-rating-star')]/text()")
if movie_rating_elem:
json['rating'] = movie_rating_elem[0]
movie_vote_count_elem = list_item.xpath(".//span[contains(@class, 'ipc-rating-star--voteCount')]/text()")
if movie_vote_count_elem:
json['vote_count'] = movie_vote_count_elem[1]
json['url'] = "https://www.imdb.com/" + list_item.xpath(".//div[contains(@class, 'ipc-title')]/a/@href")[0]
movies_list.append(json)
print(movies_list)
if __name__ == "__main__":
request()