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 無此提示。

 

好記性不如爛筆頭,特此記錄,與君共勉!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM