Python + PyQt5 实现美剧爬虫可视工具

2023-11-23 00:30

本文主要是介绍Python + PyQt5 实现美剧爬虫可视工具,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

美剧《权力的游戏》终于要开播最后一季了,作为马丁老爷子的忠实粉丝,为了能够看得懂第八季复杂庞大的剧情架构,本人想着将前几季再稳固一下,所以就上美剧天堂下载来看,可是每次都上去下载太麻烦了,于是干脆自己写个爬虫爬下来得了。

 

话不多说,先上图片。

本人才疏学浅,就写了个简单的可视化软件,关键是功能实现就行了嘛。

 

实现语言:Python ,版本 3.7.1

 

实现思路:首先运用 Python 工具爬取到数据再实现图形化软件。

 

由于这里只是实现简单的爬取数据,并没有牵扯到 cookie 之类的敏感信息,也没有设置代理,所以在选择 Python 库上并没有引入 Selenium 或者更高级的 Scrapy 框架,只是拿到数据就可以了,没必要那么麻烦。

 

所以选择了 urllib 这个库,在 Python 2.X 中应该是 urllib 和 urllib2 同时引入,由于本人选用的版本的 Python 3.X ,在 Python 3.X 中上面两个库已经被合并为 urllib 一个库,语法上有些不同,但语言这种东西都是大同小异的嘛。

 

先贴代码,缓和一下尴尬的气氛。

  1 import urllib.request
  2 from urllib import parse
  3 from lxml import etree
  4 import ssl
  5 from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QTextEdit, QVBoxLayout, QPushButton, QMessageBox
  6 import sys
  7 
  8 # 取消代理验证
  9 ssl._create_default_https_context = ssl._create_unverified_context
 10 
 11 class TextEditMeiJu(QWidget):
 12     def __init__(self, parent=None):
 13         super(TextEditMeiJu, self).__init__(parent)
 14         # 定义窗口头部信息
 15         self.setWindowTitle('美剧天堂')
 16         # 定义窗口的初始大小
 17         self.resize(500, 600)
 18         # 创建单行文本框
 19         self.textLineEdit = QLineEdit()
 20         # 创建一个按钮
 21         self.btnButton = QPushButton('确定')
 22         # 创建多行文本框
 23         self.textEdit = QTextEdit()
 24         # 实例化垂直布局
 25         layout = QVBoxLayout()
 26         # 相关控件添加到垂直布局中
 27         layout.addWidget(self.textLineEdit)
 28         layout.addWidget(self.btnButton)
 29         layout.addWidget(self.textEdit)
 30         # 设置布局
 31         self.setLayout(layout)
 32         # 将按钮的点击信号与相关的槽函数进行绑定,点击即触发
 33         self.btnButton.clicked.connect(self.buttonClick)
 34 
 35     # 点击确认按钮
 36     def buttonClick(self):
 37         # 爬取开始前提示一下
 38         start = QMessageBox.information(
 39             self, '提示', '是否开始爬取《' + self.textLineEdit.text() + "",
 40             QMessageBox.Ok | QMessageBox.No, QMessageBox.Ok
 41         )
 42         # 确定爬取
 43         if start == QMessageBox.Ok:
 44             self.page = 1
 45             self.loadSearchPage(self.textLineEdit.text(), self.page)
 46         # 取消爬取
 47         else:
 48             pass
 49 
 50     # 加载输入美剧名称后的页面
 51     def loadSearchPage(self, name, page):
 52         # 将文本转为 gb2312 编码格式
 53         name = parse.quote(name.encode('gb2312'))
 54         # 请求发送的 url 地址
 55         url = "https://www.meijutt.com/search/index.asp?page=" + str(page) + "&searchword=" + name + "&searchtype=-1"
 56         # 请求报头
 57         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"}
 58         # 发送请求
 59         request = urllib.request.Request(url, headers=headers)
 60         # 获取请求的 html 文档
 61         html = urllib.request.urlopen(request).read()
 62         # 对 html 文档进行解析
 63         text = etree.HTML(html)
 64         # xpath 获取想要的信息
 65         pageTotal = text.xpath('//div[@class="page"]/span[1]/text()')
 66         # 判断搜索内容是否有结果
 67         if pageTotal:
 68             self.loadDetailPage(pageTotal, text, headers)
 69         # 搜索内容无结果
 70         else:
 71             self.infoSearchNull()
 72 
 73     # 加载点击搜索页面点击的本季页面
 74     def loadDetailPage(self, pageTotal, text, headers):
 75         # 取出搜索的结果一共多少页
 76         pageTotal = pageTotal[0].split('/')[1].rstrip("")
 77         # 获取每一季的内容(剧名和链接)
 78         node_list = text.xpath('//a[@class="B font_14"]')
 79         items = {}
 80         items['name'] = self.textLineEdit.text()
 81         # 循环获取每一季的内容
 82         for node in node_list:
 83             # 获取信息
 84             title = node.xpath('@title')[0]
 85             link = node.xpath('@href')[0]
 86             items["title"] = title
 87             # 通过获取的单季链接跳转到本季的详情页面
 88             requestDetail = urllib.request.Request("https://www.meijutt.com" + link, headers=headers)
 89             htmlDetail = urllib.request.urlopen(requestDetail).read()
 90             textDetail = etree.HTML(htmlDetail)
 91             node_listDetail = textDetail.xpath('//div[@class="tabs-list current-tab"]//strong//a/@href')
 92             self.writeDetailPage(items, node_listDetail)
 93         # 爬取完毕提示
 94         if self.page == int(pageTotal):
 95             self.infoSearchDone()
 96         else:
 97             self.infoSearchContinue(pageTotal)
 98 
 99     # 将数据显示到图形界面
