原文地址:Web Scraping with Python Beautiful Soup: Cheat Sheet
BeautifulSoup 是一个流行的 Python 库,用于抓取网络并处理 XML 和 HTML 文档。它是一个从网站上抓取和检索数据的工具。BeautifulSoup 简化了从指定网页中轻松提取指定元素、内容和属性的过程。
本文结束时,我们将对 BeautifulSoup 的基础知识有一个很好的了解。我们将了解 BeautifulSoup 的安装、安装后的问题、提取不同类型的数据元素以及数据提取中的挑战。
网页抓取是指从网站上自动提取数据。这包括访问网页,检索网页内容,并使用脚本或工具从网页的 HTML 结构中提取特定数据。
在网站抓取过程中,脚本会向目标网站的服务器发出 HTTP 请求,寻找特定页面或页面集合的 HTML 内容。获取 HTML 内容后,刮擦器会对文档结构进行解释和导航,以便找到所需的数据,其中包括文本、链接、图像和表格。
收集到的信息可以有组织的格式保存,如数据库或 CSV 文件,以供日后研究或使用。
BeautifulSoup4 有许多有用的功能,能让网页抓取更高效、更易用。
BeautifulSoup 的部分功能如下:
让我们准备一份小抄,以便快速参考这些函数的用法。
请注意,class 在 Python 中是一个保留字,不能用作变量或参数名。因此,BeautifulSoup 为类选择器添加了下划线。
或者,您也可以用引号括住 class。
安装 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')
可使用的方法有:
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” 属性 |
方法 | 描述 |
---|---|
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 伪类,通过包含特定文本的标记名来选择元素。 |
方法 | 描述 |
---|---|
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 "的元素同级的所有姐妹类元素。 |
方法 | 描述 |
---|---|
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) | 查找并返回标签在父元素中的所有出现次数列表。 |
如果您想深入了解各项任务的细节,请继续阅读。
BeautifulSoup 并不是 Python 发行版的内置模块,因此我们必须在使用前安装它。我们将使用 BeautifulSoup4 软件包(也称为 bs4)。
运行以下命令,利用系统软件包管理器在 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 机器上安装 Beautifulsoup4 非常简单,使用以下命令即可完成安装
pip install beautifulsoup4
请注意,BeautifulSoup 只是一个用于解析和导航 HTML 和 XML 文档的高级界面。它不能解析文档,而是依赖外部解析器来完成对文档结构的实际解析。
BeautifulSoup 默认支持 Python 标准库内置的 “HTML 解析器”,但它也能与许多其他独立的第三方 Python 解析器协同工作,如 lxml 解析器和 html5lib 解析器。
使用下面给出的命令安装 html5lib 或 lxml 解析器:
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。
要抓取一个网页,我们必须先从其主机服务器上获取 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)
在使用 BeautifulSoup 进行网页抓取的过程中,我们有两个方法 find() 和 findAll() 可以从解析的 HTML 文档中定位和提取特定的 HTML 元素。这些方法使得在 Python 中浏览和操作 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"
CSS 选择器是一种模式,可根据属性和关系指定应选择页面上的哪些元素。我们可以使用 select 和 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
正则表达式是通过文本过滤数据模式的强大工具。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
让我们看看上述命令的几个示例,了解它们的运行情况。
我们可以通过以下代码查找 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)
在处理表格等结构化数据时,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
...
尽管 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.
抓取时搜索的元素有时可能不存在。为了解决这个问题,我们可以使用条件语句或返回 find() 方法的结果来防止出错。
element = soup.find("text")
if element:
print(element.text)
else:
print("Element not found")
现在,让我们来讨论一些在网络刮擦中经常出现的挑战。
从包含动态内容的网站提取数据可能比较棘手,因为这些网站通常使用 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
想象一下网页上一长串项目。分页表示有多个页面的项目,您需要一页一页地浏览才能获得所有数据。无限滚动意味着当你向下滚动时会不断出现新的项目,你必须不断滚动才能收集到所有数据。这就好比要从不断移动的传送带上收集所有的糖果。
传统的网络搜索工具可能无法胜任这一工作。要解决这个问题,您需要了解网站工作原理的定制工具。这些工具可以模仿你滚动和翻阅页面的动作。它们还能确保不会重复收集相同的糖果。
为了应对这一挑战,有各种解决方案和库可供使用,如 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
为了避免收到来自一个 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
在抓取任何网页之前,必须阅读并理解其所有服务条款或使用条件。虽然有些网站的协议明确不鼓励抓取,但其他网站可能会有特别的建议或限制。请始终遵守这些规则,以确保良好的行为。
Robots.txt 文件通常被网站用来与网络爬虫交互,并指明网站中禁止抓取和刮擦的特定区域。在抓取网站之前,查看网站的 robots.txt 文件至关重要。遵守 robots.txt 允许的准则是对网站意愿的尊重。
对爬虫的尊重包括发送可接受的请求,以避免给网站服务器造成压力。如果在短时间内向网站服务器发送过多请求,网站的性能可能会受到影响。如果要确保刮擦活动不令人讨厌,可以使用延迟和节流等策略。
在使用用户输入的信息进行网站搜刮时,要注意安全问题。确保不违反任何隐私规则,避免在获得适当同意之前收集个人信息。符合道德规范的网站搜刮实践必须始终尊重数据的保密性。
如果您的抓取活动导致出现任何问题,只要您在请求标题中包含您的电子邮件地址和姓名,网站管理员就能识别您的身份并与您联系。这种开放性鼓励了合乎道德和可接受的网络抓取。
在 Windows 上,您可能会遇到以下错误
如果您在 ROOT_TAG_NAME = u’[document]'这一行收到了语法错误,即 “语法无效”,您必须将 Python 2 代码更改为 Python 3,下载 python3 软件包即可。
python3 setup.py install
或运行 Python 在 bs4 目录中的 2 到 3 转换代码,方法是使用
2to3-3.2 -w bs4
我们已经从网络抓取的经验中掌握了数据提取的基础知识。为了高效地解析和提取数据,我们使用了 BeautifulSoup 和 CSS 选择器。考虑到道德方面的细微差别,如关注 robots.txt、遵守网站协议和保护数据隐私等,这些都凸显了遵循道德来使用这项技术。网络搜索的未来将以创造力、技术和合乎道德的数据处理为特征。
随着技术的发展,同时要适应不断变化的网络技术和规章制度所带来的困难,网络搜索对于从庞大的数字生态系统中获得洞察力至关重要。
学习愉快!