【译】使用 Python Beautiful Soup 进行网络抓取的小窍门

原文地址:Web Scraping with Python Beautiful Soup: Cheat Sheet

BeautifulSoup 是一个流行的 Python 库,用于抓取网络并处理 XML 和 HTML 文档。它是一个从网站上抓取和检索数据的工具。BeautifulSoup 简化了从指定网页中轻松提取指定元素、内容和属性的过程。

本文结束时,我们将对 BeautifulSoup 的基础知识有一个很好的了解。我们将了解 BeautifulSoup 的安装、安装后的问题、提取不同类型的数据元素以及数据提取中的挑战。

1. 网络抓取和 BeautifulSoup 简介

1.1. 什么是网页抓取?

网页抓取是指从网站上自动提取数据。这包括访问网页,检索网页内容,并使用脚本或工具从网页的 HTML 结构中提取特定数据。

在网站抓取过程中,脚本会向目标网站的服务器发出 HTTP 请求,寻找特定页面或页面集合的 HTML 内容。获取 HTML 内容后,刮擦器会对文档结构进行解释和导航,以便找到所需的数据,其中包括文本、链接、图像和表格。

收集到的信息可以有组织的格式保存,如数据库或 CSV 文件,以供日后研究或使用。

1.2. BeautifulSoup 如何帮助进行网页抓取?

BeautifulSoup4 有许多有用的功能,能让网页抓取更高效、更易用。

BeautifulSoup 的部分功能如下:

  • HTML 和 XML 解析: 为了处理多种结构化信息,BeautifulSoup 能够解析 XML 和 HTML 文档。
  • 搜索和过滤: 可使用 CSS 选择器、正则表达式和自定义过滤方法等多种技术搜索和过滤标签。
  • 编码检测: Beautiful Soup 能自动识别文档的源编码并将其转换为 Unicode,从而简化了各种字符编码的处理。
  • 强大的错误处理功能: 该库可优雅地处理结构不良或其他错误的 HTML/XML 文档,这在抓取真实网站时可能会发生。
  • 美化输出: 使用该库可以通过适当的缩进结构改进文档的输出,从而使文档更易于阅读。
  • 跨版本支持: 由于 Beautiful Soup 在 Python 3 和 Python 2 上的功能,Python 版本之间可以相互兼容。
  • 与其他库的协调: 其他库(如用于检索网站的 requests 库和用于处理和解析 XML 文档的 lxml 库)可与 Beautiful Soup 配合使用。

2. BeautifulSoup 小抄

让我们准备一份小抄,以便快速参考这些函数的用法。

请注意,class 在 Python 中是一个保留字,不能用作变量或参数名。因此,BeautifulSoup 为类选择器添加了下划线。

或者,您也可以用引号括住 class。

2.1. 使用 BeautifulSoup

安装 beautifulsoup4 库和 lxml 解析器。

pip install beautifulsoup4
pip install lxml

从本地系统加载 HTML 文档

from bs4 import BeautifulSoup

soup = BeautifulSoup(open("index.html"))
soup = BeautifulSoup("data")

从远程 URL读取 HTML 文档

import requests

url = "http://books.toscrape.com/"

response = requests.get(url)
if response.status_code == 200:
    html_content = response.text
else:
    print("Failed to retrieve the webpage. Status code:", response.status_code)

soup = BeautifulSoup(html_content, 'lxml')

2.2. find() 和 findAll() 方法

可使用的方法有:

find_all(tag, attributes, recursive, text, limit, keywords)

find(tag, attributes, recursive, text, keywords)
方法 描述
soup.find(“p”)
soup.find(“header”)
按标签名称查找元素
soup.find(id=”unique_id”) 根据 ID 查找元素
soup.find(‘div’, id=”unique_id”)
soup.find(‘div’, {‘id’:’unique_id’})
根据 ID 查找 DIV 元素
soup.find_all(class_=”class_name”) 根据 class_name 查找元素
soup.find_all(a, class_=”class_name”)
soup.find_all(a, {‘class’:’class_name’})
根据 class_name 查找 A 标记
soup.find_all(string=”text”) 查找包含"text"文本的所有元素
soup.find_all(text=”Example”, limit=3) 查找包含文本 “Example” 的前 3 个元素
soup.find_all(“a”)[“href”] 获取 A 标记的 “href” 属性

