Python脚本下载资源

声明: 本文中的脚本仅用于学习和研究目的。请务必遵守目标网站的爬取协议(robots.txt)和相关法律法规,切勿将此脚本用于任何违法或不当的用途。非法使用可能会导致法律后果。

在这篇教程中,我们将介绍如何使用Python脚本从网页中批量下载文件。我们将使用requests库进行HTTP请求,BeautifulSoup进行HTML解析,openpyxl读取Excel文件中的URL,并通过ThreadPoolExecutor实现多线程下载。我们还将添加一些有趣和实用的功能,如日志记录、统计信息和命令行参数。

环境准备

首先,我们需要安装必要的Python库。你可以使用以下命令安装这些库:

1
pip install requests beautifulsoup4 openpyxl tqdm

导入必要的库

在我们的Python脚本中,我们需要导入以下库:

1
2
3
4
5
6
7
8
9
10
import os
import requests
import logging
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
from tqdm import tqdm
from mimetypes import guess_extension
from concurrent.futures import ThreadPoolExecutor, as_completed
import openpyxl
import argparse

设置日志

为了方便调试和记录程序运行情况,我们需要设置日志记录到文件和控制台:

1
2
3
4
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[
logging.FileHandler("download.log"),
logging.StreamHandler()
])

这样,我们可以在控制台和日志文件中查看日志信息,方便调试和追踪问题。

定义User-Agent

为了模拟真实用户的浏览器访问,我们定义一个User-Agent:

1
2
3
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}

这可以帮助我们避免被一些网站的反爬虫机制阻挡。

创建存储文件的基文件夹

为了组织下载的文件,我们在当前目录下创建一个名为“下载”的文件夹:

1
2
base_folder = "下载"
os.makedirs(base_folder, exist_ok=True)

这样,我们下载的文件将按文件类型存储在不同的子文件夹中,方便管理。

使用requests.Session复用连接

为了提高下载效率,我们使用requests.Session复用HTTP连接:

1
2
session = requests.Session()
session.headers.update(headers)

这可以减少连接建立的开销,加快下载速度。

定义文件下载函数

这个函数负责下载单个文件,并处理潜在的错误和重试机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def download_file(file_url, retries=3):
try:
# 解析URL并获取文件扩展名
parsed_url = urlparse(file_url)
file_ext = os.path.splitext(parsed_url.path)[1][1:]
if not file_ext:
file_ext = 'unknown' # 如果没有扩展名,则标记为 unknown
folder = os.path.join(base_folder, file_ext)
os.makedirs(folder, exist_ok=True)

# 设置本地文件路径
local_filename = os.path.join(folder, os.path.basename(parsed_url.path))

for attempt in range(retries):
try:
with session.get(file_url, stream=True) as r:
r.raise_for_status()

# 检查文件大小是否大于0
content_length = int(r.headers.get('content-length', 0))
if content_length == 0:
logging.error(f"File at {file_url} is empty. Skipping...")
return

# 根据Content-Type来推断文件扩展名
content_type = r.headers.get('Content-Type')
guessed_extension = guess_extension(content_type) or ''
if guessed_extension and not local_filename.endswith(guessed_extension):
local_filename += guessed_extension

# 如果文件已存在且大小匹配,跳过下载
if os.path.exists(local_filename) and os.path.getsize(local_filename) == content_length:
logging.info(f"{local_filename} already exists and is complete. Skipping...")
return

# 启用断点续传
resume_header = {}
if os.path.exists(local_filename):
resume_header['Range'] = f'bytes={os.path.getsize(local_filename)}-'

# 下载文件并显示进度条
with session.get(file_url, headers=resume_header, stream=True) as r:
r.raise_for_status()
mode = 'ab' if 'Range' in resume_header else 'wb'
with open(local_filename, mode) as f, tqdm(
desc=local_filename,
total=content_length,
initial=os.path.getsize(local_filename) if 'Range' in resume_header else 0,
unit='B',
unit_scale=True,
unit_divisor=1024,
) as bar:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
bar.update(len(chunk))
logging.info(f"Saved {local_filename}")
return
except requests.exceptions.RequestException as e:
logging.error(f"Failed to download {file_url} (attempt {attempt + 1}): {e}")
logging.error(f"Failed to download {file_url} after {retries} attempts.")
except Exception as e:
logging.error(f"Unexpected error while downloading {file_url}: {e}")