100     def writeDetailPage(self, items, node_listDetail):
101         for index, nodeLink in enumerate(node_listDetail):
102             items["link"] = nodeLink
103             # 写入图形界面
104             self.textEdit.append(
105                 "<div>"
106                     "<font color='black' size='3'>" + items['name'] + "</font>" + "\n"
107                     "<font color='red' size='3'>" + items['title'] + "</font>" + "\n"
108                     "<font color='orange' size='3'>第" + str(index + 1) + "集</font>" + "\n"
109                     "<font color='green' size='3'>下载链接:</font>" + "\n"
110                     "<font color='blue' size='3'>" + items['link'] + "</font>"
111                     "<p></p>"
112                 "</div>"
113             )
114 
115     # 搜索不到结果的提示信息
116     def infoSearchNull(self):
117         QMessageBox.information(
118             self, '提示', '搜索结果不存在,请重新输入搜索内容',
119             QMessageBox.Ok, QMessageBox.Ok
120         )
121 
122     # 爬取数据完毕的提示信息
123     def infoSearchDone(self):
124         QMessageBox.information(
125             self, '提示', '爬取《' + self.textLineEdit.text() + '》完毕',
126             QMessageBox.Ok, QMessageBox.Ok
127         )
128 
129     # 多页情况下是否继续爬取的提示信息
130     def infoSearchContinue(self, pageTotal):
131         end = QMessageBox.information(
132             self, '提示', '爬取第' + str(self.page) + '页《' + self.textLineEdit.text() + '》完毕,还有' + str(int(pageTotal) - self.page) + '页,是否继续爬取',
133             QMessageBox.Ok | QMessageBox.No, QMessageBox.No
134         )
135         if end == QMessageBox.Ok:
136             self.page += 1
137             self.loadSearchPage(self.textLineEdit.text(), self.page)
138         else:
139             pass
140 
141 
142 if __name__ == '__main__':
143     app = QApplication(sys.argv)
144     win = TextEditMeiJu()
145     win.show()
146     sys.exit(app.exec_())

以上是实现功能的所有代码,可以运行 Python 的小伙伴直接复制到本地运行即可。都说 Python 是做爬虫最好的工具,写完之后发现确实是这样。

 

我们一点点分析代码:

1 import urllib.request
2 from urllib import parse
3 from lxml import etree
4 import ssl
5 from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QTextEdit, QVBoxLayout, QPushButton, QMessageBox, QLabel
6 import sys

以上为我们引入的所需要的库,前 4 行是爬取 美剧天堂 官网所需要的库,后两个是实现图形化应用所需的库。

 

我们先来看一下如何爬取网站信息。

 

由于现在 美剧天堂 使用的是 https 协议,进入页面需要代理验证,为了不必要的麻烦,我们干脆取消代理验证,所以用到了 ssl 模块。

 

然后我们就可以正大光明的进入网站了:https://www.meijutt.com/  

 

令人遗憾的是 url 链接为 https://www.meijutt.com/search/index.asp  ,显然没有为我们提供任何有用的信息,当我们刷新页面时,如下图:

 

当我们手动输入 ulr 链接 https://www.meijutt.com/search/index.asp  进行搜索时:

 

 

很明显了,当我们在首页输入想看的美剧并搜索时网站将我们的请求表单信息隐藏了,并没有给到 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 字段解码转成汉字:

没错,正是我们想要的,万里长征终于实现了第一步。

 1 # 加载输入美剧名称后的页面
 2 def loadSearchPage(self, name, page):
 3     # 将文本转为 gb2312 编码格式
 4     name = parse.quote(name.encode('gb2312'))
 5     # 请求发送的 url 地址
 6     url = "https://www.meijutt.com/search/index.asp?page=" + str(page) + "&searchword=" + name + "&searchtype=-1"
 7     # 请求报头
 8     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"}
 9     # 发送请求
10     request = urllib.request.Request(url, headers=headers)
11     # 获取请求的 html 文档
12     html = urllib.request.urlopen(request).read()
13     # 对 html 文档进行解析
14     text = etree.HTML(html)
15     # xpath 获取想要的信息
16     pageTotal = text.xpath('//div[@class="page"]/span[1]/text()')
17     # 判断搜索内容是否有结果
18     if pageTotal:
19         self.loadDetailPage(pageTotal, text, headers)
20     # 搜索内容无结果
21     else:
22         self.infoSearchNull()
23             

 接下来我们只需要将输入的美剧名转化成 url 编码格式就可以了。如上代码,通过 urllib 库对搜索的网站进行操作。

 

其中我们还需要做判断,搜索结果是否存在,比如我们搜索 行尸跑肉,结果不存在。

 

当搜索结果存在时:

 

我们通过谷歌的 xpath 插件对页面内的 dom 进行搜索,发现我们要选取的 class 类名,关于谷歌插件本人之前的文章讲过一些 https://www.cnblogs.com/weijiutao/p/10608107.html,这里就不多说了。

 

我们根据获取到的页数,找到所有页面里我们要搜索的信息:

 

 1 # 加载点击搜索页面点击的本季页面
 2 def loadDetailPage(self, pageTotal, text, headers):
 3     # 取出搜索的结果一共多少页
 4     pageTotal = pageTotal[0].split('/')[1].rstrip("")
 5     # 获取每一季的内容(剧名和链接)
 6     node_list = text.xpath('//a[@class="B font_14"]')
 7     items = {}
 8     items['name'] = self.textLineEdit.text()
 9     # 循环获取每一季的内容
10     for node in node_list:
11         # 获取信息
12         title = node.xpath('@title')[0]
13         link = node.xpath('@href')[0]
14         items["title"] = title
15         # 通过获取的单季链接跳转到本季的详情页面
16         requestDetail = urllib.request.Request("https://www.meijutt.com" + link, headers=headers)
17         htmlDetail = urllib.request.urlopen(requestDetail).read()
18         textDetail = etree.HTML(htmlDetail)
19         node_listDetail = textDetail.xpath('//div[@class="tabs-list current-tab"]//strong//a/@href')
20         self.writeDetailPage(items, node_listDetail)
21     # 爬取完毕提示
22     if self.page == int(pageTotal):
23         self.infoSearchDone()
24     else:
25         self.infoSearchContinue(pageTotal)
26         

 

