美剧《权力的游戏》终于要开播最后一季了,作为马丁老爷子的忠实粉丝,为了能够看得懂第八季复杂庞大的剧情架构,本人想着将前几季再稳固一下,所以就上美剧天堂下载来看,可是每次都上去下载太麻烦了,于是干脆自己写个爬虫爬下来得了。
Python + PyQt5 实现美剧爬虫可视工具话不多说,先上图片。
本人才疏学浅,就写了个简单的可视化软件,关键是功能实现就行了嘛。
实现语言:Python ,版本 3.7.1
实现思路:首先运用 Python 工具爬取到数据再实现图形化软件。
由于这里只是实现简单的爬取数据,并没有牵扯到 cookie 之类的敏感信息,也没有设置代理,所以在选择 Python 库上并没有引入 Selenium 或者更高级的 Scrapy 框架,只是拿到数据就可以了,没必要那么麻烦。
所以选择了 urllib 这个库,在 Python 2.X 中应该是 urllib 和 urllib2 同时引入,由于本人选用的版本的 Python 3.X ,在 Python 3.X 中上面两个库已经被合并为 urllib 一个库,语法上有些不同,但语言这种东西都是大同小异的嘛。
先贴代码,缓和一下尴尬的气氛。
importurllib.requestfromurllibimportparsefromlxmlimportetreeimportsslfromPyQt5.QtWidgetsimportQApplication,QWidget,QLineEdit,QTextEdit,QVBoxLayout,QPushButton,QMessageBoximportsys#Python学习交流群:973783996# 取消代理验证ssl._create_default_https_context=ssl._create_unverified_contextclassTextEditMeiJu(QWidget):def__init__(self,parent=None):super(TextEditMeiJu,self).__init__(parent)# 定义窗口头部信息self.setWindowTitle('美剧天堂')# 定义窗口的初始大小self.resize(500,600)# 创建单行文本框self.textLineEdit=QLineEdit# 创建一个按钮self.btnButton=QPushButton('确定')# 创建多行文本框self.textEdit=QTextEdit# 实例化垂直布局layout=QVBoxLayout# 相关控件添加到垂直布局中layout.addWidget(self.textLineEdit)layout.addWidget(self.btnButton)layout.addWidget(self.textEdit)# 设置布局self.setLayout(layout)# 将按钮的点击信号与相关的槽函数进行绑定,点击即触发self.btnButton.clicked.connect(self.buttonClick)# 点击确认按钮defbuttonClick(self):# 爬取开始前提示一下start=QMessageBox.information(self,'提示','是否开始爬取《'+self.textLineEdit.text+"》",QMessageBox.Ok|QMessageBox.No,QMessageBox.Ok)# 确定爬取ifstart==QMessageBox.Ok:self.page=1self.loadSearchPage(self.textLineEdit.text,self.page)# 取消爬取else:pass# 加载输入美剧名称后的页面defloadSearchPage(self,name,page):# 将文本转为 gb2312 编码格式name=parse.quote(name.encode('gb2312'))# 请求发送的 url 地址url="https://www.meijutt.com/search/index.asp?page="+str(page)+"&searchword="+name+"&searchtype=-1"# 请求报头headers={"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}# 发送请求request=urllib.request.Request(url,headers=headers)# 获取请求的 html 文档html=urllib.request.urlopen(request).read# 对 html 文档进行解析text=etree.HTML(html)# xpath 获取想要的信息pageTotal=text.xpath('//div[@class="page"]/span[1]/text')# 判断搜索内容是否有结果ifpageTotal:self.loadDetailPage(pageTotal,text,headers)# 搜索内容无结果else:self.infoSearchNull# 加载点击搜索页面点击的本季页面defloadDetailPage(self,pageTotal,text,headers):# 取出搜索的结果一共多少页pageTotal=pageTotal[0].split('/')[1].rstrip("页")# 获取每一季的内容(剧名和链接)node_list=text.xpath('//a[@class="B font_14"]')items={}items['name']=self.textLineEdit.text# 循环获取每一季的内容fornodeinnode_list:# 获取信息title=node.xpath('@title')[0]link=node.xpath('@href')[0]items["title"]=title# 通过获取的单季链接跳转到本季的详情页面requestDetail=urllib.request.Request("https://www.meijutt.com"+link,headers=headers)htmlDetail=urllib.request.urlopen(requestDetail).readtextDetail=etree.HTML(htmlDetail)node_listDetail=textDetail.xpath('//div[@class="tabs-list current-tab"]//strong//a/@href')self.writeDetailPage(items,node_listDetail)# 爬取完毕提示ifself.page==int(pageTotal):self.infoSearchDoneelse:self.infoSearchContinue(pageTotal)# 将数据显示到图形界面defwriteDetailPage(self,items,node_listDetail):forindex,nodeLinkinenumerate(node_listDetail):items["link"]=nodeLink# 写入图形界面self.textEdit.append("<div>""<font color='black' size='3'>"+items['name']+"</font>"+"\n""<font color='red' size='3'>"+items['title']+"</font>"+"\n""<font color='orange' size='3'>第"+str(index+1)+"集</font>"+"\n""<font color='green' size='3'>下载链接:</font>"+"\n""<font color='blue' size='3'>"+items['link']+"</font>""<p></p>""</div>")# 搜索不到结果的提示信息definfoSearchNull(self):QMessageBox.information(self,'提示','搜索结果不存在,请重新输入搜索内容',QMessageBox.Ok,QMessageBox.Ok)# 爬取数据完毕的提示信息definfoSearchDone(self):QMessageBox.information(self,'提示','爬取《'+self.textLineEdit.text+'》完毕',QMessageBox.Ok,QMessageBox.Ok)# 多页情况下是否继续爬取的提示信息definfoSearchContinue(self,pageTotal):end=QMessageBox.information(self,'提示','爬取第'+str(self.page)+'页《'+self.textLineEdit.text+'》完毕,还有'+str(int(pageTotal)-self.page)+'页,是否继续爬取',QMessageBox.Ok|QMessageBox.No,QMessageBox.No)ifend==QMessageBox.Ok:self.page+=1self.loadSearchPage(self.textLineEdit.text,self.page)else:passif__name__=='__main__':app=QApplication(sys.argv)win=TextEditMeiJuwin.showsys.exit(app.exec_)
以上是实现功能的所有代码,可以运行 Python 的小伙伴直接复制到本地运行即可。都说 Python 是做爬虫最好的工具,写完之后发现确实是这样。
我们一点点分析代码:
import urllib.requestfrom urllib import parsefrom lxml import etreeimport sslfrom PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QTextEdit, QVBoxLayout, QPushButton, QMessageBox, QLabelimport sys
以上为我们引入的所需要的库,前 4 行是爬取 美剧天堂 官网所需要的库,后两个是实现图形化应用所需的库。
我们先来看一下如何爬取网站信息。
由于现在 美剧天堂 使用的是 https 协议,进入页面需要代理验证,为了不必要的麻烦,我们干脆取消代理验证,所以用到了 ssl 模块。
然后我们就可以正大光明的进入网站了:
令人遗憾的是 url 链接为 ,显然没有为我们提供任何有用的信息,当我们刷新页面时,如下图:
当我们手动输入 ulr 链接 进行搜索时:
很明显了,当我们在首页输入想看的美剧并搜索时网站将我们的请求表单信息隐藏了,并没有给到 url 链接里,但是本人可不想每次都从首页进行搜索再提交表单获取信息,很不爽,还好本人发现了一个更好的方法。如下图:
在页面顶部有一个页面跳转的按钮,我们可以选择跳转的页码,当选择跳转页码后,页面变成了如下:
url 链接已经改变了:https://www.meijutt.com/search/index.asp?page=&searchword=%C8%A8%C1%A6%B5%C4%D3%CE%CF%B7&searchtype=-1
我们再将 page 中动态添加为 page=1 ,页面效果不变。
经过搜索多个不同的美剧的多次验证发现只有 page 和 searchword 这两个字段是改变的,其中 page 字段默认为 1 ,而其本人搜索了许多季数很长的美剧,比如《老友记》、《生活大爆炸》、《邪恶力量》,这些美剧也就一页,但仍有更长的美剧,比如《辛普森一家》是两页,《法律与秩序》是两页,这就要求我们对页数进行控制,但是需要特别注意的是如果随意搜索内容,比如在搜索框只搜索了一个 ”i“,整整搜出了 219 页,这要扒下来需要很长的时间,所以就需要对其搜索的页数进行控制。
我们再来看一下 searchword 字段,将 searchword 字段解码转成汉字:
没错,正是我们想要的,万里长征终于实现了第一步。
# 加载输入美剧名称后的页面def loadSearchPage(self, name, page):# 将文本转为 gb2312 编码格式name = parse.quote(name.encode('gb2312'))# 请求发送的 url 地址url = "https://www.meijutt.com/search/index.asp?page=" + str(page) + "&searchword=" + name + "&searchtype=-1"# 请求报头headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}# 发送请求request = urllib.request.Request(url, headers=headers)# 获取请求的 html 文档html = urllib.request.urlopen(request).read# 对 html 文档进行解析text = etree.HTML(html)# xpath 获取想要的信息pageTotal = text.xpath('//div[@class="page"]/span[1]/text')# 判断搜索内容是否有结果if pageTotal:self.loadDetailPage(pageTotal, text, headers)# 搜索内容无结果else:self.infoSearchNull
接下来我们只需要将输入的美剧名转化成 url 编码格式就可以了。如上代码,通过 urllib 库对搜索的网站进行操作。
其中我们还需要做判断,搜索结果是否存在,比如我们搜索 行尸跑肉,结果不存在。
当搜索结果存在时:
我们通过谷歌的 xpath 插件对页面内的 dom 进行搜索,发现我们要选取的 class 类名,关于谷歌插件本人之前的文章讲过一些,这里就不多说了。
我们根据获取到的页数,找到所有页面里我们要搜索的信息:
# 加载点击搜索页面点击的本季页面def loadDetailPage(self, pageTotal, text, headers):# 取出搜索的结果一共多少页pageTotal = pageTotal[0].split('/')[1].rstrip("页")# 获取每一季的内容(剧名和链接)node_list = text.xpath('//a[@class="B font_14"]')items = {}items['name'] = self.textLineEdit.text# 循环获取每一季的内容for node in node_list:# 获取信息title = node.xpath('@title')[0]link = node.xpath('@href')[0]items["title"] = title# 通过获取的单季链接跳转到本季的详情页面requestDetail = urllib.request.Request("https://www.meijutt.com" + link, headers=headers)htmlDetail = urllib.request.urlopen(requestDetail).readtextDetail = etree.HTML(htmlDetail)node_listDetail = textDetail.xpath('//div[@class="tabs-list current-tab"]//strong//a/@href')self.writeDetailPage(items, node_listDetail)# 爬取完毕提示if self.page == int(pageTotal):self.infoSearchDoneelse:self.infoSearchContinue(pageTotal)
我们根据获取到的链接,再次通过 urllib 库进行页面访问,即我们手动点击进入其中的一个页面,比如 权利的游戏第一季,再次通过 xpath 获取到我们所需要的下载链接:
至此我们就将所有我们搜索到的 权力的游戏 的下载链接拿到手了,接下来就是写图形界面了。
本人选用了 PyQt5 这个框架,它内置了 QT 的操作语法,对于本人这种小白用起来也很友好。至于如何使用本人也都在代码上添加了注释,在这儿做一下简单的说明,就不过多解释了。
from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QTextEdit, QVBoxLayout, QPushButton, QMessageBox, QLabelimport sys
将获取的信息写入搜索结果内:
# 将数据显示到图形界面def writeDetailPage(self, items, node_listDetail):for index, nodeLink in enumerate(node_listDetail):items["link"] = nodeLink# 写入图形界面self.textEdit.append("<div>""<font color='black' size='3'>" + items['name'] + "</font>" + "\n""<font color='red' size='3'>" + items['title'] + "</font>" + "\n""<font color='orange' size='3'>第" + str(index + 1) + "集</font>" + "\n""<font color='green' size='3'>下载链接:</font>" + "\n""<font color='blue' size='3'>" + items['link'] + "</font>""<p></p>""</div>")
因为可能有多页情况,所以我们得做一次判断,提示一下剩余多少页,可以选择继续爬取或停止,做到人性化交互。
# 搜索不到结果的提示信息def infoSearchNull(self):QMessageBox.information(self, '提示', '搜索结果不存在,请重新输入搜索内容',QMessageBox.Ok, QMessageBox.Ok)# 爬取数据完毕的提示信息def infoSearchDone(self):QMessageBox.information(self, '提示', '爬取《' + self.textLineEdit.text + '》完毕',QMessageBox.Ok, QMessageBox.Ok)# 多页情况下是否继续爬取的提示信息def infoSearchContinue(self, pageTotal):end = QMessageBox.information(self, '提示', '爬取第' + str(self.page) + '页《' + self.textLineEdit.text + '》完毕,还有' + str(int(pageTotal) - self.page) + '页,是否继续爬取',QMessageBox.Ok | QMessageBox.No, QMessageBox.No)if end == QMessageBox.Ok:self.page += 1self.loadSearchPage(self.textLineEdit.text, self.page)else:pass
demo 图形化软件操作如下:
在搜索框内输入要搜索的美剧名,点击确认。提示一下是否要爬取,点击 No 不爬取,点击 OK 爬取。
判断一下是否存在搜索结果,比如吧 ”辛普森一家“ 换成了 ”吉普森一家“,搜索内容不存在。
如果搜索内容存在,在搜索完成第一页后提示一下是否需要继续爬取,点击 No 表示停止爬取,点击 OK 表示继续爬取。
最后爬取完毕后提示爬取完毕:
由于本人对 Python 了解不深,代码中有很多不足之处,需要不断学习改进,代码中有任何要改进的地方请各位大佬批评指教!