2.3. 使用正则表达式

方法 描述
soup.find_all(string=pattern)
soup.find_all(text=re.compile(pattern))
搜索包含与给定模式匹配的文本的元素。
soup.find_all(attrs={‘attribute’: re.compile(pattern)}) 搜索属性值符合模式的元素。
soup.select(‘tag:contains(pattern)’) 使用 :contains 伪类,通过包含特定文本的标记名来选择元素。

2.4. 使用 CSS 选择器

方法 描述
soup.select(‘element’) 选择具有指定标记名称的所有元素。
soup.select(‘.class’) 选择具有指定类别的所有元素。
soup.select(‘#id’) 选择具有指定 ID 的元素。
soup.select(‘element.class’)
soup.select(‘element#id’)
选择具有特定标记、ID 或类别的元素。
soup.select(‘element.class1.class2’) 选择具有指定多个类别的元素。
soup.select(‘element[attribute=”value”]’) 选择具有指定属性名和值的元素。
soup.select(“p nth-of-type(3)”) 选择第三个

元素。

soup.select(“p > a:nth-of-type(2)”) 选择作为

元素直接子元素的第二个 元素。

soup.select(“#link1 ~ .sister”) 选择与 ID 为 "link1 "的元素同级的所有姐妹类元素。

2.5. 导航

方法 描述
element.find_next(tag) 查找并返回当前元素之后出现的第一个标记。
element.find_all_next(tag) 查找并返回在当前元素之后出现的所有标记。
element.find_previous(tag) 查找并返回当前元素之前第一次出现的标签。
element.find_all_previous(tag) 查找并返回当前元素之前第一次出现的标签。
element.find_parent(tag) 查找并返回标签在父元素中的首次出现。
element.find_all_parents(tag) 查找并返回标签在父元素中的所有出现次数列表。

如果您想深入了解各项任务的细节,请继续阅读。

3. 设置BeautifulSoup

3.1. 安装 BeautifulSoup4

BeautifulSoup 并不是 Python 发行版的内置模块,因此我们必须在使用前安装它。我们将使用 BeautifulSoup4 软件包(也称为 bs4)。

在 Linux 机器上安装

运行以下命令,利用系统软件包管理器在 Linux 上安装 bs4

sudo apt-get install python-bs4 (for python 2.x)
//or
sudo apt-get install python3-bs4 (for python 3.x)

如果使用系统打包程序安装失败,也可以使用 pip 或 easy_install 安装 bs4。

easy_install beautifulsoup4
//or
pip install beautifulsoup4

在 Windows 机器上安装

在 Windows 机器上安装 Beautifulsoup4 非常简单,使用以下命令即可完成安装

pip install beautifulsoup4

3.2. 安装解析器

请注意,BeautifulSoup 只是一个用于解析和导航 HTML 和 XML 文档的高级界面。它不能解析文档,而是依赖外部解析器来完成对文档结构的实际解析。

BeautifulSoup 默认支持 Python 标准库内置的 “HTML 解析器”,但它也能与许多其他独立的第三方 Python 解析器协同工作,如 lxml 解析器和 html5lib 解析器。

使用下面给出的命令安装 html5lib 或 lxml 解析器:

在 Linux 机器上

apt-get install python-lxml
apt-get insall python-html5lib

在 Windows 机器上

pip install lxml
pip install html5lib

一般来说,lxml 更擅长解析 “混乱” 或畸形的 HTML 代码。它很宽容,能修复未闭合的标记、嵌套不当的标记以及缺少 head 或 body 标记等问题。

虽然 lxml 比 html.parser 稍微快一些,但好的网页抓取代码一般都注重稳健、易读的实现,而不是巧妙的处理优化,因为网络延迟很容易让这些优化相形见绌。

和 lxml 一样,html5lib 也是一个宽容度极高的解析器,它能更主动地纠正损坏的 HTML。

4. 加载和解析网页,生成 Beautiful Soup 和请求模块

要抓取一个网页,我们必须先从其主机服务器上获取 HTML 或 XML 字符串,然后才能解析其内容。

例如,我们可以使用 Python 的 requests 库来获取网页的 HTML 内容。如果我们还没有安装 requests,请务必安装:

import requests

url = "http://books.toscrape.com/"
response = requests.get(url)

if response.status_code == 200:
    html_content = response.text
else:
    print("Failed to retrieve the webpage. Status code:", response.status_code)

现在,我们可以运行各种命令,从解析后的文本中获取精确数据。在本演示中,我们将使用网站 (http://books.toscrape.com/) 进行抓取。

现在我们有了网页的 HTML 内容,可以创建一个带有解析器的 BeautifulSoup 对象:

import requests
from bs4 import BeautifulSoup

url = "http://books.toscrape.com/"
response = requests.get(url)

if response.status_code == 200:
    html_content = response.text
else:
    print("Failed to retrieve the webpage. Status code:", response.status_code)

soup = BeautifulSoup(html_content, 'lxml')

print(soup.h1)
print(soup.h1.text)
print(soup.h1.string)

程序只输出页面上找到的第一个 h1 标签实例。

<h1>All productsh1>
All products
All products

请注意,使用 bs4 可以有多种路径到达 HTML 结构中的一个节点。事实上,以下任何函数调用都会产生相同的输出结果:

print(soup.html.body.h1)
print(soup.body.h1)
print(soup.html.h1)

5. 使用 BeautifulSoup 进行网络打包的基础知识

5.1. find() 和 findAll() 方法

在使用 BeautifulSoup 进行网页抓取的过程中,我们有两个方法 find() 和 findAll() 可以从解析的 HTML 文档中定位和提取特定的 HTML 元素。这些方法使得在 Python 中浏览和操作 HTML 数据变得非常容易。

  • find() 方法根据元素的名称 (标记)、属性、文本内容或它们的组合,定位并检索与指定条件相匹配的特定 HTML 元素的第一次出现。该方法会返回一个元素,如果没有找到匹配元素,则返回 None。
  • findAll() 方法会查找并返回与上述提供的条件相匹配的特定 HTML 元素的所有出现列表。它会返回一个匹配元素列表,如果没有找到匹配元素,则返回空列表。
find_all(tag, attributes, recursive, text, limit, keywords)

find(tag, attributes, recursive, text, keywords)

让我们举个例子。

from bs4 import BeautifulSoup

html = """

Hello, world!

  • Item 1
  • Item 2
  • Item 3
"""
soup = BeautifulSoup(html, 'html.parser') element = soup.find('p', class_='my-class') print(element.text) # Prints "Hello, world!" items = soup.find_all('li') for item in items: print(item.text) # Prints "Item 1" "Item 2" "Item 3"

5.2. 使用 CSS 选择器进行精细提取

CSS 选择器是一种模式,可根据属性和关系指定应选择页面上的哪些元素。我们可以使用 select 和 select_one 方法应用 CSS 选择器,并提取与选择器匹配的元素。

  • select 方法返回一个与 CSS 选择器匹配的所有元素的列表。如果没有元素匹配选择器,则返回空列表。
  • select_one 方法返回与 CSS 选择器匹配的第一个元素。如果没有元素匹配选择器,则返回 “无”。
    我们可以使用这些方法从使用普通方法可能难以触及的元素中收集数据。
elements = soup.select_one(selector)

例如,要从选择器为 “div.col-sm-6.product_main > h1” 的元素中获取数据,我们将使用下面给出的代码。

selector = "div.col-sm-6.product_main > h1"

element = soup.select_one(selector)

element_text=element.text

if element:
    print("Element Text:", element_text)
else:
    print("Element not found on the page.")

程序输出:

Element Text: A Light in the Attic

5.3. 使用正则表达式

正则表达式是通过文本过滤数据模式的强大工具。BeautifulSoup 主要处理结构化 HTML,但与 regex 结合使用时,它可以改进从元素内容中收集数据的工作。

让我们通过下面的示例来了解这一点

import re

pattern = re.compile(r"(\d{3})-\d{3}-\d{4}")

phone_numbers = soup.find_all(text=pattern)

上面代码中给出的正则表达式(\d{3})-\d{3}-\d{4}) 正在查找格式为 "###-###-####"的电话号码,其中每个 "#"代表一位数字。因此,运行这段代码后,我们将得到 HTML 页面上的电话号码列表。

现在,假设我们想查看该网页上的所有价格,那么我们将使用下面给出的代码。

pattern = re.compile(r"£\d+\.\d{2}")
prices = soup.find_all(text=pattern)

if prices:
    for price in prices:
        print("Price:",price)
else:
    print("Element not found on the page.")

这段代码获取一个网页,解析其 HTML 内容,并使用正则表达式提取和打印所有格式为"£XX.XX "的价格。

Price: £51.77
Price: £51.77
Price: £51.77
Price: £0.00

6. 实际网络抓取

让我们看看上述命令的几个示例,了解它们的运行情况。

6.1. 查找所有标题

我们可以通过以下代码查找 HTML 文档中的所有标题。

soup = BeautifulSoup(html_content, 'lxml')

headings = soup.find_all(['h1','h2','h3','h4','h5','h6'])

# Iterate over the headings and print their text
for heading in headings:
    print(heading.text)

6.2. 抓取表格和结构化数据

在处理表格等结构化数据时,BeautifulSoup 的导航和数据提取功能仍然非常有用。要仔细提取表格数据,我们可以专注于特定的单元格和行。

让我们通过一个例子来了解语法和流程:

table = soup.find("table")

for row in table.find_all("tr"):
    cells = row.find_all("td")
    for cell in cells:
        print(cell.text)

上述代码将找出 HTML 表格,然后遍历每一行及其单元格。提取每个单元格的内容后,将其打印到控制台。

让我们通过一个示例来更好地理解这一点:

table = soup.find("table")

if table:
    for row in table.find_all("tr"):
        cells = row.find_all("th")
        cells += row.find_all("td")
        for cell in cells:
            print(cell.text)
else:
    print("Table not found on the page.")

程序输出:

UPC
a897fe39b1053632

Product Type
Books

Price (excl. tax)
£51.77

...

6.3. 抓取图片和媒体文件

尽管 BeautifulSoup 主要用于解释 HTML,但我们仍然可以从照片等媒体资产中提取数据。

让我们举另一个例子来更清楚地理解这一点。现在,假设我们想把网页上的一个图像下载到本地磁盘,那么我们将使用下面给出的代码:

from bs4 import BeautifulSoup
import requests
import os
from urllib.parse import urljoin  # Import the urljoin function

# Define the URL of the web page you want to scrape
base_url = "http://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"
url = base_url

# Send an HTTP GET request to the URL
response = requests.get(url)

# Check if the request was successful (status code 200)
if response.status_code == 200:
    html_content = response.content
else:
    print("Failed to retrieve the web page. Status code:", response.status_code)
    exit()

# Create a BeautifulSoup object to parse the HTML content
soup = BeautifulSoup(html_content, 'html.parser')

image_tags = soup.find_all("img")

if image_tags:
    for img in image_tags:
        img_url_relative = img.get("src")
        
        # Construct the complete URL by joining the relative path with the base URL
        img_url_absolute = urljoin(base_url, img_url_relative)
        
        img_response = requests.get(img_url_absolute)
        if img_response.status_code == 200:
            # Generate a unique filename based on the image URL
            img_filename = os.path.basename(img_url_absolute)
            with open(img_filename, 'wb') as fp:
                fp.write(img_response.content)
                print(f"Image '{img_filename}' downloaded and saved.")
        else:
            print("Failed to download image from URL:", img_url_absolute)
else:
    print("No image tags found on the page.")

该代码提取该图片的相对 URL,并将其与基本 URL 结合,创建一个完整的图片 URL。向这些 URL 发送 HTTP 请求后,它会检查响应的状态代码是否为 200(表示成功)。

如果成功,它就会以唯一的文件名将图片保存到本地目录,并打印下载图片的信息。如果图像下载失败,则会打印一条错误信息。如果网页上没有图像标记,则会显示一条提示信息。这段代码的目的是从网页中抓取并下载图片以供进一步使用。

输出:

Image 'fe72f0532301ec28892ae79a629a293c.jpg' downloaded and saved.

6.4. 处理缺失元素和错误

抓取时搜索的元素有时可能不存在。为了解决这个问题,我们可以使用条件语句或返回 find() 方法的结果来防止出错。

element = soup.find("text")

if element:
    print(element.text)
else:
    print("Element not found")
  1. 数据提取中的挑战

现在,让我们来讨论一些在网络刮擦中经常出现的挑战。

7.1. 带 JavaScript 和动态内容的网站

从包含动态内容的网站提取数据可能比较棘手,因为这些网站通常使用 JavaScript 来加载和更新信息。事实上,BeautifulSoup 并不理解 JavaScript,这给标准的网络搜刮造成了障碍。

异步请求(即在初始页面加载后加载数据)会使数据捕获变得更加复杂。此外,浏览复杂的网页结构以及处理身份验证、验证码和速率限制等任务也带来了挑战。网站可能会经常更改布局,这就要求不断更新刮擦脚本。法律和道德方面的问题也会产生影响,因为刮擦动态网站可能会违反服务条款和版权法。

为了应对这一挑战,有各种解决方案和库可供使用。

例如,Selenium 或 Puppeteer 等Headless浏览器可以呈现 JavaScript 驱动的内容并与网页交互。当标准 HTML 解析能力不足时,它们就能派上用场。我们还可以使用 Selenium 来显式等待和处理初始页面加载后动态加载的内容。

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "my_element")))
data = element.text