我们根据获取到的链接,再次通过 urllib 库进行页面访问,即我们手动点击进入其中的一个页面,比如 权利的游戏第一季,再次通过 xpath 获取到我们所需要的下载链接:

 

 

至此我们就将所有我们搜索到的 权力的游戏 的下载链接拿到手了,接下来就是写图形界面了。

 

本人选用了 PyQt5 这个框架,它内置了 QT 的操作语法,对于本人这种小白用起来也很友好。至于如何使用本人也都在代码上添加了注释,在这儿做一下简单的说明,就不过多解释了。

 

1 from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QTextEdit, QVBoxLayout, QPushButton, QMessageBox, QLabel
2 import sys

 

将获取的信息写入搜索结果内:

 1 # 将数据显示到图形界面
 2 def writeDetailPage(self, items, node_listDetail):
 3     for index, nodeLink in enumerate(node_listDetail):
 4         items["link"] = nodeLink
 5         # 写入图形界面
 6         self.textEdit.append(
 7             "<div>"
 8                 "<font color='black' size='3'>" + items['name'] + "</font>" + "\n"
 9                 "<font color='red' size='3'>" + items['title'] + "</font>" + "\n"
10                 "<font color='orange' size='3'>第" + str(index + 1) + "集</font>" + "\n"
11                 "<font color='green' size='3'>下载链接:</font>" + "\n"
12                 "<font color='blue' size='3'>" + items['link'] + "</font>"
13                 "<p></p>"
14             "</div>"
15         )

 

因为可能有多页情况,所以我们得做一次判断,提示一下剩余多少页,可以选择继续爬取或停止,做到人性化交互。

 1 # 搜索不到结果的提示信息
 2 def infoSearchNull(self):
 3     QMessageBox.information(
 4         self, '提示', '搜索结果不存在,请重新输入搜索内容',
 5         QMessageBox.Ok, QMessageBox.Ok
 6     )
 7 
 8 # 爬取数据完毕的提示信息
 9 def infoSearchDone(self):
10     QMessageBox.information(
11         self, '提示', '爬取《' + self.textLineEdit.text() + '》完毕',
12         QMessageBox.Ok, QMessageBox.Ok
13     )
14 
15 # 多页情况下是否继续爬取的提示信息
16 def infoSearchContinue(self, pageTotal):
17     end = QMessageBox.information(
18         self, '提示', '爬取第' + str(self.page) + '页《' + self.textLineEdit.text() + '》完毕,还有' + str(int(pageTotal) - self.page) + '页,是否继续爬取',
19         QMessageBox.Ok | QMessageBox.No, QMessageBox.No
20     )
21     if end == QMessageBox.Ok:
22         self.page += 1
23         self.loadSearchPage(self.textLineEdit.text(), self.page)
24     else:
25         pass

 

demo 图形化软件操作如下:

 

在搜索框内输入要搜索的美剧名,点击确认。提示一下是否要爬取,点击 No 不爬取,点击 OK 爬取。

 

判断一下是否存在搜索结果,比如吧 ”辛普森一家“ 换成了 ”吉普森一家“,搜索内容不存在。

 

 

如果搜索内容存在,在搜索完成第一页后提示一下是否需要继续爬取,点击 No 表示停止爬取,点击 OK 表示继续爬取。

 

最后爬取完毕后提示爬取完毕:

 

由于本人对 Python 了解不深,代码中有很多不足之处,需要不断学习改进,代码中有任何要改进的地方请各位大佬批评指教!

 

最后本人做了一套包含 Mac 和 windows 版的图形化美剧天堂抓包程序,只需要在对应电脑上点击运行即可,需要的小伙伴可以在本人的公众号后台回复 美剧天堂  就可以拿到了,注:在 windows 上打包生成的 .exe 软件第一打开时被 360 阻止,大家允许操作就可以了,Mac 无此提示。

 

好记性不如烂笔头,特此记录,与君共勉!

 

转载于:https://www.cnblogs.com/weijiutao/p/10614694.html

