in

Python爬虫入门教程:一学就会!

Python爬虫的入门教程

开始进行网络爬虫其实并不难,除非你遇到了问题,这可能正是你来到这里的原因。Python 是入门最简单的方法之一,因为它是一种面向对象的语言,类和对象的使用比其他语言要容易得多。而且,Python 有很多库可以帮助你轻松构建网络爬虫工具。

在这篇 Python 网络爬虫教程中,我们将介绍从零开始构建一个简单应用所需的一切。你将学到:

  • 如何准备一个用于网络爬虫的 Python 环境;
  • 如何使用 Python 库,如 requests、Beautiful Soup、lxml、Selenium 和 pandas;
  • 如何打开开发者工具,找到包含所需数据的 HTML 元素;
  • 如何将爬取的数据保存到 CSV 和 Excel 文件中;
  • 其他更多高级网络爬虫的选项。

通过按照本教程中的步骤操作,你将能掌握如何进行网络爬虫。不过,为了节省构建自定义爬虫的时间和精力,我们还提供了免维护的网络情报解决方案,比如我们的通用 Web Scraper API,欢迎试用我们的一周免费试用版。

此Python网页抓取教程适用于所有操作系统。在安装Python或开发环境时会有细微的差异,但在其他任何环境中都没有。


构建网络爬虫:Python 准备工作

在整个网络爬虫教程中,我们将使用 Python 3.4 及以上版本。具体来说,我们使用的是 3.12.0 版本,但任何 3.4 以上的版本都应该可以正常工作。

对于 Windows 安装,在安装 Python 时,请确保勾选“PATH installation”。添加到 PATH 会将可执行文件添加到默认的 Windows 命令提示符的可执行文件搜索路径中。这样,Windows 就会识别 pip 或 python 等命令,而不需要用户指向可执行文件的目录(例如,C:/tools/python/…/python.exe)。如果你已经安装了 Python 但没有勾选该选项,只需重新运行安装程序并选择修改。在第二个屏幕上,选择“Add to environment variables”。


了解库的使用

由于有许多实用的库,用 Python 进行网络爬虫变得非常简单。

Python 的一个优势在于它有大量用于网络爬虫的库。这些网络爬虫库是成千上万个 Python 项目的一部分——仅在 PyPI 上就有超过 300,000 个项目。值得注意的是,有几种类型的 Python 网络爬虫库可供选择:

  • Requests
  • Beautiful Soup
  • lxml
  • Selenium

以下我们以Oxylabs代理为例来进行介绍

Requests 库

网络爬虫首先需要向网站的服务器发送 HTTP 请求,例如 POST 或 GET 请求,服务器会返回包含所需数据的响应。然而,标准的 Python HTTP 库使用起来较为复杂,并且为了提高效率,需要编写大量代码,这使得问题更加复杂。

与其他 HTTP 库不同,Requests 库简化了发送此类请求的过程,通过减少代码行数,使代码更易于理解和调试,同时不影响其效率。可以在终端中使用 pip 命令安装该库:

python -m pip install requests

根据你的 Python 设置,你可能需要运行以下命令:

python3 -m pip install requests

请求库提供了发送 HTTP GETPOST请求的简便方法。例如,发送 HTTP GET请求的函数恰如其分地命名为get()

import requests
response = requests.get('https://oxylabs.io/')
print(response.text)

如果需要发布表单,可以使用post()方法轻松完成。表单数据可以作为字典发送,如下所示:

form_data = {'key1': 'value1', 'key2': 'value2'}
response = requests.post('https://httpbin.org/post', data=form_data)
print(response.text)

总的来说,代理与请求库的集成使得使用需要身份验证的代理变得非常容易:

proxies={
    'http': 'http://USERNAME:[email protected]:7777',
    'https': 'http://USERNAME:[email protected]:7777',
}
response = requests.get('https://ip.oxylabs.io/location', proxies=proxies)
print(response.text)

但是,这个库有一个限制,它不解析提取的 HTML 数据,即它无法将数据转换为更易读的格式以供分析。此外,它不能用于抓取使用 JavaScript 加载内容的网站。

Beautiful Soup