7.2. 处理分页和无限滚动

想象一下网页上一长串项目。分页表示有多个页面的项目,您需要一页一页地浏览才能获得所有数据。无限滚动意味着当你向下滚动时会不断出现新的项目,你必须不断滚动才能收集到所有数据。这就好比要从不断移动的传送带上收集所有的糖果。

传统的网络搜索工具可能无法胜任这一工作。要解决这个问题,您需要了解网站工作原理的定制工具。这些工具可以模仿你滚动和翻阅页面的动作。它们还能确保不会重复收集相同的糖果。

为了应对这一挑战,有各种解决方案和库可供使用,如 Scrappy、Selenium 等。

import requests
from bs4 import BeautifulSoup

url = "https://example.com/api/data"
headers = {"User-Agent": "BrowserAgent"}
params = {"param1": "value1", "param2": "value2"}

response = requests.get(url, headers=headers, params=params)

if response.status_code == 200:
    # If the response is JSON, parse it directly
    data = response.json()

    # If the response is HTML, parse it with BeautifulSoup
    soup = BeautifulSoup(data, 'html.parser')
    # Now, you can navigate and extract data from 'soup'

#7.3. 需要身份验证的网站

从需要用户身份验证的网站提取数据会遇到各种障碍。首先,要进入网站内容就必须进行用户身份验证管理,包括提交用户名和密码等登录信息。这可能是一个复杂的安全自动化过程。认证成功后,必须保持会话状态并处理 cookie,以便访问网站的受保护区域。

