美文网首页PyQt
给妹子图网爬虫做个界面

给妹子图网爬虫做个界面

作者: Garfield_Liang | 来源:发表于2017-05-21 08:23 被阅读1569次

<p>妹子图网,相信很多老司机都不会陌生,这是一个专放各种打着擦边球有下限累了可以养养眼的网站。曾经有那么个Python大大为了可以将这些妹子保存在自己硬盘里面,写了一个专门用来爬妹子图网的爬虫,尝试模仿了下,简直膜拜了(当时刚刚开始学习爬虫)。</p>

<p>但这里面有个问题,后面下载下来的图里面,我删除了很多不感兴趣的。爬虫虽然可以按照我们的设定,帮我们下载浏览量达到多少多少的,什么时候更新的图片。但是他没有办法按照我的审美来帮我下载图片(我相信机器学习可以,但这个学习成本挺高),所以这一篇文章就是教你怎么将交互引入到爬虫中,怎么让我们的爬虫根据我们的筛选来下载,做个讨人喜欢的爬虫:</p>

<p>上一篇文章已经讲过怎么在Python里面用PyQt5做一个界面,这一次就用在妹子图网上,准备实现的功能包括:</p>

  • 在界面上做一个链接的预浏览
  • 筛选爬虫获得的链接
  • 实现翻页功能

<p>在正菜来临之前,我们先上两个小程序,熟悉下PyQt中,新增元素的方法,最后对整个程序进行讲解。这里先祭出最终实现的效果图:</p>

最终效果图
图片预览

<p>再以前命令行运行的Python爬虫里面是没有办法预览图片的,但如果有界面交互,是完全可能的。在我们做妹子图的爬虫的时候,可以发现每一个节点内,都有一个预览图片的链接。现在问题是,我们怎么将这个链接转化为交互界面上的图片。例如下面这个截图中,我们可以找到图片链接是:http://i.meizitu.net/thumbs/2017/04/90448_18b47_236.jpg</p>

图片链接

<p>在PyQt里面,Qlabel可以用来加载jpg图片。Qlabel是一个图片、文字以及数字的浏览组件,另外他也可以作为其他组件的一个标识工具。根据其说明文档,Qlabel只能识别PyQt下面QPixmap格式的图片,故我们整体的逻辑是通过request.get来将图片的数据下载出来,再用Pixmap装载这些数据,最后用Qlabel将Pixmap还原回图片。</p>

<p>新建一个.ui文件,再主页面上简单拖拽一个Push Button以及Label(注意要够大,能放图片),然后通过pyuic5命令将.ui转为.py文件。我们的目标是点击Push Button,Label上将显示链接对应的图片。</p>

Demo1
from PyQt5 import QtCore, QtGui, QtWidgets
import requests

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        ... ...  ##省去的这些都是元素加载的命令,仔细看是可以发现新大陆,这里限于篇幅,不再累赘
        self.pushButton.clicked.connect(self._showpic)  ##这里设定pushButton的点击事件

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
        
    def _showpic(self):
        url = 'http://i.meizitu.net/thumbs/2017/04/90448_18b47_236.jpg'   ##图片链接
        pic = requests.get(url).content  ##获取图片链接的数据
        pixmap = QtGui.QPixmap()  ##从QtGui中新建一个QPixmap的类
        pixmap.loadFromData(pic)  ##pixmap装载图片数据
        self.label.setPixmap(pixmap)  ##最终在label上显示图片

   ... ...

if __name__ == "__main__":
       ... ...

<p>完成后点击pushButton,我们就可以看到链接对应的图片了。这个小程序的关键问题是,为什么是通过loadFromData这个函数来加载数据。实话,一开始,我也绕了很多弯路,最早用的还不是Qlabel,是QPicture,结果发现QPicture只能读png,我又不想将图片下载后转格式再重新加载,继续再stack overflow中逛,找到这篇文章,才找到借鉴的。</p>