Beautiful Soup 是一个 Python 库,它与解析器配合使用从 HTML 中提取数据,甚至可以将无效标记转换为解析树。但是,此库仅用于解析,无法以 HTML 文档/文件的形式从 Web 服务器请求数据。因此,它主要与 Python 请求库一起使用。请注意,Beautiful Soup 可以轻松查询和浏览 HTML,但仍然需要解析器。以下示例演示了如何使用html.parser模块,它是 Python 标准库的一部分。打开终端并运行此行来安装该库:

pip install beautifulsoup4

第 1 部分– 使用请求获取 HTML

import requests
url = 'https://oxylabs.io/blog'
response = requests.get(url)

第 2 部分– 查找元素

import requests
from bs4 import BeautifulSoup

url = 'https://oxylabs.io/blog'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
print(soup.title)

这将打印标题元素如下:

<title>Oxylabs Blog | Oxylabs</title>

由于 Beautiful Soup 的导航、搜索和修改解析树的方式简单,即使是初学者也可以轻松上手,并且通常可以为开发人员节省大量工作时间。例如,要打印此页面上的所有博客标题,可以使用find_all()方法。

你可能需要使用开发人员工具,这是 Web 浏览器的内置功能,可让你查看页面的 HTML 并为 Web 开发人员提供其他功能。通过导航到浏览器设置打开开发人员工具或使用键盘快捷键:在 Windows 上,按F12Shift + Ctrl + I,在 macOS 上,按Option + ⌘ + I

然后按下元素选择器按钮,该按钮位于开发者工具的左上角。或者,你可以在 Windows 上按Shift + Ctrl + C ,在 macOS 上按 Shift + ⌘ + C。现在,使用元素选择器在页面上选择博客文章标题,你应该会看到开发者工具在 HTML 源代码中突出显示此元素:

标题元素

查看此代码片段,你可以看到博客文章标题存储在<a>标签内,其属性设置为oxy-rmqaiqe1dscegp1

注意:由于网站使用动态渲染,你可能看不到设置为css-rmqaiqe1dscegp1 的类。如果收到空响应,最好在 Python 中打印整个 HTML 文档并仔细检查元素和属性。

进一步查看,你应该看到所有其他标题都以完全相同的方式存储。由于整个 HTML 文档中没有其他具有相同类值的元素,因此你可以使用值e1dscegp1来选择存储博客标题的所有元素。可以按如下方式将此信息提供给find_all函数:

blog_titles = soup.find_all('a', class_='e1dscegp1')
for title in blog_titles:
    print(title.text)
# Output:
# Prints all blog tiles on the page

请注意,必须使用 class_ 关键字(带下划线)来设置类名。否则,你会收到错误。

你也可以在 find_all() 方法中使用正则表达式(regex)。例如,下面的代码会抓取所有博客文章的标题,如前所示:

import re
# Send a request and pass the response to Beautiful Soup just like before

blog_titles = soup.find_all('a', class_=re.compile('oxy-rmqaiq'))
for title in blog_titles:
    print(title.text)

当你需要更灵活和精确地查找元素时,这非常有价值。

BeautifulSoup 还使得使用 CSS 选择器变得很容易。如果开发者已经知道 CSS 选择器,那么就不需要学习 find() 或 find_all() 方法了。下面的示例使用了 soup.select 方法:

blog_titles = soup.select('a.e1dscegp1')
for title in blog_titles:
    print(title.text)

虽然损坏的 HTML 解析是该库的主要功能之一,但它还提供了许多功能,包括可以检测页面编码,从而进一步提高从 HTML 文件中提取的数据的准确性。

此外,只需几行代码,就可以轻松配置它以提取任何自定义的公开可用数据或识别特定数据类型。

lxml

lxml 是一个快速、强大且易于使用的解析库,可处理 HTML 和 XML 文件。此外,lxml 非常适合从大型数据集中提取数据。然而,与 Beautiful Soup 不同,这个库会受到设计不良的 HTML 的影响,从而导致其解析能力受到阻碍。

可以使用 pip 命令在终端中安装 lxml 库:

pip install lxml

该库包含一个用于处理 HTML 的 html 模块。然而,lxml 库首先需要 HTML 字符串。这个 HTML 字符串可以使用前面提到的 requests 库来获取。一旦获取到 HTML 后,可以使用 fromstring 方法来构建树结构,如下所示:

import requests
from lxml import html