此外,可能还需要应对速率限制、验证码测试或多因素身份验证(MFA)方法等挑战,从而增加了复杂性。此外,经过身份验证的页面结构往往与可公开访问的页面有很大不同,因此有必要调整刮擦脚本。

最后,考虑法律和道德方面的问题也很重要,因为对经过验证的网站进行刮擦可能会违反服务条款或隐私法规。因此,需要谨慎合规和符合道德规范的数据使用方法。

为了应对这一挑战,我们可以使用 Python 中的请求库来管理身份验证、会话控制和 Cookie 持久性。

import requests

url = 'https://example.com/login'
payload = {'username': 'your_username', 'password': 'your_password'}

# Perform authentication
session = requests.Session()
response = session.post(url, data=payload)

if response.status_code == 200:
    # You are now authenticated and can make requests as an authenticated user

7.4. 处理速率限制

为了避免收到来自一个 IP 地址的过多请求,网站经常使用速率限制技术。在收集数据时处理速率限制有点像在自助餐厅里一次只能吃一盘菜,而且要等一会儿才能吃到更多。同样,有些网站为了避免出现问题,会放慢收集数据的速度。

你需要慢慢收集数据,必要时休息一下,并考虑到网站的资源。这可能意味着在两次数据请求之间要等待一段时间,并留意是否有任何迹象表明你收集数据的速度过快。关键是要找到合适的节奏,既能获得想要的数据,又不会造成任何问题。

