PyQt制作【小红书图片抓取】神器


在这里插入图片描述

📢闲言碎语

最近写一个系统,被一个Bug折腾了两天,至今还未解决。由于解决Bug弄得我有点心力憔悴,于是想着写其他小项目玩玩(爬取小红书图片),放松放松,在构思这个小项目的时候想着弄得稍微复杂点,弄着弄着花了大概三个小时时间(屁股要坐烂了),以为要准备结束了,结果又遇到Bug……😵

前前后后解决大大小小的Bug又花了大概一个小时……😊

在这里插入图片描述

好在最终将Bug解决了,项目也成功的启动咯~

在这里插入图片描述

具体实现,请往下看👇

🐾窗口设计

如下图所示,主窗口主要有窗口图标、自动的鼠标图标、“小红书图片抓取”标签、带有frame窗口的QLineEdit和两个QPushButton,虽然看起来简单,但其背后逻辑功能的实现也不是很难。

首先需要定义一个子类Frame,并通过继承QFrame类来进行一些窗口的基本设置,设置其窗口的样式和背景颜色。

class Frame(QFrame):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFrameStyle(QFrame.Box | QFrame.Plain)
        self.setLineWidth(3)	# 外线宽
        self.setMidLineWidth(3)		# 中线宽 
        self.setStyleSheet("QFrame {border: 3px solid #ff2442;}")   # 设置边框颜色

接着设计主窗口,设置窗口和鼠标的图标,再设置基本的控件,其中setFrameWidget方法主要是将控件放在Frame窗口中,并通过传入的参数,计算控件的大小和移动的位置,让控件与Frame窗口贴合,达到一个美化的效果。

然后重写mousePressEventmouseMoveEventmouseReleaseEvent事件,实现鼠标在窗口中按下可移动的功能,其中还会带动processWindow(显示过程信息的窗口)的移动。

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        # 设置窗口
        self.setWindowTitle('小红书图片抓取')
        self.resize(400, 250)
        self.setWindowIcon(QIcon('window.png'))
        self.setStyleSheet('background-color: white;')

        # 设置鼠标
        self.setCursor(QCursor(QPixmap('mouse.png').scaled(30, 30), 0, 0))

        # 设置frame窗口宽度
        self.frameWidth = 3

        self.mouseFlag = False

        self.setup_ui()

    # 设置组件
    def setup_ui(self):
        # 设置窗口大标题
        label_title = QLabel(self)
        label_title.setText('小红书图片抓取')
        label_title.setStyleSheet('color: #ff2442')
        label_title.move(50, 10)
        label_title.setFont(QFont('华文行楷', 25))
        label_title.adjustSize()

        # 设置文本框接收
        self.lineEdit_filepath = self.setFrameWidget(QLineEdit, (130, 30), (70, 80))
        # 设置选择文件按钮
        self.button_selectFile = self.setFrameWidget(QPushButton, (100, 30), (220, 80))
        self.button_selectFile.setText("选择文件")
        self.button_selectFile.setFont(QFont('华文行楷', 13))
        self.button_selectFile.clicked.connect(lambda: self.selectFile(self.lineEdit_filepath))

        # 设置开始启动按钮
        self.button_start = self.setFrameWidget(QPushButton, (200, 70), (90, 150))
        self.button_start.setText('开始启动')
        self.button_start.setFont(QFont('华文行楷', 20))
        self.button_start.clicked.connect(self.start)
	
	    # 选择文件
    def selectFile(self, lineEdit):
        # 弹出对话框选择文件夹
        file_dialog = QFileDialog()
        file_dialog.setFileMode(QFileDialog.AnyFile)
        file_dialog.exec_()

        selected_files = file_dialog.selectedFiles()
        if selected_files:
            lineEdit.setText(selected_files[0])

    # 开始启动
    def start(self):
        self.setProcessWindow()
        # 实例化功能类
        appFunction = AppFunction(self.textEdit_process)
        # 获取小红书链接
        LinkUrls = appFunction.getLinkUrls(self.lineEdit_filepath.text())

        for linkurl in LinkUrls:
            t = threading.Thread(target=appFunction.run, args=(linkurl,))
            t.setDaemon(True)
            t.start()
            
    # 设置frame组件
    def setFrameWidget(self, widget, widget_size, widget_move, frameToplevel=None):
        if frameToplevel != None:
            frame = Frame(frameToplevel)
        else:
            frame = Frame(self)

        widget = widget(frame)
        frame.move(widget_move[0], widget_move[1])
        frame.resize(widget_size[0], widget_size[1])
        widget.move(self.frameWidth, self.frameWidth)
        widget.resize(widget_size[0] - self.frameWidth*2, widget_size[1] - self.frameWidth*2)

        if widget.inherits("QPushButton"):
            widget.setStyleSheet("background-color: #ff2442; color: white;")
        elif widget.inherits("QLineEdit") or widget.inherits("QTextEdit"):
            widget.setStyleSheet('border: none; font-weight: bold;')   # 设置为无边框

        return widget

    # 新窗口的设置
    def setProcessWindow(self):
        self.processWindow = ProcessWindow()
        self.processWindow.setGeometry(self.x()+500, self.y(), 250, 290)

        # 设置记录过程的textEdit
        self.textEdit_process = QTextEdit(self.processWindow)
        self.textEdit_process.resize(250, 290)
        self.textEdit_process.setStyleSheet('border: none; color: white;')    # 设置为无边框
        self.textEdit_process.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)     # 隐藏垂直滚动条
        self.processWindow.show()

    # 窗口移动
    def mousePressEvent(self, evt):
        if evt.button() == Qt.LeftButton:
            self.mouseFlag = True
            self.mouse_x, self.mouse_y = evt.globalPos().x(), evt.globalPos().y()
            self.origin_x, self.origin_y = self.x(), self.y()


    def mouseMoveEvent(self, evt):
        if self.mouseFlag == True:
            move_x = self.origin_x + (evt.globalPos().x() - self.mouse_x)
            move_y = self.origin_y + (evt.globalPos().y() - self.mouse_y)
            self.move(move_x, move_y)
            try:
                self.processWindow.move(move_x+500, move_y)     # 过程信息展示窗口移动
            except:
                pass

    def mouseReleaseEvent(self, evt):
        self.mouseFlag = False

    # 窗口关闭
    def closeEvent(self, evt):
        try:
            self.processWindow.close()
        except:
            pass

