<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>
Demo1from 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>
网友评论