我们可以使用 time.sleep() 函数在请求之间引入延迟。这是一种确保不超过允许请求速率的简单方法。

import requests
import time

max_retries = 3
retry_delay = 5  # Wait for 5 seconds between retries

for _ in range(max_retries):
    response = requests.get('https://example.com')
    if response.status_code == 200:
        # Process the response
    elif response.status_code == 429:
        time.sleep(retry_delay)
    else:
        break  # Exit the loop on other status codes

8. 道德方面的考虑

8.1. 理解网站服务条款

在抓取任何网页之前,必须阅读并理解其所有服务条款或使用条件。虽然有些网站的协议明确不鼓励抓取,但其他网站可能会有特别的建议或限制。请始终遵守这些规则,以确保良好的行为。

8.2. Robots.txt 和尊重网站权限

Robots.txt 文件通常被网站用来与网络爬虫交互,并指明网站中禁止抓取和刮擦的特定区域。在抓取网站之前,查看网站的 robots.txt 文件至关重要。遵守 robots.txt 允许的准则是对网站意愿的尊重。

8.3. 爬虫礼貌和避免过度请求

对爬虫的尊重包括发送可接受的请求,以避免给网站服务器造成压力。如果在短时间内向网站服务器发送过多请求,网站的性能可能会受到影响。如果要确保刮擦活动不令人讨厌,可以使用延迟和节流等策略。

