使用 Python 和 PyQt5 构建多线程图片下载器
在这篇文章中,我们将探讨如何使用 Python 和 PyQt5 构建一个多线程图片下载器,该下载器可以从 wallhaven.cc
网站批量下载壁纸图片。我将通过代码示例逐步讲解项目的各个部分,帮助你理解其工作原理,并提供一些优化建议。
文章代码是结合AI的建议不断的修改,还有不足之处,如有bug也可以进行反馈!!!
完整代码下载在文末!!!
1. 项目概述
这个项目的核心功能是通过并发的方式快速下载大量图片。用户可以通过一个图形界面(GUI)选择下载条件(如图片类别、纯度、页数等),然后程序会自动从指定的网页抓取图片并下载到本地。
2. 项目代码结构
首先,我们来看看项目的代码结构。
import os
import sys
import time
import requests
from PyQt5 import QtCore, QtGui, QtWidgets
import threading
from PyQt5.QtWidgets import QMessageBox, QFileDialog
from bs4 import BeautifulSoupheaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, Gecko) Chrome/81.0.4389.90 Safari/531.36'
}
cent = 0
lock = threading.Lock()
在代码开头,我们导入了必要的模块:
os
,sys
,time
: 用于文件操作、系统操作和时间处理。requests
: 处理 HTTP 请求。threading
: 实现多线程。PyQt5
: 用于构建图形界面。BeautifulSoup
: 用于解析 HTML 内容。
headers
是请求头,伪装成浏览器以避免被网站拒绝。cent
是全局计数器,用于记录已下载的图片数量。
3. 图片下载逻辑
接下来,我们定义了一个类 eachPageThread
,用于处理每个页面的图片下载任务。
class eachPageThread(threading.Thread):def __init__(self, url, file_name, form):threading.Thread.__init__(self)self.form = formself.eachPageUrl = urlself.file_path = file_namedef run(self):try:eachPageCollection = []content = open_url(self.eachPageUrl)soup = BeautifulSoup(content, 'lxml')images = soup.find('section', class_="thumb-listing-page")for li in images.find_all('li'):string = str(li.a['href'])eachPageCollection.append(string)threadingSet = []for eachImage in eachPageCollection:name = eachImage.split('/')[-1]eachImageUrl = f'https://w.wallhaven.cc/full/{name[0:2]}/wallhaven-{name}.jpg'html = requests.head(eachImageUrl)res = html.status_codeImagePosixFlag = 0if res == 404:eachImageUrl = eachImageUrl[:-3] + 'png'ImagePosixFlag = 1t = threading.Thread(target=downloadEachImage, args=(eachImageUrl, name, self.file_path, ImagePosixFlag))threadingSet.append(t)t.start()ui.set_down_nums(f'已经下载 {cent} 张')for eachThread in threadingSet:eachThread.join()except Exception as e:print(f"Error in eachPageThread: {e}")
eachPageThread
类:这是一个继承自threading.Thread
的类,用于处理每个页面的图片下载。run
方法:抓取页面中的所有图片链接,并启动多个线程下载这些图片。图片下载完成后,使用全局计数器cent
更新下载数量。
图片下载的实际执行在 downloadEachImage
函数中进行:
def downloadEachImage(url, name, file_path, flag):global centfix_file_name = f'{file_path}/{name}.jpg' if flag == 0 else f'{file_path}/{name}.png'if not os.path.exists(fix_file_name):print(f"正在下载 {fix_file_name}")with open(fix_file_name, 'wb') as f:img = requests.get(url, headers=headers).contentf.write(img)with lock:cent += 1else:print(f"发现 {fix_file_name} 存在,未下载")
downloadEachImage
函数:下载单张图片,并确保下载后计数器cent
的更新是线程安全的。通过flag
标记来区分下载.jpg
或.png
格式的图片。
4. 网络请求与 HTML 解析
为了抓取网页内容,我们定义了一个简单的函数 open_url
:
def open_url(url):response = requests.get(url, headers=headers)response.encoding = 'utf-8'return response.text
这个函数发送 HTTP 请求并返回页面的 HTML 内容,供 BeautifulSoup
进行解析。
5. GUI 界面实现
项目的图形界面由 Ui_Form
类构建,该类定义了窗口的布局和逻辑。
class Ui_Form(object):def setupUi(self, Form):# 配置窗口和控件Form.setObjectName("Form")Form.resize(794, 497)self.mesb = QMessageBoxself.mark = [1, 1, 0]self.mark_2 = [1, 1, 0]self.file = ''self.sorting = 'toplist'self.topRange = '1M'self.categories = '110'self.purity = '110'font = QtGui.QFont()font.setPointSize(12)# 添加按钮和输入框self.Button_start = QtWidgets.QPushButton(Form)self.Button_start.setGeometry(QtCore.QRect(510, 50, 169, 61))self.Button_start.setFont(font)self.Button_start.setObjectName("Button_start")self.Button_start.clicked.connect(lambda: self.start(Form))# 选择文件夹按钮self.Button_choose_file = QtWidgets.QPushButton(Form)self.Button_choose_file.setGeometry(QtCore.QRect(320, 240, 121, 61))self.Button_choose_file.setFont(font)self.Button_choose_file.setObjectName("Button_choose_file")self.Button_choose_file.clicked.connect(lambda: self.thread_it(self.get_filename(Form)))# 其他控件省略...
setupUi
方法:设置窗口和控件的属性及位置,包括按钮、输入框、标签等。- 按钮的功能:如
Button_start
按钮连接到start
方法,用于触发图片下载。
在 GUI 中,用户可以输入下载页数、选择保存路径、设置图片类别和纯度,所有操作都通过按钮和输入框来完成。
def start(self, Form):if not self.mark_2[2]:self.mesb.warning(Form, '警告', '请选择文件目录', QMessageBox.Yes)returnif not self.Page_input.text().isdigit():self.mesb.warning(Form, '警告', '请输入有效的页数', QMessageBox.Yes)returnnums = self.spinBox_nums_common.value()self.mark[2] = 1page = int(self.Page_input.text())url_template = f"https://wallhaven.cc/search?categories={self.categories}&purity={self.purity}&sorting={self.sorting}&topRange={self.topRange}&page="for i in range(1, page + 1):print(f'正在爬取第 {i} 页')t = eachPageThread(f"{url_template}{i}", self.file, Form)t.start()t.join()self.mark[2] = 0self.mesb.warning(Form, '提示', '下载完成', QMessageBox.Yes)
start
方法:验证用户输入的页数和文件夹路径,构建下载 URL,并调用eachPageThread
类进行下载。
6. 多线程下载与优化
多线程的实现是这个项目的关键之一。在 eachPageThread
类中,每个页面的下载任务都是在一个独立的线程中执行的,这大大提高了下载速度。
然而,多线程编程中经常会遇到资源争用问题,比如多个线程同时修改一个变量。在这个项目中,我们使用全局锁 lock
来保护计数器 cent
,确保即使在高并发的情况下,程序也能稳定运行。
with lock:cent += 1
这种做法确保了 cent
的更新操作是线程安全的,避免了并发冲突。
7. 可能的改进与优化
虽然这个项目功能齐全,但仍有一些改进的空间:
- 错误处理:目前的错误处理比较简单,可以进一步增强
open_url
和图片下载中的错误处理,防止程序因为网络问题或其他异常而崩溃。 - 多线程优化:考虑使用线程池或
asyncio
来优化并发模型,这可以提高在高并发情况下的性能表现。 - 用户体验:在 GUI 中添加更多的用户提示或进度条,以更好地显示下载进度和状态。
8. 总结
通过这个项目,我们可以看到 Python 在处理多线程任务以及构建 GUI 应用程序方面的强大能力。多线程的使用显著提高了任务处理的效率,而 PyQt5 则提供了一个用户友好的图形界面,使得复杂的操作变得简单直观。对于需要批量处理大量数据或文件下载的场景,这种多线程与 GUI 的组合无疑是非常有效的解决方案。
这个项目不仅展示了 Python 在实际项目中的应用,也为进一步的优化和扩展提供了可能。希望这篇文章能为你提供一些关于多线程编程和 PyQt5 开发的灵感和实用技巧。或 asyncio
来进一步优化并发模型,特别是在高并发的情况下。
- 用户体验:添加更多的用户提示或进度条,进一步提高用户体验。
- 代码结构优化:将下载逻辑、GUI 逻辑和线程管理逻辑分离到不同的模块,提高代码的可维护性。
8. 总结
通过这个项目,我们展示了如何利用 Python 的多线程能力和 PyQt5 的 GUI 构建一个高效的图片下载器。这个项目不仅展示了 Python 在处理并发任务上的强大能力,还展示了如何通过图形界面使复杂的操作变得更加直观和用户友好。
完整代码地址:https://github.com/stfghly/wallhaven-download
你可以根据自己的需求,进一步扩展和优化这个项目,打造一个更为强大的图片下载器。希望这篇文章能为你提供一些关于多线程编程和 PyQt5 开发的灵感!