图片浏览
链接筛选

<p>能预览图片,就能直观筛选自己喜欢链接了。同样,也是先作一个小的demo,demo的目的是可以生成的随机十个字母的Check Boxes,最后在Text Browser内显示所勾选的字母。</p>

<p>通过pyuic5命令生成的py,我们可以知道Check Box的生成代码形如:</p>

    self.checkBox = QtWidgets.QCheckBox(self.centralwidget)  ##生成checkbox的类
    self.checkBox.setGeometry(QtCore.QRect(410, 60, 81, 20))  ##设定类的位置,四个数字依次代表左上角坐标以及横和宽
    self.checkBox.setObjectName("checkBox")  ##最后设定类的名称

<p>现在的问题是怎么在页面上显示不同字母的Check Box,最容易想到的是直接修改Check Box的坐标。后来我又发现在Vertical Layout里面,插入的Check Box是可以有自动对齐的,事实上我自己第一次做这个程序时就是用这个方法。但最后在写这篇文章的时候,我发现更好的方法,即通过Table Widget来实现。</p>

<p>新建一个ui文件,从界面的左侧拖拽一个Table Widget、Text Browser以及Push Button到主界面上。同样通过pyuic5转为py文件,目的是点击Push Button后,在Table Widget中被勾选的字母将会显示在右侧的Text Browser中。</p>

Demo2

<p>Demo中randomalpha是一个10个随机字母的序列,_addcheckbox就是用于在Table Widget中生成Check Box,注意最后是用Table Widget的setCellWidget函数关联起来,而_printchecked是用来输出Check Box序列中已经选上(isChecked)的字母:</p>

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        ... ...
        self.tableWidget.setColumnCount(2) ##设定Table Widget的列数
        self.tableWidget.setRowCount(10) ##设定Table Widget的行数
        ... ...
        
        strings = self.randomalpha()   ##生成随机字母串
        self.checkboxs = [self._addcheckbox(alpha, i) for i, alpha in enumerate(strings)]  ##根据字母串生成checkboxs
        self.pushButton.clicked.connect(self._printchecked)  ##将函数以及pushButton进行关联
                
    def randomalpha(self):
        import random, string
        return [random.choice(string.ascii_lowercase) for i in range(10)]

    def _addcheckbox(self, alpha, i):
        checkbox = QtWidgets.QCheckBox(self.centralwidget)
        checkbox.setObjectName(alpha)
        checkbox.setText(alpha)
        self.tableWidget.setCellWidget(i,0, checkbox)
        return checkbox
    
    def _printchecked(self):
        printalpha = [cb.objectName() for cb in self.checkboxs if cb.isChecked() == True]
        [self.textBrowser.append(alpha) for alpha in printalpha]
        return printalpha
    ... ...

if __name__ == "__main__":
    ... ...

<p>最后生成效果如下图所示。这里也是跌跌撞撞找了好多资料才完成,看PyQt的说明文件还真的很重要,根据一些关键词,在stack overflow能找到很多前人的工作。打个比方,在一开始我在用Table Widget的时候,老想着在Qt Creater软件的主界面里放入Check Box,但是致死都没有成功。后面就是通过Check Box、Table Widget 以及 PyQt5的关键词再google才找到对标的例子才解决。</p>

效果图

<p>此外,我们可以调节表单的列宽以及行高来将整个表格做的更加的紧凑。tableWidget自带setRowHeight只能调整单独一行的高度,为了可以将整个表格的行高进行调整,可以通过调整列表头的行高来实现:</p>

        self.tableWidget.setColumnWidth(0, 30)  ##设定第一列宽度为30
        verticalHeader = self.tableWidget.verticalHeader() ##新建一个列表头的类
        verticalHeader.setSectionResizeMode(QtWidgets.QHeaderView.Fixed)  ##设定列表头的尺寸为固定
        verticalHeader.setDefaultSectionSize(100) ##设定每一行的高度为100
        verticalHeader.hide() ##将列表头设置为隐藏