这篇关于Python + PyQt5 实现美剧爬虫可视工具的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/414030

相关文章

MyBatis流式查询两种实现方式

《MyBatis流式查询两种实现方式》本文详解MyBatis流式查询,通过ResultHandler和Cursor实现边读边处理,避免内存溢出,ResultHandler逐条回调,Cursor支持迭代... 目录MyBATis 流式查询详解:ResultHandler 与 Cursor1. 什么是流式查询?

Springboot项目登录校验功能实现

《Springboot项目登录校验功能实现》本文介绍了Web登录校验的重要性,对比了Cookie、Session和JWT三种会话技术,分析其优缺点,并讲解了过滤器与拦截器的统一拦截方案,推荐使用JWT... 目录引言一、登录校验的基本概念二、HTTP协议的无状态性三、会话跟android踪技术1. Cook

C++归并排序代码实现示例代码

《C++归并排序代码实现示例代码》归并排序将待排序数组分成两个子数组,分别对这两个子数组进行排序,然后将排序好的子数组合并,得到排序后的数组,:本文主要介绍C++归并排序代码实现的相关资料,需要的... 目录1 算法核心思想2 代码实现3 算法时间复杂度1 算法核心思想归并排序是一种高效的排序方式,需要用

mybatis用拦截器实现字段加解密全过程

《mybatis用拦截器实现字段加解密全过程》本文通过自定义注解和MyBatis拦截器实现敏感信息加密,处理Parameter和ResultSet,确保数据库存储安全且查询结果解密可用... 目录前言拦截器的使用总结前言根据公司业务需要,灵活对客户敏感信息进行加解密,这里采用myBATis拦截器进行简单实

java实现多数据源切换方式

《java实现多数据源切换方式》本文介绍实现多数据源切换的四步方法:导入依赖、配置文件、启动类注解、使用@DS标记mapper和服务层,通过注解实现数据源动态切换,适用于实际开发中的多数据源场景... 目录一、导入依赖二、配置文件三、在启动类上配置四、在需要切换数据源的类上、方法上使用@DS注解结论一、导入

Python自定义异常的全面指南(入门到实践)

《Python自定义异常的全面指南(入门到实践)》想象你正在开发一个银行系统,用户转账时余额不足,如果直接抛出ValueError,调用方很难区分是金额格式错误还是余额不足,这正是Python自定义异... 目录引言:为什么需要自定义异常一、异常基础:先搞懂python的异常体系1.1 异常是什么?1.2

更改linux系统的默认Python版本方式

《更改linux系统的默认Python版本方式》通过删除原Python软链接并创建指向python3.6的新链接,可切换系统默认Python版本,需注意版本冲突、环境混乱及维护问题,建议使用pyenv... 目录更改系统的默认python版本软链接软链接的特点创建软链接的命令使用场景注意事项总结更改系统的默

SQLServer中生成雪花ID(Snowflake ID)的实现方法

《SQLServer中生成雪花ID(SnowflakeID)的实现方法》:本文主要介绍在SQLServer中生成雪花ID(SnowflakeID)的实现方法,文中通过示例代码介绍的非常详细,... 目录前言认识雪花ID雪花ID的核心特点雪花ID的结构(64位)雪花ID的优势雪花ID的局限性雪花ID的应用场景

Linux升级或者切换python版本实现方式

《Linux升级或者切换python版本实现方式》本文介绍在Ubuntu/Debian系统升级Python至3.11或更高版本的方法,通过查看版本列表并选择新版本进行全局修改,需注意自动与手动模式的选... 目录升级系统python版本 (适用于全局修改)对于Ubuntu/Debian系统安装后,验证Pyt

Python实现开根号的五种方式

《Python实现开根号的五种方式》在日常数据处理、数学计算甚至算法题中,开根号是一个高频操作,但你知道吗?Python中实现开根号的方式远不止一种!本文总结了5种常用方法,感兴趣的小伙伴跟着小编一起... 目录一、为什么需要多种开根号方式?二、5种开根号方式详解方法1:数学库 math.sqrt() ——