在设计显示过程信息的窗口时将窗口设置为半透明、背景颜色为黑色,并在其中添加QTextEdit输入框并设置其无边框和垂直滚动条为不可见,最终在整个程序执行时,会将不同过程不同颜色字体的信息放到输入框中显示,达到一个电影中常见的黑客操作信息时的炫酷效果。

    # 新窗口的设置
    def setProcessWindow(self):
        self.processWindow = ProcessWindow()
        self.processWindow.setGeometry(self.x()+500, self.y(), 250, 290)

        # 设置记录过程的textEdit
        self.textEdit_process = QTextEdit(self.processWindow)
        self.textEdit_process.resize(250, 290)
        self.textEdit_process.setStyleSheet('border: none; color: white;')    # 设置为无边框
        self.textEdit_process.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)     # 隐藏垂直滚动条
        self.processWindow.show()
        
# ====================================== 新窗口-过程信息展示区 ======================================
class ProcessWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowFlag(Qt.FramelessWindowHint)  # 将窗口设置为无边框无标题
        self.setWindowOpacity(0.5)  # 将窗口设置为半透明
        self.setStyleSheet('background-color: black;')

🐾功能设计

功能设计主要实现读取excel中的链接、访问链接、在显示过程信息的窗口的输入框中插入信息、获取图片URL、爬取并下载图片。

# ====================================== 爬取图片能执行 ======================================
class AppFunction:
    def __init__(self, textEdit):
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'
        }

        self.pattern = 'url\("(.*)"\);'

        self.nowPath = os.getcwd()

        self.textEdit = textEdit

        self.cursor = self.textEdit.textCursor()    # 获取光标位置
        # 红色
        self.red = QColor(Qt.yellow)
        # 绿色
        self.green = QColor(Qt.green)
        # 驱动路径
        driver_path = 'D:\chromedriver-win32\chromedriver.exe'
        # 固定搭配直接用就行了
        self.service = Service(executable_path=driver_path)
        # 无头模式
        self.option = webdriver.ChromeOptions()
        self.option.add_experimental_option('excludeSwitches', ['enable-automation'])
        self.option.add_argument('--disable-blink-features=AutomationControlled')
        # self.option.add_argument('--headless')

    # 执行方法
    def run(self, url):
        self.getPhotoUrl(url)

    # 插入文本
    def insertText(self, text, color):
        format = QTextCharFormat()
        format.setForeground(color)
        self.cursor.insertText(text, format)    # 在QTextEdit中插入文字
        self.textEdit.ensureCursorVisible()     # 在插入的时候随着光标向下滚动,即达到文字自动滚动效果

    # 获取图片URL
    def getPhotoUrl(self, url):
        url = url
        driver = webdriver.Chrome(service=self.service, options=self.option)
        self.insertText('\n' + url + ' -- 开始抓取图片URL', self.red)
        driver.get(url)

        # 等待图片URL出现
        WebDriverWait(driver, 1000).until(
            EC.presence_of_element_located((By.XPATH, '//div[@class="swiper-wrapper"]/div'))
        )
        # 获取图片URL
        temp_PhotoUrls = [i.get_attribute('style') for i in driver.find_elements(By.XPATH, '//div[@class="swiper-wrapper"]/div')]
        # 获取小红书标题
        title = driver.find_element(By.XPATH, '//div[@id="detail-title"]').text

        # 正则方法清洗图片url
        PhotoUrls = list(set([re.findall(self.pattern, i)[0] for i in temp_PhotoUrls]))

        # 关闭浏览器
        driver.close()
        self.insertText('\n' + url + ' -- 图片URL抓取完成', self.green)

        self.getPhoto(PhotoUrls, title)

    # 获取图片
    def getPhoto(self, PhotoUrls, title):
        path = os.path.join(self.nowPath, title)
        if not os.path.exists(path):  # 创建文件夹
            os.makedirs(path)

        for i in range(len(PhotoUrls)):
            self.insertText('\n' + PhotoUrls[i] + ' -- 开始抓取图片', self.red)

            res = requests.get(PhotoUrls[i], headers=self.headers)
            with open(f'./{title}/{i}.webp', 'wb') as f:
                f.write(res.content)
                self.insertText('\n' + f'{self.nowPath}\\{title}\\{i}.png' + ' -- 下载完成', self.green)
        self.insertText(f'\n{title}图片下载完成!\n'.center(71, '='), self.green)

    # 读取文件,获取网页链接
    def getLinkUrls(self, path):
        f = openpyxl.load_workbook(path)
        sheet = f['Sheet1']
        LinkUrls = [i.value for i in sheet['A']]
        return LinkUrls

📚资源领取

关注微信公众号👉【Python小作坊】,回复💬“小红书图片爬取”,即可免费领取(含源代码)~
在这里插入图片描述