url = 'https://oxylabs.io/blog'
response = requests.get(url)

tree = html.fromstring(response.text)

现在可以使用 XPath 来查询这个树对象。继续前面讨论的示例,获取博客标题的 XPath 表达式如下:

//a[contains(@class, "e1dscegp1")]

contains() 函数只选择 class 值为 e1dscegp1 的 <a> 元素。这个 XPath 表达式可以传递给 tree.xpath() 函数。该函数将返回所有与此 XPath 匹配的元素:

blog_titles = tree.xpath('//a[contains(@class, "e1dscegp1")]')
for title in blog_titles:
    print(title.text)

Selenium

如前所述,一些网站使用 JavaScript 编写,这种语言允许开发者动态填充字段和菜单。这对只能从静态网页抓取数据的 Python 库来说是个问题。事实上,如前所述,Requests 库在处理 JavaScript 时并不可行。这就是 Selenium 网络爬虫大显身手的地方。

这个 Python 网络库是一个开源的浏览器自动化工具(Web 驱动),允许你自动化处理诸如登录社交媒体平台等过程。Selenium 广泛用于在 Web 应用程序上执行测试用例或测试脚本。它在网络爬虫中的优势在于能够像任何浏览器一样渲染网页,通过运行 JavaScript——标准的网络爬虫无法运行这种编程语言。然而,它现在被开发者广泛使用。此外,Selenium 可以帮助绕过 CAPTCHA 测试,因为网络请求是通过真实的 Web 浏览器发出的,使爬虫看起来更像是真人访客。

Selenium 需要三个组件:

  • Web 浏览器——支持的浏览器有 Chrome、Edge、Firefox 和 Safari;
  • 浏览器驱动——从 Selenium 4.6 开始,驱动会自动安装。然而,如果你遇到任何问题,请参阅此页面获取驱动链接;
  • Selenium 包。

可以在终端中安装 Selenium 包:

pip install selenium

现在,可以使用 get() 方法在浏览器中加载任何页面

driver.get('https://oxylabs.io/blog')

Selenium 允许使用 CSS 选择器和 XPath 来提取元素。以下示例使用 CSS 选择器打印所有博客标题:

blog_titles = driver.find_elements(By.CSS_SELECTOR, 'a.e1dscegp1')
for title in blog_titles:
    print(title.text)
driver.quit()  # closing the browser

基本上,通过运行 JavaScript,Selenium 可以处理动态显示的任何内容,随后使网页内容可供内置方法甚至 Beautiful Soup 解析。此外,它还可以模仿人类行为。

在网页抓取中使用 Selenium 的唯一缺点是它会减慢处理速度,因为它必须先执行每个页面的 JavaScript 代码,然后才能解析。因此,它不适合大规模数据提取。但如果你希望以较小的规模提取数据,或者速度不够快不是缺点,Selenium 是一个不错的选择。

网页抓取 Python 库比较

 

Requests Beautiful Soup lxml Selenium
用途 简化 HTTP 请求 解析 解析 简化 HTTP 请求
易用性 中等 中等
速度 非常快
学习曲线 非常简单(适合初学者) 非常简单(适合初学者) 简单的 简单的
文档 出色的 出色的 好的 好的
JavaScript 支持 不支持 不支持 不支持 支持
CPU 和内存使用
支持的网页抓取项目规模 大和小 大和小 大和小 小项目

对于本次 Python 网络爬虫教程,我们将使用三个重要的库——BeautifulSoup v4、Pandas 和 Selenium。本指南的后续步骤假设这些库已经成功安装。如果你收到“NameError: name * is not defined”错误,可能是其中一个库没有成功安装。


WebDrivers 和浏览器

每个网络爬虫,无论是通用爬虫还是搜索引擎结果页爬虫,都需要使用浏览器来连接到目标 URL。出于测试目的,我们强烈建议使用常规浏览器(而不是无头浏览器),特别是对于新手来说。看到编写的代码如何与应用程序交互,可以更容易地进行故障排除和调试,并且更好地理解整个过程。

无头浏览器可以在以后使用,因为它们在处理复杂任务时更高效。在整个网络爬虫教程中,我们将使用 Chrome 浏览器,尽管整个过程对于 Firefox 是相同的。