<p>上述设定后,原来程序会变成下面的样子,可以看到第一列的宽度缩窄了,每一行的行高变大了,并且通过最后hide()命令可以将列表头隐藏,达到节省空间和美观效果。</p>

调整尺寸后效果

主程序

<p>两个Demo完成后,下面我将讲解整个程序。首先是制作ui界面,拖曳3个Push Button 分别作为翻页的上页(pushButton_uppage)和下页(pushButton_nextpage)以及获得链接的开关(pushButton),拖曳Table Widget作为我们放置页面内容(tableWidget),拖曳2个Label分别作为页数显示(label_pagenum)以及图片输出(label),拖曳Text Browser作为我们链接输出(textBrowser)。</p>

最终效果图

<p>保存后好,对ui文件的进行加载(注意,直接加载就不需要再采用pyuic5进行转化了):</p>

import re, requests
from bs4 import BeautifulSoup
from PyQt5.QtWidgets import QApplication, QCheckBox, QPushButton, QHeaderView
from PyQt5.QtGui import QPixmap
import PyQt5.uic

ui_file = 'mainwindowmztable.ui'
(class_ui, class_basic_class) = PyQt5.uic.loadUiType(ui_file)

<p>主类先继承form class 以及qt base class,然后调用爬虫的class来生成首页内容的列表,根据首页的内容数量来确定Table Widget的行数,并设定相应列宽以及行高,最后是将Check Box以及Push Button放置在Table Widget中,并且对其中的Push Button与相应的功能函数进行关联:</p>

class Window(class_basic_class, class_ui):
    
    def __init__(self):
        super(Window, self).__init__() 
        self.url = "http://www.mzitu.com/"
        self.setupUi(self)
        totlist = Mzitu(self.url).printli()
        self.tableWidget.setRowCount(len(totlist))
        self.tableWidget.setColumnCount(2)
        self.tableWidget.setColumnWidth(0, 435)
        verticalHeader = self.tableWidget.verticalHeader()
        verticalHeader.setSectionResizeMode(QHeaderView.Fixed)
        verticalHeader.setDefaultSectionSize(30)
        self.textBrowser.setOpenExternalLinks(True)  ##隐藏列表头
        self.page = 1  ##设定初始的页面数,为后面做翻页器做准备
        self.label_pagenum.setNum(self.page)  
        self.checkBoxs = [self._addCheckbox(index, item[0], item[1]) for index, item in enumerate(totlist)]
        self.pushButtons = [self._addpushButtonpic(index, item[0], item[4]) for index, item in enumerate(totlist)]
        self.pushButton.clicked.connect(self.getSelList)
        self.pushButton_nextpage.clicked.connect(lambda: self._nextPage(1))
        self.pushButton_uppage.clicked.connect(lambda: self._nextPage(-1))

<p>_addCheckbox用于生成Check Box单元,并将单元通过setCellWidget关联到Table Widget对应位置中。每一个Check Box最终都用isChecked()函数来判断是否有被勾选上,getSelList是用来在textBrowser中输出对应内容的名称以及链接,注意链接这里我为了可以直接点开,而不需要复制到浏览器里面才能打开,所以采用html文本书写,以达到超级链接的效果:</p>

    def _addCheckbox(self, index, idd, boxtitle):
        checkBox = QCheckBox()
        checkBox.setObjectName(idd)
        checkBox.setText(boxtitle)
        self.tableWidget.setCellWidget(index, 0, checkBox) ##setCellWidget前面两个数字分别代表行和列,最后是需要关联的元素
        return checkBox
    
    def getSelList(self):
        selList = [(item.objectName(), item.text()) for item in self.checkBoxs if item.isChecked() == True]
        for item in selList:
            url = 'http://www.mzitu.com/'+item[0]
            self.textBrowser.append(item[1])
            self.textBrowser.append('<a href = %s>%s</a>' % (url, url))  ##此处输出超级链接
        return selList

