天天看點

Python 網絡爬蟲實戰:爬取并下載下傳《電影天堂》3千多部動作片電影

不知不覺,玩爬蟲玩了一個多月了。

我愈發覺得,爬蟲其實并不是什麼特别高深的技術,它的價值不在于你使用了什麼特别牛的架構,用了多麼了不起的技術,它不需要。它隻是以一種自動化搜集資料的小工具,能夠擷取到想要的資料,就是它最大的價值。

我的爬蟲課老師也常跟我們強調,學習爬蟲最重要的,不是學習裡面的技術,因為前端技術在不斷的發展,爬蟲的技術便會随着改變。學習爬蟲最重要的是,學習它的原理,萬變不離其宗。

爬蟲說白了是為了解決需要,友善生活的。如果能夠在日常生活中,想到并應用爬蟲去解決實際的問題,那麼爬蟲的真正意義也久發揮出來了。

這是些閑話啦,有感而發而已。

最近有點片荒,不知道該看什麼電影,而且有些電影在網上找好久也找不到資源。後來我了解到這個網站,發現最近好多不錯的電影上面都有資源(這裡我就先不管它的來源正不正規啦,#掩面)。

是以這次我們要爬取的網站是:《電影天堂》,屯一些電影,等無聊的時候拿出來看看,消遣消遣也是不錯。

Python 網絡爬蟲實戰:爬取并下載下傳《電影天堂》3千多部動作片電影

這次的網站,從爬蟲的技術角度上來講,難度不大,而且可以說是非常簡單了。但是,它實用啊!你想嘛,早上上班前跑一下爬蟲,晚上回家以後已經有幾十部最新大片在你硬碟裡等着你啦,累了一天躺床上看看電影,這種感覺是不是很爽啊。

而且正因為這個爬蟲比較簡單,是以我會寫的稍微細一點,争取讓 python 小白們也能盡可能看懂,并且能夠在這個爬蟲的基礎上修改,得到爬取這個網站其他闆塊或者其他電影網站的爬蟲。

寫在前面的話

在編寫爬蟲程式之前,我先捋一捋我們的思路。

  1. 爬蟲的原理,是通過給定的一個 URL(就是類似于 http://www.baidu.com 這樣的,俗稱網址的東東) 請求,去通路一個網頁,擷取那個網頁上的源代碼(不知道源代碼的,随便打開一個網頁,右鍵,檢視網頁源代碼,出來的一大堆像亂碼一樣的東西就是網頁源代碼,我們需要的資料就藏在這些源代碼裡面)并傳回來。
  2. 然後,通過一些手段(比如說json庫,BeautifulSoup庫,正規表達式等)從網頁源代碼中篩選出我們想要的資料(當然,前提是我們需要分析網頁結構,知道自己想要什麼資料,以及這些資料存放在網頁的哪兒,存放的位置有什麼特征等)。
  3. 最後,将我們擷取到的資料按照一定的格式,存儲到本地或者資料庫中,這樣就完成了爬蟲的全部工作。

當然,也有一些 「騷操作」,如果你嫌爬蟲效率低,可以開多線程(就是相當于幾十隻爬蟲同時給你爬,效率直接翻了幾十倍);如果擔心爬取頻率過高被網站封 IP,可以挂 IP 代理(相當于打幾槍換個地方,對方網站就不知道你究竟是爬蟲還是正常通路的使用者了);如果對方網站有反爬機制,那麼也有一些騷操作可以繞過反爬機制(有點黑客攻防的感覺,有木有!)。這些都是後話了。

爬蟲部分

一、分析網站結構(以動作片電影為例)

1. 分析網頁的 URL 的組成結構

首先,我們需要分析網頁的 URL 的組成結構,主要關注兩方面,一是如何切換選擇的電影類型,二是網頁如何翻頁的。

  • 點選網頁上的電影類型的按鈕,觀察位址欄中的 URL ,發現網址和電影類型的關系如下:
電影類型 網址
劇情片 https://www.dy2018.com/0/
喜劇片 https://www.dy2018.com/1/
動作片 https://www.dy2018.com/2/
愛情片 https://www.dy2018.com/3/
科幻片 https://www.dy2018.com/4/
卡通片 https://www.dy2018.com/5/
懸疑片 https://www.dy2018.com/6/
驚悚片 https://www.dy2018.com/7/
恐怖片 https://www.dy2018.com/8/
記錄片 https://www.dy2018.com/9/
...... ......
災難片 https://www.dy2018.com/18/
武俠片 https://www.dy2018.com/19/
古裝片 https://www.dy2018.com/20/

發現規律了吧,以後如果想爬其他類型的電影,隻要改變 url 中的數字即可,甚至你可以寫一個循環,把所有闆塊中的電影全部爬取下來。

  • 随便打開一個分類,我們滾動到頁面的最下面,發現這裡有翻頁的按鈕,點選按鈕翻頁的同時,觀察 url 的變化。
頁碼 URL
第一頁 https://www.dy2018.com/2/index.html
第二頁 https://www.dy2018.com/2/index_2.html
第三頁 https://www.dy2018.com/2/index_3.html
第四頁 https://www.dy2018.com/2/index_4.html

除了第一頁是 「index」外,其餘頁碼均是 「index_頁碼」的形式。

是以我們基本掌握了網站的 url 的構成形式,這樣我們就可以通過自己構造 url 來通路任意類型電影的任意一頁了,是不是很酷。

2. 分析網站的頁面結構

其次,我們分析一下網站的頁面結構,看一看我們需要的資訊都藏在網頁的什麼地方(在這之前我們先要明确一下我們需要哪些資料),由于我們這個目的是下載下傳電影,是以對我有用的資料隻有兩個,電影名稱和下載下傳電影的磁力連結。

按 F12 召喚出開發者工具(這個工具可以幫助你快速定位網頁中的元素在 html 源代碼中位置)。

Python 網絡爬蟲實戰:爬取并下載下傳《電影天堂》3千多部動作片電影

然後,我們可以發現,電影清單中,每一部電影的資訊存放在一個 <table> 标簽裡,而電影的名字,就藏在裡面的一個 <a> 标簽中。電影下載下傳的磁力連結在電影的詳情頁面,而電影詳情頁面的網址也在這個 <a> 标簽中( href 屬性的值)。

Python 網絡爬蟲實戰:爬取并下載下傳《電影天堂》3千多部動作片電影

而下載下傳的磁力連結,存放在 <tbody> 标簽下的 <a> 标簽中,是不是很好找啊!

最後我們來縷一縷思路,一會兒我們準備這樣操作:通過前面的網址的構造規則,通路到網站的某一頁,然後擷取到這個頁面裡的所有 table 标簽(這裡存放着電影的資料),然後從每一個 table 标簽中找到存有電影名稱的 a 标簽(這裡可以拿到電影名稱以及詳情頁面的網址),然後通過這裡擷取的網址通路電影的詳情頁面,在詳情頁面挑選出 <tbody> 标簽下的 <a> 标簽(這裡存放着電影的下載下傳連結),這樣我們就找到了我們所需要的全部資料了,是不是很簡單啊。

二、爬蟲編碼階段 

爬蟲的程式,我一般習慣把它分成五個部分, 一是主函數,作為程式的入口,二是爬蟲排程器,三是網絡請求函數,四是網頁解析函數,五是資料存儲函數。

  • get_data :其參數是目标網頁 url,這個函數可以模拟浏覽器通路 url,擷取并将網頁的内容傳回。
  • parse_data :其參數是網頁的内容,這個函數主要是用來解析網頁内容,篩選提取出關鍵的資訊,并打包成清單傳回。
  • save_data :其參數是資料的清單,這個函數用來将清單中的資料寫入本地的檔案中。
  • main :這個函數是爬蟲程式的排程器,可以根據事先分析好的 url 的規則,不斷的構造新的請求 url,并調用其他三個函數,擷取資料并儲存到本地,直到結束。
  • if __name__ == '__main__' :這是主程式的入口,在這裡調用 main 函數,啟動爬蟲排程器即可。
# 我們用到的庫
import requests
import bs4
import re
import pandas as pd
           

 1. 網絡請求函數 :get_data (url)

負責通路指定的 url 網頁,并将網頁的内容傳回,此部分功能比較簡單固定,一般不需要做修改(除非你要挂代理,或者自定義請求頭等,可以做一些相應的調整)。

def get_data(url):
    '''
    功能:通路 url 的網頁,擷取網頁内容并傳回
    參數:
        url :目标網頁的 url
    傳回:目标網頁的 html 内容
    '''
    headers = {
        'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
    }
 
    try:
        r = requests.get(url, headers=headers)
        r.raise_for_status()
        return r.text
    
    except requests.HTTPError as e:
        print(e)
        print("HTTPError")
    except requests.RequestException as e:
        print(e)
    except:
        print("Unknown Error !")
           

2. 網頁解析函數:parse_data(html)

 這個函數是整個爬蟲程式的核心所在,整體思路在上一部分已經講過了。我這裡使用的庫是 BeautifulSoup。

這部分的寫法多種多樣,有很多發揮的空間,也沒有什麼太多固定的模式,因為這部分的寫法是要随着不同網站的頁面結構來做調整的,比如說有的網站提供了資料的 api 接口,那麼傳回的資料就是 json 格式,我們隻需要調用 json 庫就可以完成資料解析,而大部分的網站隻能通過從網頁源代碼中一層層篩選(篩選手段也多種多樣,什麼正規表達式,beautifulsoup等等)。

這裡需要根據資料的形式來選擇不同的篩選政策,是以,知道原理就可以了,習慣什麼方法就用什麼方法,反正最後能拿到資料就好了。

def parse_data(html):
    '''
    功能:提取 html 頁面資訊中的關鍵資訊,并整合一個數組并傳回
    參數:html 根據 url 擷取到的網頁内容
    傳回:存儲有 html 中提取出的關鍵資訊的數組
    '''
    bsobj = bs4.BeautifulSoup(html,'html.parser')
    info = []
    
    # 擷取電影清單
    tbList = bsobj.find_all('table', attrs = {'class': 'tbspan'})
    
    # 對電影清單中的每一部電影單獨處理
    for item in tbList:

        movie = []
        link = item.b.find_all('a')[1]

        # 擷取電影的名稱
        name = link["title"]

        # 擷取詳情頁面的 url
        url = 'https://www.dy2018.com' + link["href"]

        # 将資料存放到電影資訊清單裡
        movie.append(name)
        movie.append(url)
        
        try:
            # 通路電影的詳情頁面,查找電影下載下傳的磁力連結
            temp = bs4.BeautifulSoup(get_data(url),'html.parser')
            tbody = temp.find_all('tbody')
            
            # 下載下傳連結有多個(也可能沒有),這裡将所有連結都放進來
            for i in tbody:
                download = i.a.text
                movie.append(download)
                
            #print(movie)

            # 将此電影的資訊加入到電影清單中
            info.append(movie)
            
        except Exception as e:
            print(e)
    
    return info
           

3. 資料存儲函數:save_data(data)

 這個函數目的是将資料存儲到本地檔案或資料庫中,具體的寫法要根據實際需要的存儲形式來定,我這裡是将資料存放在本地的 csv 檔案中。

當然這個函數也并不隻能做這些事兒,比如你可以在這裡寫一些簡單的資料處理的操作,比如說:資料清洗,資料去重等操作。

def save_data(data):
    '''
    功能:将 data 中的資訊輸出到檔案中/或資料庫中。
    參數:data 将要儲存的資料  
    '''
    filename = 'Data/電影天堂/動作片.csv'
    
    dataframe = pd.DataFrame(data)
    dataframe.to_csv(filename, mode='a', index=False, sep=',', header=False)
           

4. 爬蟲排程器:main()

這個函數負責根據 url 生成規則,構造新的 url 請求,然後依次調用網絡請求函數,網頁解析函數,資料存儲函數,爬取并儲存該頁資料。

所謂爬蟲排程器,就是控制爬蟲什麼時候開始爬,多少隻爬蟲一起爬,爬哪個網頁,爬多久休息一次,等等這些事兒。

def main():
    # 循環爬取多頁資料
    for page in range(1, 114):
        print('正在爬取:第' + str(page) + '頁......')       
        # 根據之前分析的 URL 的組成結構,構造新的 url
        if page == 1:
            index = 'index'
        else:
            index = 'index_' + str(page)            
        url = 'https://www.dy2018.com/2/'+ index +'.html'
        # 依次調用網絡請求函數,網頁解析函數,資料存儲函數,爬取并儲存該頁資料
        html = get_data(url)
        movies = parse_data(html)
        save_data(movies)
        
        print('第' + str(page) + '頁完成!')
           

5. 主函數:程式入口

主函數作為程式的入口,隻負責啟動爬蟲排程器。

這裡我一般習慣在 main() 函數前後輸出一條語句,以此判斷爬蟲程式是否正常啟動和結束。

if __name__ == '__main__':
    print('爬蟲啟動成功!')
    main()
    print('爬蟲執行完畢!')
           

三、程式運作結果

 運作了兩個小時左右吧,終于爬完了 113 頁,共 3346 部動作片電影的資料(本來不止這些的,但是有一些電影沒有提供下載下傳連結,我在 excel 中排序後直接手動剔除了)。

Python 網絡爬蟲實戰:爬取并下載下傳《電影天堂》3千多部動作片電影
Python 網絡爬蟲實戰:爬取并下載下傳《電影天堂》3千多部動作片電影

然後想看什麼電影的話,直接複制這些電影下載下傳的磁力連結,到迅雷裡面下載下傳就好啦。 

四、爬蟲程式的一些小優化

1. 在網站提供的下載下傳連結中,我試了一下,發現 magnet 開頭的這類連結放在迅雷中可以直接下載下傳,而 ftp 開頭的連結在迅雷中總顯示資源擷取失敗(我不知道是不是我打開的方式不對,反正就是下載下傳不來),于是我對程式做了一些小的調整,使其隻擷取 magnet 這類的連結。

修改的方式也很簡單,隻需要調整 網頁解析函數 即可(爬蟲的五個部分是相對獨立的,修改時隻需調整相應的子產品即可,其餘部分無需修改)。

def parse_data(html):
    '''
    功能:提取 html 頁面資訊中的關鍵資訊,并整合一個數組并傳回
    參數:html 根據 url 擷取到的網頁内容
    傳回:存儲有 html 中提取出的關鍵資訊的數組
    '''
    bsobj = bs4.BeautifulSoup(html,'html.parser')
    info = []
    
    # 擷取表頭資訊
    tbList = bsobj.find_all('table', attrs = {'class': 'tbspan'})
    
    for item in tbList:
        movie = []
        link = item.b.find_all('a')[1]
        name = link["title"]
        url = 'https://www.dy2018.com' + link["href"]
        
        try:
            # 查找電影下載下傳的磁力連結
            temp = bs4.BeautifulSoup(get_data(url),'html.parser')
            tbody = temp.find_all('tbody')
            
            for i in tbody:
                download = i.a.text
                if 'magnet:?xt=urn:btih' in download:
                    movie.append(name)
                    movie.append(url)
                    movie.append(download)
                    #print(movie)
                    info.append(movie)
                    break
        except Exception as e:
            print(e)
    
    return info
           

 注意代碼 26 行處,我加了一個 if 語句的判斷,如果下載下傳連結中包含 magnet:?xt=urn:btih 字元串,則視為有效連結,下載下傳下來,否則跳過。

2. 我一直在想能不能有個辦法讓迅雷一鍵批量下載下傳我們爬到的電影。使用 python 操縱第三方的軟體,這其實挺難的。不過後來找到了一種方法,也算是解決了這個問題。

就是我們發現迅雷軟體啟動後,會自動檢測我們的剪切闆,隻要我們複制了下載下傳連結,它便會自動彈出下載下傳的提示框。借助這個思路,我們可以使用代碼,将下載下傳的連結複制進入剪切闆,等下載下傳框自動出現後,手動确認開始下載下傳(這是我目前想到的最好的辦法了,不知道各位大佬有沒有更好的思路,歡迎指導交流)。

import pyperclip
import os
import pandas as pd

imageData = pd.read_csv("Data/電影天堂/動作片2.csv",names=['name','link','download'],encoding = 'gbk')
# 擷取電影的下載下傳連結,并用換行符分隔
a_link = imageData['download']
links = '\n'.join(a_link)

# 複制到剪切闆
pyperclip.copy(links);
print('已粘貼');
 
# 打開迅雷
thunder_path = r'D:\Program Files (x86)\Thunder Network\Thunder9\Program\Thunder.exe'
os.startfile(thunder_path)
           

親測可以實作,但是。。。不建議嘗試(你能想象迅雷打開的一瞬間建立幾百個下載下傳任務的場景嗎?反正我的電腦是緩了好久好久才反應過來)。大家還是老老實實的,手動複制連結下載下傳吧(csv檔案可以用 excel 打開,豎着選中一列,然後複制,也能達到相同的效果) ,這種騷操作太蠢了還是不要試了。

寫在後面的話

 啰啰嗦嗦的寫了好多,也不知道關鍵的問題講清楚了沒有。有哪裡沒講清楚,或者哪裡講的不合适的話,歡迎騷擾。

其實吧,寫文章,寫部落格,寫教程,都是一個知識重新熔煉内化的過程,在寫這篇部落格的時候,我也一直在反複審視我學習爬蟲的過程,以及我爬蟲代碼一步步的變化,從一開始的所有代碼全部揉在主函數中,到後來把一些變動較少的功能提取出來,寫成單獨的函數,再到後來形成基本穩定的五大部分。

以至于在我後來學習使用 scrapy 架構時候,驚人的發現 scrapy 架構的結構跟我的爬蟲結構有着異曲同工之妙,我的這個相當于是一個簡易版的爬蟲架構了,純靠自己摸索達到這個效果,我感覺還是挺有成就感的。