
本文将详细介绍如何使用Python的requests和BeautifulSoup库,从动态生成的ASP网站上抓取PDF文件链接,并利用HTML链接的显示文本作为下载文件的本地文件名。教程涵盖了HTTP POST请求、HTML解析、URL处理、文件下载与保存等核心步骤,旨在提供一个结构清晰、实用的网页内容自动化下载解决方案。
在进行网页内容抓取时,经常会遇到需要下载特定文件(如PDF、图片等)并以更具描述性的名称保存它们的需求。传统的下载方式可能只提供一个由URL路径生成的默认文件名,这通常不够直观。本教程将聚焦于一个常见场景:从一个通过POST请求动态加载内容的ASP网站上,识别并下载PDF文件,同时将HTML中对应的<a>标签文本作为本地文件名。
我们将使用以下Python库:
首先,确保你的Python环境中安装了requests和BeautifulSoup库。如果没有,可以通过pip进行安装:
立即学习“Python免费学习笔记(深入)”;
pip install requests beautifulsoup4 lxml
接下来,在你的Python脚本中导入必要的模块:
import os import requests from bs4 import BeautifulSoup from urllib import parse as urlparse # 用于URL处理,尽管在本例中主要用于替换路径分隔符
为了与目标网站进行交互并保存文件,我们需要定义请求的URL、HTTP头部、POST请求体以及本地文件输出路径。
# 目标网站的基础URL
BASE_URL = "https://www.svpo.nl/curriculum.asp"
# 模拟浏览器行为的HTTP头部
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.9',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
}
# 定义要抓取的分类和科目(作为POST请求的payload)
klassen = ['1e klas']
vakken = ['Wiskunde'] # 可以扩展此列表以抓取更多科目
# 本地PDF文件保存的根目录
OUTPUT_ROOT_PATH = r'c:ooks' # 注意使用原始字符串r''以避免反斜杠转义问题目标网站通过POST请求动态加载内容。我们需要为每个“班级”和“科目”组合发送一个POST请求,然后解析返回的HTML内容以找到PDF链接。
for klas in klassen:
for vak in vakken:
# 构建当前科目和班级的本地保存路径
current_save_path = os.path.join(OUTPUT_ROOT_PATH, klas, vak)
# 确保目录存在,如果不存在则创建,exist_ok=True避免目录已存在时报错
os.makedirs(current_save_path, exist_ok=True)
# 构建POST请求的表单数据
payload = {'vak': vak, 'klas_en_schoolsoort': klas}
print(f"正在处理:班级 '{klas}', 科目 '{vak}'")
try:
# 发送POST请求
response = requests.post(BASE_URL, data=payload, headers=HEADERS)
response.raise_for_status() # 检查HTTP请求是否成功 (200 OK)
# 使用BeautifulSoup解析响应的HTML内容
soup = BeautifulSoup(response.text, "lxml")
# 查找所有带有href属性的<a>标签
all_links = soup.find_all('a', {'href': True})
# ... 后续步骤将在此处处理找到的链接
except requests.exceptions.RequestException as e:
print(f"请求失败:{e}")
continue # 继续处理下一个科目或班级这是实现核心功能的部分。我们需要遍历所有找到的<a>标签,筛选出PDF链接,并提取其文本内容作为文件名。
# ... (接上一步骤的循环内部) ...
for link_tag in all_links:
# 获取href属性值
href_url = link_tag.get('href')
# 检查链接是否以.pdf结尾(不区分大小写)
if href_url and href_url.lower().endswith('.pdf'):
# 统一URL中的路径分隔符,将反斜杠替换为正斜杠,以确保URL的正确性
download_url = href_url.replace('\', '/')
# 从<a>标签的文本内容中获取期望的本地文件名
# 例如:<a href="...">Chapter 3 - Weird science</a> -> "Chapter 3 - Weird science.pdf"
filename_base = link_tag.text.strip() # .strip()去除首尾空白
# 构建完整的本地文件名,添加.pdf后缀
local_filename = filename_base + '.pdf'
# 构建PDF文件在本地的完整保存路径
full_local_path = os.path.join(current_save_path, local_filename)
print(f" 发现PDF: {filename_base}")
print(f" 下载链接: {download_url}")
print(f" 保存至: {full_local_path}")
# ... 后续步骤将在此处下载文件使用requests.get()方法下载PDF文件的二进制内容,并将其写入本地文件。
# ... (接上一步骤的循环内部) ...
try:
# 发送GET请求下载PDF文件
pdf_response = requests.get(download_url, headers=HEADERS, stream=True)
pdf_response.raise_for_status() # 检查下载请求是否成功
# 以二进制写入模式打开文件,并将下载内容分块写入
with open(full_local_path, 'wb') as f:
for chunk in pdf_response.iter_content(chunk_size=8192):
f.write(chunk)
print(f" 成功下载 '{local_filename}'")
except requests.exceptions.RequestException as e:
print(f" 下载 '{download_url}' 失败: {e}")
except IOError as e:
print(f" 保存文件 '{full_local_path}' 失败: {e}")
print('---') # 分隔不同PDF的处理信息将以上所有步骤整合,形成一个完整的Python脚本:
import os
import requests
from bs4 import BeautifulSoup
from urllib import parse as urlparse
# 目标网站的基础URL
BASE_URL = "https://www.svpo.nl/curriculum.asp"
# 模拟浏览器行为的HTTP头部
HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
}
# 定义要抓取的分类和科目
klassen = ['1e klas']
vakken = ['Wiskunde']
# 可以扩展此列表以抓取更多科目,例如:
'''vakken = ['Engels','Aardrijkskunde','Economie', 'Filosofie','Frans', 'Geschiedenis',
'Nask', 'Natuurkunde', 'Nederlands', 'Scheikunde', 'Spaans', 'Wiskunde',
'Biologie', 'Duits', 'Grieks','Latijn','Leesmateriaal',
'Loopbaanorientatie','NLT']'''
# 本地PDF文件保存的根目录
OUTPUT_ROOT_PATH = r'c:ooks' # 使用原始字符串r''以避免反斜杠转义问题
def download_pdfs_from_website():
"""
从指定网站下载PDF文件,并使用链接文本作为文件名。
"""
print("开始下载PDF文件...")
for klas in klassen:
for vak in vakken:
# 构建当前科目和班级的本地保存路径
current_save_path = os.path.join(OUTPUT_ROOT_PATH, klas, vak)
# 确保目录存在,如果不存在则创建
os.makedirs(current_save_path, exist_ok=True)
# 构建POST请求的表单数据
payload = {'vak': vak, 'klas_en_schoolsoort': klas}
print(f"
--- 正在处理:班级 '{klas}', 科目 '{vak}' ---")
try:
# 发送POST请求获取HTML内容
response = requests.post(BASE_URL, data=payload, headers=HEADERS)
response.raise_for_status() # 检查HTTP请求是否成功
# 使用BeautifulSoup解析HTML
soup = BeautifulSoup(response.text, "lxml")
# 查找所有带有href属性的<a>标签
all_links = soup.find_all('a', {'href': True})
found_pdfs_count = 0
for link_tag in all_links:
href_url = link_tag.get('href')
# 检查链接是否是PDF文件
if href_url and href_url.lower().endswith('.pdf'):
# 统一URL中的路径分隔符(将反斜杠替换为正斜杠)
download_url = href_url.replace('\', '/')
# 从<a>标签的文本内容中获取期望的本地文件名
filename_base = link_tag.text.strip()
# 构建完整的本地文件名
local_filename = filename_base + '.pdf'
# 构建PDF文件在本地的完整保存路径
full_local_path = os.path.join(current_save_path, local_filename)
print(f" 发现PDF: '{filename_base}'")
print(f" 下载链接: {download_url}")
print(f" 保存至: {full_local_path}")
try:
# 发送GET请求下载PDF文件
pdf_response = requests.get(download_url, headers=HEADERS, stream=True)
pdf_response.raise_for_status() # 检查下载请求是否成功
# 以二进制写入模式打开文件,并将下载内容分块写入
with open(full_local_path, 'wb') as f:
for chunk in pdf_response.iter_content(chunk_size=8192):
f.write(chunk)
print(f" 成功下载 '{local_filename}'")
found_pdfs_count += 1
except requests.exceptions.RequestException as e:
print(f" 下载 '{download_url}' 失败: {e}")
except IOError as e:
print(f" 保存文件 '{full_local_path}' 失败: {e}")
print('---') # 分隔不同PDF的处理信息
if found_pdfs_count == 0:
print(f" 在班级 '{klas}', 科目 '{vak}' 中未找到PDF文件。")
except requests.exceptions.RequestException as e:
print(f" 处理班级 '{klas}', 科目 '{vak}' 时请求失败:{e}")
print("
所有PDF文件下载尝试完成。")
if __name__ == "__main__":
download_pdfs_from_website()文件名合法性: link_tag.text获取的文本可能包含操作系统不允许作为文件名的特殊字符(如/, , :, *, ?, ", <, >, |等)。在实际应用中,建议对filename_base进行额外的清理或“slugify”处理,例如使用正则表达式移除或替换非法字符,确保生成的文件名在所有操作系统上都是有效的。
import re
def sanitize_filename(filename):
# 移除或替换非法字符
filename = re.sub(r'[\/:*?"<>|]', '_', filename)
# 移除文件名末尾的点,防止Windows系统问题
filename = filename.rstrip('.')
return filename
# 在代码中使用:
# local_filename = sanitize_filename(filename_base) + '.pdf'URL编码: 虽然本例中的link.text直接用于本地文件名,但如果URL本身包含特殊字符(如空格、非ASCII字符),requests库通常会正确处理其编码。对于URL路径中的反斜杠,我们已经通过replace('\', '/')进行了标准化。
错误处理: 代码中包含了try-except块来捕获requests.exceptions.RequestException(网络错误、HTTP状态码非2xx)和IOError(文件保存错误),这使得脚本更加健壮。
User-Agent: 模拟浏览器User-Agent是良好的实践,可以避免某些网站的访问限制。
速率限制与反爬: 对于频繁或大规模的抓取任务,应考虑增加请求之间的延迟(time.sleep()),以避免给服务器造成过大负担或触发网站的反爬机制。
stream=True与iter_content: 在下载大文件时,requests.get(..., stream=True)结合response.iter_content()可以避免将整个文件一次性加载到内存中,从而节省内存资源。
通过本教程,你已经学会了如何使用Python requests和BeautifulSoup库,实现从动态加载内容的网站上抓取PDF文件,并根据HTML链接的文本内容自定义保存文件名。这个方法不仅提升了下载文件的可读性,也展示了Python在自动化网页数据处理方面的强大能力。掌握这些技术,可以帮助你高效地管理和组织从网络获取的信息。
以上就是Python与BeautifulSoup:从网站下载PDF并自定义文件名的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号