<p>_addpushButton是用于在Table Widget中关联Push Button,方法和上述Check Box类似。注意Push Button关联函数那里,我使用了Lambda表达式。_showpic就是一个在Label中输出图片的功能,具体在之前Demo那里已经做过叙述:</p>

    def _addpushButtonpic(self, index, idd, href):
        pushButton = QPushButton()
        pushButton.setObjectName(idd)
        pushButton.setText(idd)
        self.tableWidget.setCellWidget(index, 1, pushButton)
        pushButton.clicked.connect(lambda: self._showpic(idd, href))
        return pushButton
    
    def _showpic(self, idd, href):
        pic = requests.get(href).content
        pixmap = QPixmap()
        pixmap.loadFromData(pic)
        self.label.setPixmap(pixmap)

<p>_nextPage是实现翻页器功能,一开始是考虑只做下一页的,但使用中发现不方便。所以需要增加向上翻的功能,这是通过将翻页数分别设定为+1改成-1来实现的。更换了页码之后,需要重新通过爬虫class获得新页面的内容,并重新用_addCheckbox以及_addpushButton函数来更新Check Box以及Push Button的内容,最后输出新的self.checkboxs以及self.pushButtons:</p>

    def _nextPage(self, page):
        self.page += page
        self.label_pagenum.setNum(self.page)
        url = self.url + '/page/' + str(self.page)
        totlist = Mzitu(url).printli()
        newcheckBoxs = []
        newpushButtons = []
        for index, item in enumerate(totlist):
            newcheckbox = self._addCheckbox(index, item[0], item[1])
            newpushbutton = self._addpushButtonpic(index, item[0], item[4])
            newcheckBoxs.append(newcheckbox)
            newpushButtons.append(newpushbutton)
        self.checkBoxs = newcheckBoxs
        self.pushButtons = newpushButtons

<p>妹子网爬虫的class,在这里不再详细叙述:</p>

class Mzitu():
    
    def __init__(self, url):
        self.url = url
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36',
                   'Host': 'www.mzitu.com'}
        response = requests.get(self.url, headers = headers)
        content = BeautifulSoup(response.text, 'lxml')
        linkblock = content.find('div', class_="postlist")
        self.linklist = linkblock.find_all('li')
    
    def printli(self):
        linklist = self.linklist
        linksum = list()
        
        for link in linklist:
            url = link.a.get('href')
            picurl = link.img.get('data-original')
            linkid = re.search(r'(\d+)', url).group()
            firstspan = link.span
            titleword = firstspan.get_text()
            secondspan = firstspan.find_next_sibling('span')
            uploadtime = secondspan.get_text()
            thirdspan = secondspan.find_next_sibling('span')
            viewcount = thirdspan.get_text()
            linksum.append((linkid, titleword, uploadtime, viewcount, picurl))
        return linksum

<p>最后就是整个PyQt类的实现表达,实现的效果可以看本文第一张图:</p>

if __name__ == '__main__':
    import sys
    
    app = QApplication(sys.argv)
    MainWindow = Window()
    MainWindow.show()
    sys.exit(app.exec_())

结语

<p>基本这样,我们一个比较完整的程序就完成了,后面可以通过py2exe转化是window实际可以操作的执行程序,限于篇幅这里没有写道,整个Table Widget还可以加入浏览数、新增时间以及全选等功能,来完善整个体验。最后相信精明的老司机已经发现,既然获得链接,通过爬虫可以进一步获得链接后面的图片(你们奖赏我,奖赏达到30块,我就将demo放出[坏笑])</p>

相关文章

网友评论

    本文标题:给妹子图网爬虫做个界面

    本文链接:https://www.haomeiwen.com/subject/jbnhzttx.html