为我们的 Python 网络爬虫找到一个舒适的地方

在进入本次网络爬虫教程的编程部分之前,还有最后一步:使用一个好的编码环境。有很多选项,从一个简单的文本编辑器,只需创建一个 *.py 文件并直接编写代码,到一个功能齐全的 IDE(集成开发环境)。

如果你已经安装了 Visual Studio Code,选择这个 IDE 是最简单的选项。否则,我们强烈推荐新手使用 PyCharm,因为它的入门门槛非常低,并且界面直观。我们将假设在本次网络爬虫教程的其余部分中使用 PyCharm。

在 PyCharm 中,右键点击项目区域,然后选择 New > Python File。给它起个好名字!


导入和使用库

让我们使用pandas库将数据导出到文件中。在终端中,运行以下命令:

pip install pandas pyarrow

现在是时候将你安装的所有模块投入使用了:

import pandas as pd
from bs4 import BeautifulSoup
from selenium import webdriver

PyCharm 可能会将这些导入显示为灰色,因为它会自动标记未使用的库。不要接受其删除未使用的库的建议(至少现在不要)。

首先定义你的浏览器。根据你选择的 webdriver,你应该输入以下内容:

driver = webdriver.Chrome()

OR

driver = webdriver.Firefox()

选择一个 URL

在执行第一次测试运行之前,请选择一个 URL。由于本网页抓取教程旨在创建一个基本应用程序,因此我们强烈建议选择一个简单的目标 URL:

  • 避免将数据隐藏在 JavaScript 元素中。有时需要通过执行特定操作来触发这些操作,才能显示所需的数据。从 JavaScript 元素中抓取数据需要更复杂地使用 Python 及其逻辑。

  • 避免图像抓取。可以使用 Selenium 直接下载图像。

  • 在进行任何抓取活动之前,请确保你正在抓取公共数据,并且不会侵犯第三方权利。此外,不要忘记检查 robots.txt 文件以获取指导。