8.4. 数据隐私和用户同意

在使用用户输入的信息进行网站搜刮时,要注意安全问题。确保不违反任何隐私规则,避免在获得适当同意之前收集个人信息。符合道德规范的网站搜刮实践必须始终尊重数据的保密性。

8.5. 在请求标题中发送您的姓名和电子邮件

如果您的抓取活动导致出现任何问题,只要您在请求标题中包含您的电子邮件地址和姓名,网站管理员就能识别您的身份并与您联系。这种开放性鼓励了合乎道德和可接受的网络抓取。

9. 常见问题

9.1. ImportError “No module named HTMLParser“(没有名为 HTMLParser 的模块)

在 Windows 上,您可能会遇到以下错误

  • ImportError “No module named HTMLParser”- 该错误表示您正在 Python 3 下执行 Python 2 版本的代码。
  • ImportError “No module named html.parser”(没有名为 html.parser 的模块)——这个错误表示您正在 Python 2 下执行 Python 3 发布的代码。
    解决上述两个问题的唯一方法是完全卸载之前的安装,然后重新安装 BeautifulSoup。

9.2. Invalid Syntax: ROOT_TAG_NAME = u’[document]’(无效语法:ROOT_TAG_NAME = u’[document]’)

如果您在 ROOT_TAG_NAME = u’[document]'这一行收到了语法错误,即 “语法无效”,您必须将 Python 2 代码更改为 Python 3,下载 python3 软件包即可。

python3 setup.py install

或运行 Python 在 bs4 目录中的 2 到 3 转换代码,方法是使用

2to3-3.2 -w bs4

10. 结论

我们已经从网络抓取的经验中掌握了数据提取的基础知识。为了高效地解析和提取数据,我们使用了 BeautifulSoup 和 CSS 选择器。考虑到道德方面的细微差别,如关注 robots.txt、遵守网站协议和保护数据隐私等,这些都凸显了遵循道德来使用这项技术。网络搜索的未来将以创造力、技术和合乎道德的数据处理为特征。

随着技术的发展,同时要适应不断变化的网络技术和规章制度所带来的困难,网络搜索对于从庞大的数字生态系统中获得洞察力至关重要。

学习愉快!

你可能感兴趣的:(Python,python,开发语言)