这个函数会根据文件URL解析出文件扩展名,并创建相应的文件夹。它还会处理文件的断点续传,并在下载过程中显示进度条。

提取网页中的链接

这个函数从给定的URL中提取所有带有文件扩展名的链接,并支持根据文件类型过滤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def extract_links(url, file_types=None):
try:
response = session.get(url)
response.raise_for_status()
soup = BeautifulSoup(response.content, "html.parser")

links_to_download = []
for link in soup.find_all("a", href=True):
href = link['href']
if '.' in href:
file_url = href if href.startswith("http") else urljoin(url, href)
if file_types:
if any(file_url.endswith(file_type) for file_type in file_types):
links_to_download.append(file_url)
else:
links_to_download.append(file_url)

return links_to_download
except requests.exceptions.RequestException as e:
logging.error(f"Failed to process {url}: {e}")
return []

这个函数会解析网页内容,提取所有带有文件扩展名的链接。如果用户指定了文件类型过滤,它只会返回匹配的链接。

主程序

这个函数读取Excel文件中的URL,并调用提取和下载函数,同时统计下载的文件数量和总大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def main(urls, max_workers, file_types=None):
all_links_to_download = []

# 使用线程池提取链接
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [executor.submit(extract_links, url, file_types) for url in urls]
for future in as_completed(futures):
result = future.result()
if result:
all_links_to_download.extend(result)

# 统计信息
logging.info(f"Found {len(all_links_to_download)} files to download.")

total_size = 0
downloaded_files = 0

# 使用线程池下载文件
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [executor.submit(download_file, file_url) for file_url in all_links_to_download]
for future in as_completed(futures):
future.result()
downloaded_files += 1

logging.info(f"Downloaded {downloaded_files} files.")

这个函数会提取所有要下载的文件链接,并使用多线程下载文件,同时显示统计信息。

读取Excel文件中的URL

这个函数从Excel文件中读取URL:

1
2
3
4
5
6
7
8
def read_urls_from_xlsx(filename):
urls = []
wb = openpyxl.load_workbook(filename)
ws = wb.active
for row in ws.iter_rows(min_row=2, values_only=True):
if row[0]:
urls.append(row[0])
return urls

这个函数假设Excel文件的第一行是标题行,从第二行开始读取URL。

运行脚本

最后,我们在脚本中添加以下代码来运行整个程序,并支持从命令行传递参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Batch download files from web pages.")
parser.add_argument("xlsx_filename", help="Path to the Excel file containing URLs.")
parser.add_argument("--max_workers", type=int, default=5, help="Maximum number of threads to use.")
parser.add_argument("--file_types", nargs="*", help="List of file types to download (e.g. pdf, jpg).")
args = parser.parse_args()

if os.path.exists

(args.xlsx_filename):
urls = read_urls_from_xlsx(args.xlsx_filename)
if urls:
main(urls, args.max_workers, args.file_types)
else:
logging.error("No URLs found in the Excel file.")
else:
logging.error("The specified Excel file does not exist.")

这个部分解析命令行参数,包括Excel文件路径、最大线程数和文件类型过滤,并检查Excel文件是否存在和包含URL。如果一切正常,调用main函数开始批量下载。

示例用法

假设你的Excel文件名为urls.xlsx,并且你想使用10个线程下载所有的PDF和JPG文件,可以在命令行中运行以下命令:

1
python script.py urls.xlsx --max_workers 10 --file_types pdf jpg

这样,脚本会读取urls.xlsx中的URL,并使用最多10个线程同时下载所有PDF和JPG文件。

结论

在本教程中,详细介绍了如何使用Python脚本批量下载网页中的文件。从读取Excel文件中的URL,到提取链接并下载文件,逐步讲解了每个环节的实现方法。通过这个教程,你学会了如何使用Python脚本从网页中批量下载文件,并掌握了如何使用多线程、日志记录和命令行参数吗?行动起来吧!希望这个教程对你有所帮助!

如果你有任何问题或建议,请在后台留言。