选择要访问的登录页面,并将 URL 输入driver.get(‘URL')参数。Selenium 要求提供连接协议。因此,始终需要在 URL 后附加“http://”或“https://”。

driver.get('https://sandbox.oxylabs.io/products')

在这里,我们使用Oxylabs' scraping sandbox 作为电子商务目标网站,我们将在以下步骤中使用它。尝试通过单击左下角的绿色箭头或右键单击编码环境并选择运行来执行测试运行。


定义对象并构建列表

Python 允许程序员设计对象而不指定确切的类型。只需输入其标题并分配一个值即可创建对象:

# Object is “results”, brackets make the object an empty list.
# We will be storing our data here.
results = []

Python 中的列表是有序且可变的,并且允许重复成员。可以使用其他集合(例如集合或字典)​​,但列表最容易使用。是时候创建更多对象了!

# Add the page source to the variable `content`.
content = driver.page_source
# Load the contents of the page, its source, into BeautifulSoup 
# class, which analyzes the HTML as a nested data structure and allows to select
# its elements by using various selectors.
soup = BeautifulSoup(content, 'html.parser')

在继续之前,让我们回顾一下到目前为止你的代码应该是什么样子:

import pandas as pd
from bs4 import BeautifulSoup
from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://sandbox.oxylabs.io/products')
results = []
content = driver.page_source
soup = BeautifulSoup(content, 'html.parser')

尝试再次运行该应用程序。应该不会显示任何错误。如果出现任何错误,前面的章节中概述了一些可能的故障排除选项。


使用 Python 网络爬虫提取数据

从 HTML 文件中提取数据是个有趣又困难的部分。由于在几乎所有情况下,你都是从页面的许多不同部分中提取小部分,目标是将数据存储到列表中,因此你应该处理每个较小的部分,然后将其添加到列表中:

# Loop over all elements returned by the `findAll` call. It has the filter `attrs` given
# to it in order to limit the data returned to those elements with a given class only.
for element in soup.findAll(attrs={'class': 'list-item'}):
    ...

`soup.find_all` 接受多种参数。在本教程中,我们只使用 `attrs`(属性)。它允许你通过设置一个条件来缩小搜索范围,“如果属性等于 X 为真,那么……”。类名(classes)很容易找到和使用,因此你应该使用它们。

在继续之前,让我们在真实浏览器中访问选定的 URL。使用 CTRL + U(Chrome)打开页面源代码,或者右键点击并选择“查看页面源代码”。找到数据嵌套的“最近”类名。另一种选择是打开开发者工具,如前所述,选择元素。例如,抓取沙盒网站的产品标题是这样嵌套的:

scraping sandbox website

整个列表的属性类将是product-card,而产品 标题则用<h4> HTML 标签包裹。如果你选择了一个简单的目标,在大多数情况下,数据将以与上述示例类似的方式嵌套。复杂的目标可能需要更多努力才能获取数据。让我们回到编码并添加在源代码中找到的类:

# Change ‘list-item’ to ‘product-card’.
for element in soup.findAll(attrs={'class': 'product-card'}):

循环现在将遍历页面源中带有product-card类的所有对象。我们将分别处理它们:

name = element.find('h4')

让我们看看循环是如何遍历 HTML 的。循环中的第一个语句找到所有 class 属性包含 `product-card` 的标签。然后在这个类中执行另一次搜索,找到文档中的所有 `<h4>` 标签。最后,将对象赋值给变量 `name`。

你可以将对象 `name` 分配给之前创建的列表数组 `results`,但这样做会将整个 `<h4 class=”title css-7u5e79 eag3qlw7″>The Legend of Zelda: Ocarina of Time</h4>` 元素及其内部的文本一起带过来。在大多数情况下,你只需要文本本身,而不需要任何额外的标签:

# Add the object of “name” to the list “results”.
# `<element>.text` extracts the text in the element, omitting the HTML tags.
    results.append(name.text)

循环将遍历整个页面源代码,找到上面列出的类的所有出现位置,然后如果嵌套数据尚未出现则将其附加到列表中:

import pandas as pd
from bs4 import BeautifulSoup
from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://sandbox.oxylabs.io/products')

results = []
content = driver.page_source
soup = BeautifulSoup(content, 'html.parser')

for element in soup.find_all(attrs={'class': 'product-card'}):
    name = element.find('h4')
    if name not in results:
        results.append(name.text)

请注意,循环后的两个语句是缩进的。循环需要缩进来表示嵌套。任何一致的缩进都将被视为合法。没有缩进的循环将输出“IndentationError”,并使用插入符号 (^) 指出有问题的语句。


将数据导出至 CSV

即使运行我们的程序时没有出现语法或运行时错误,仍然可能存在语义错误。为契机应该检查我们是否真正将数据分配给正确的对象并将其正确移动到数组。

检查前面步骤中获取的数据是否正确收集的最简单方法之一是使用print。由于数组有许多不同的值,因此通常使用一个简单的循环将每个条目分成输出中的单独一行:

for x in results:
   print(x)

此时printfor应该是不言而喻的。我们只是为了快速测试和调试而启动此循环。直接打印结果是完全可行的:

print(results)

到目前为止,你的 Python 代码应该如下所示:

driver = webdriver.Chrome()
driver.get('https://sandbox.oxylabs.io/products')
results = []
content = driver.page_source
soup = BeautifulSoup(content, 'html.parser')
for a in soup.find_all(attrs={'class': 'product-card'}):
    name = a.find('h4')
    if name not in results:
        results.append(name.text)
for x in results:
    print(x)

现在运行你的程序应该不会显示错误,并且会在调试器窗口中显示获取的数据。虽然 `print` 对于测试来说很有用,但在解析和分析数据时并不那么理想。

你可能已经注意到 `import pandas as pd` 仍然是灰色的。我们终于要开始好好利用这个库了。暂时移除 `print` 循环,因为你将通过将数据移动到 CSV 文件中来做类似的事情:

df = pd.DataFrame({'Names': results})
df.to_csv('names.csv', index=False, encoding='utf-8')

这两个新语句依赖于 pandas 库。第一个语句创建一个变量df并将其对象转换为二维数据表。Names是列的名称,而results是要打印出来的列表。请注意, pandas可以创建多个列,只是你还没有足够的列表来利用这些参数(目前)。

第二条语句将变量df的数据移动到特定文件类型(在本例中为 CSV)。第一个参数为即将生成的文件分配名称和扩展名。添加扩展名是必要的,否则 pandas 将输出没有扩展名的文件,并且必须手动更改扩展名。index用于为列分配特定的起始数字。encoding用于以特定格式保存数据。在几乎所有情况下,UTF-8 就足够了。

import pandas as pd
from bs4 import BeautifulSoup
from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://sandbox.oxylabs.io/products')
results = []
content = driver.page_source
soup = BeautifulSoup(content, 'html.parser')

for a in soup.find_all(attrs={'class': 'product-card'}):
    name = a.find('h4')
    if name not in results:
        results.append(name.text)

df = pd.DataFrame({'Names': results})
df.to_csv('names.csv', index=False, encoding='utf-8')

现在不应该有任何导入项是灰色的,运行应用程序后,项目目录中应该会生成一个名为 names.csv 的文件。


将数据导出到 Excel

Pandas 库具有将数据导出到 Excel 的功能。这使得一次性将数据移动到 Excel 文件变得容易得多。但它要求你安装openpyxl库,你可以在终端中使用以下命令执行此操作:

pip install openpyxl

现在,让我们看看如何使用 pandas 将数据写入 Excel 文件:

df = pd.DataFrame({'Names': results})
df.to_excel('names.xlsx', index=False)

new 语句创建一个 DataFrame – 一个二维表格数据结构。列标签为Name,行包含来自结果数组的数据。Pandas 可以跨越多列,但这里不需要这样做,因为我们只有一列数据。

第二条语句将 DataFrame 转换为 Excel 文件(“.xlsx”)。该函数的第一个参数指定文件名 – “names.xlsx”。接下来是将 index参数设置为 false 以避免对行进行编号:

import pandas as pd
from bs4 import BeautifulSoup
from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://sandbox.oxylabs.io/products')
results = []
content = driver.page_source
soup = BeautifulSoup(content, 'html.parser')

for a in soup.find_all(attrs={'class': 'product-card'}):
    name = a.find('h4')
    if name not in results:
        results.append(name.text)

df = pd.DataFrame({'Names': results})
df.to_excel('names.xlsx', index=False)

上面的代码创建了一个名为“names.xlsx”的文件,其中包含一个Names列,该列包括我们在results数组中到目前为止的所有数据。

当然,CSV和Excel输出只是保存抓取数据的示例之一。除此之外,你还可以将数据保存为JSON文件以便于解析,或者在进行大规模网页抓取时使用数据库。


更多列表

许多网页抓取操作需要获取多组数据。例如,仅提取电子商务网站上列出的商品标题很少有用。为了收集有意义的信息并从中得出结论,至少需要两个数据点。

在本教程中,我们将尝试一些略有不同的方法。由于从同一 HTML 元素获取数据只意味着将相同的结果附加到其他列表中,因此我们应该尝试从不同的 HTML 元素中提取数据,但同时保持表格的结构。

显然,你需要另一个列表来存储数据。因此,让我们提取每个产品列表的价格:

import pandas as pd
from bs4 import BeautifulSoup
from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://sandbox.oxylabs.io/products')

results = []
other_results = []

content = driver.page_source
soup = BeautifulSoup(content, 'html.parser')

for b in soup.find_all(attrs={'class': 'product-card'}):
# Note the use of 'attrs' to again select an element with the specified class.
    name2 = b.find(attrs={'class': 'price-wrapper'})
    other_results.append(name2.text)

由于你将从 HTML 的不同部分提取额外的数据点,因此需要一个额外的循环。如果需要,你还可以添加另一个if语句来控制重复的条目:

for b in soup.find_all(attrs={'class': 'product-card'}):
    name2 = b.find(attrs={'class': 'price-wrapper'})
    if name2 not in other_results:
        other_results.append(name2.text)

最后,需要改变数据表的形成方式:

df = pd.DataFrame({'Names': results, 'Prices': other_results})

到目前为止,的代码的最新版本看起来应该是这样的:

import pandas as pd
from bs4 import BeautifulSoup
from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://sandbox.oxylabs.io/products')
results = []
other_results = []
content = driver.page_source
soup = BeautifulSoup(content, 'html.parser')

for a in soup.find_all(attrs={'class': 'product-card'}):
    name = a.find('h4')
    if name not in results:
        results.append(name.text)

for b in soup.find_all(attrs={'class': 'product-card'}):
    name2 = b.find(attrs={'class': 'price-wrapper'})
    if name2 not in other_results:
        other_results.append(name2.text)

df = pd.DataFrame({'Names': results, 'Prices': other_results})
df.to_csv('products.csv', index=False, encoding='utf-8')

如果运气好的话,运行这段代码不会报错。但有些情况下,pandas 可能会输出“ValueError: arrays must all be the same length”的错误。简单来说,resultsother_results列表的长度不相等。因此,pandas 无法创建二维表。

有几十种方法可以解决该错误消息。从用“empty”值填充最短列表到创建字典,再到创建两个系列并列出它们。我们将选择第三种选择:

series1 = pd.Series(results, name='Names')
series2 = pd.Series(other_results, name='Categories')
df = pd.DataFrame({'Names': series1, 'Categories': series2})
df.to_csv('names.csv', index=False, encoding='utf-8')

请注意,由于列表长度不均匀,数据将无法匹配,但如果需要两个数据点,则创建两个系列是最简单的解决方法。最终代码应如下所示:

import pandas as pd
from bs4 import BeautifulSoup
from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://sandbox.oxylabs.io/products')
results = []
other_results = []
content = driver.page_source
soup = BeautifulSoup(content, 'html.parser')

for a in soup.find_all(attrs={'class': 'product-card'}):
    name = a.find('h4')
    if name not in results:
        results.append(name.text)

for b in soup.find_all(attrs={'class': 'product-card'}):
    name2 = b.find(attrs={'class': 'price-wrapper'})
    if name2 not in other_results:
        other_results.append(name2.text)

series1 = pd.Series(results, name='Names')
series2 = pd.Series(other_results, name='Prices')
df = pd.DataFrame({'Names': series1, 'Prices': series2})
df.to_csv('products.csv', index=False, encoding='utf-8')

运行它应该会创建一个名为“names”且包含两列数据的 CSV 文件。


使用 Python 进行网页抓取的最佳实践

现在,你的第一个网页抓取工具应该可以完全正常运行。当然,它非常基础和简单,因此执行任何严肃的数据采集都需要进行重大升级。在进入更广阔的领域之前,我们强烈建议你尝试一些其他功能:

  • 通过创建一个循环来创建长度相等的列表,从而创建匹配的数据提取。

  • 一次性抓取多个 URL。实现此功能的方法有很多。最简单的方法之一就是重复上述代码,每次更改 URL。这样会很无聊。构建一个循环和一个要访问的 URL 数组。

  • 另一种选择是创建多个数组来存储不同的数据集,并将其输出到具有不同行的一个文件中。一次抓取几种不同类型的信息是电子商务数据采集的重要组成部分。

  • 一旦令人满意的网页抓取工具开始运行,你就不再需要观察浏览器的操作。运行 Chrome 或 Firefox 浏览器的无头版本,并使用这些浏览器来减少加载时间。

  • 创建抓取模式。想想普通用户如何浏览互联网并尝试自动化他们的操作。肯定需要新的库。使用import timefrom random import randint在网页之间创建等待时间。添加scrollto()或使用特定的键输入在浏览器中移动。在创建抓取模式时,几乎不可能列出所有可能的选项。

  • 创建监控流程。某些网站上的数据可能对时间(甚至用户)敏感。尝试创建一个长期循环,以设定的时间间隔重新检查某些 URL 并抓取数据。确保你获取的数据始终是最新的。

  • 利用Python Requests库。Requests 是任何 Web 抓取工具包中的强大工具,因为它可以优化发送到服务器的 HTTP 方法。

  • 掌握基础知识后,利用异步 Python 库同时发出多个请求。我想到两个常见的异步库 – asyncioaiohttp

  • 最后,将代理集成到你的网络爬虫中。使用特定位置的请求源可以让你获取原本无法访问的准确数据。


结    论

用Python构建网页抓取器、获取数据并从大量信息中得出结论,本质上是一个有趣且复杂的过程。

Written by 河小马

河小马是一位杰出的数字营销行业领袖,广告中国论坛的重要成员,其专业技能涵盖了PPC广告、域名停放、网站开发、联盟营销以及跨境电商咨询等多个领域。作为一位资深程序开发者,他不仅具备强大的技术能力,而且在出海网络营销方面拥有超过13年的经验。