Python 實現(xiàn)視頻爬取下載及斷點續(xù)傳優(yōu)化五鲫、異步下載

轉(zhuǎn)載自公眾號:FightingCoder

一般情況下我們使用爬蟲更多的應(yīng)該是爬數(shù)據(jù)或者圖片吧,今天在這里和大家分享一下關(guān)于使用爬蟲技術(shù)來進行視頻下載的方法,不僅可以方便的下載一些體積小的視頻,針對大容量的視頻下載同樣試用滓技。

image

先上個??

requests模塊的iter_content方法

這里我們使用的是python的requests模塊作為例子,需要獲取文本的時候我們會使用response.text獲取文本信息,使用response.content獲取字節(jié)流,比如下載圖片保存到一個文件,而對于大個的文件我們就要采取分塊讀取的方式了,

requests.get方法的stream

第一步吟温,我們需要設(shè)置requests.get的stream參數(shù)為True侦啸。
默認情況下是stream的值為false,它會立即開始下載文件并存放到內(nèi)存當中谬返,倘若文件過大就會導(dǎo)致內(nèi)存不足的情況.
當把get函數(shù)的stream參數(shù)設(shè)置成True時之斯,它不會立即開始下載,當你使用iter_content或iter_lines遍歷內(nèi)容或訪問內(nèi)容屬性時才開始下載遣铝。需要注意一點:文件沒有下載之前佑刷,它也需要保持連接。

iter_content:一塊一塊的遍歷要下載的內(nèi)容
iter_lines:一行一行的遍歷要下載的內(nèi)容

使用上面兩個函數(shù)下載大文件可以防止占用過多的內(nèi)存酿炸,因為每次只下載小部分數(shù)據(jù)瘫絮。
示例代碼:

r = requests.get(url_file, stream=True)
f = open("file_path", "wb")
for chunk in r.iter_content(chunk_size=512):
     if chunk:
        f.write(chunk)

上面的代碼表示請求了url_file,這個url_file是一個大文件,所以開啟了stream模式填硕,然后通過迭代r對象的iter_content方法麦萤,同時指定chunk_size=512(即每次讀取512個字節(jié))來進行讀取鹿鳖。但是如果僅僅是迭代是不行,如果下載中途出現(xiàn)問題我們之前的努力就白費了壮莹,所以我們需要做到一個斷點續(xù)傳的功能翅帜。

斷點續(xù)傳

所謂斷點續(xù)傳,也就是要從文件已經(jīng)下載的地方開始繼續(xù)下載命满。在以前版本的 HTTP 協(xié)議是不支持斷點的涝滴,HTTP/1.1 開始就支持了。一般斷點下載時會用到 header請求頭的Range字段胶台,這也是現(xiàn)在眾多號稱多線程下載工具(如 FlashGet歼疮、迅雷等)實現(xiàn)多線程下載的核心所在。

image

如何在代碼中實現(xiàn)用呢诈唬,來接著往下看

HTTP請求頭Range

range是請求資源的部分內(nèi)容(不包括響應(yīng)頭的大泻唷),單位是byte铸磅,即字節(jié)骤素,從0開始.
如果服務(wù)器能夠正常響應(yīng)的話,服務(wù)器會返回 206 Partial Content 的狀態(tài)碼及說明.
如果不能處理這種Range的話愚屁,就會返回整個資源以及響應(yīng)狀態(tài)碼為 200 OK .(這個要注意,要分段下載時痕檬,要先判斷這個)

Range請求頭格式

Range: bytes=start-end

Range頭域

Range頭域可以請求實體的一個或者多個子范圍霎槐。例如,
表示頭500個字節(jié):bytes=0-499
表示第二個500字節(jié):bytes=500-999
表示最后500個字節(jié):bytes=-500
表示500字節(jié)以后的范圍:bytes=500-
第一個和最后一個字節(jié):bytes=0-0,-1
同時指定幾個范圍:bytes=500-600,601-999
例如

Range: bytes=10- :第10個字節(jié)及最后個字節(jié)的數(shù)據(jù)
Range: bytes=40-100 :第40個字節(jié)到第100個字節(jié)之間的數(shù)據(jù).

注意梦谜,這個表示[start,end]丘跌,即是包含請求頭的start及end字節(jié)的,所以唁桩,下一個請求闭树,應(yīng)該是上一個請求的[end+1, nextEnd]

下載實例

下面我們通過具體的代碼去進一步了解一些細節(jié)。

import requests
import tqdm
 def download_from_url(url, dst):
    response = requests.get(url, stream=True) #(1)
    file_size = int(response.headers['content-length']) #(2)
    if os.path.exists(dst):
        first_byte = os.path.getsize(dst) #(3)
    else:
        first_byte = 0
    if first_byte >= file_size: #(4)
        return file_size
    header = {"Range": f"bytes={first_byte}-{file_size}"} 
    pbar = tqdm(
        total=file_size, initial=first_byte,
        unit='B', unit_scale=True, desc=dst)
    req = requests.get(url, headers=header, stream=True) #(5)
    with(open(dst, 'ab')) as f:
        for chunk in req.iter_content(chunk_size=1024): #(6)
            if chunk:
                f.write(chunk)
                pbar.update(1024)
    pbar.close()
    return file_size

下面我們開始解讀標有注釋的代碼:
tqdm是一個可以顯示進度條的包荒澡,具體的用法可以參考官網(wǎng)文檔:https://pypi.org/project/tqdm/
(1)設(shè)置stream=True參數(shù)讀取大文件报辱。
(2)通過header的content-length屬性可以獲取文件的總?cè)萘俊?br> (3)獲取本地已經(jīng)下載的部分文件的容量,方便繼續(xù)下載单山,當然需要判斷文件是否存在碍现,如果不存在就從頭開始下載。
(4)本地已下載文件的總?cè)萘亢途W(wǎng)絡(luò)文件的實際容量進行比較米奸,如果大于或者等于則表示已經(jīng)下載完成昼接,否則繼續(xù)。
(5)開始請求視頻文件了
(6)循環(huán)讀取每次讀取一個1024個字節(jié)悴晰,當然你也可以設(shè)置512個字節(jié)

效果演示

首先調(diào)用上面的方法并傳入?yún)?shù)慢睡。

url = "http://v11-tt.ixigua.com/7da2b219bc734de0f0d04706a9629b61/5c77ed4b/video/m/220d4f4e99b7bfd49efb110892d892bea9011612eb3100006b7bebf69d81/?rc=am12NDw4dGlqajMzNzYzM0ApQHRAbzU6Ojw8MzQzMzU4NTUzNDVvQGgzdSlAZjN1KWRzcmd5a3VyZ3lybHh3Zjc2QHFubHBfZDJrbV8tLTYxL3NzLW8jbyMxLTEtLzEtLjMvLTUvNi06I28jOmEtcSM6YHZpXGJmK2BeYmYrXnFsOiMzLl4%3D"
download_from_url(url, "夏目友人帳第一集.mp4")

在命令行中運行代碼之后看到效果如下

image

如果在pycharm直接運行的話是下面的效果

image

完全不一樣的效果逐工,個人感覺還是在pycharm里看著舒服,后面并發(fā)的時候看著也方便。
好了下面我們就打開我們的文件看看結(jié)果如何:

image

可以發(fā)現(xiàn)這個視頻被成功的下載下來漂辐,怎么樣激不動激不動啊泪喊。

image.gif

對于單文件的下載我們就完成,但是對于夏目友人帳這個動漫來說不只有一集,如果我們下載一個系列的話,我們就得使用并發(fā)了,這里我使用aiohttp把上面的代碼改成并發(fā)的版本。

使用aiohttp進行并發(fā)下載

import aiohttp
import asyncio
from tqdm import tqdm
async def fetch(session, url, dst, pbar=None, headers=None):
    if headers:
        async with session.get(url, headers=headers) as req:
            with(open(dst, 'ab')) as f:
                while True:
                    chunk = await req.content.read(1024)
                    if not chunk:
                        break
                    f.write(chunk)
                    pbar.update(1024)
            pbar.close()
    else:
        async with session.get(url) as req:
            return req


async def async_download_from_url(url, dst):
    '''異步'''
    async with aiohttp.connector.TCPConnector(limit=300, force_close=True, enable_cleanup_closed=True) as tc:
        async with aiohttp.ClientSession(connector=tc) as session:
            req = await fetch(session, url, dst)

            file_size = int(req.headers['content-length'])
            print(f"獲取視頻總長度:{file_size}")
            if os.path.exists(dst):
                first_byte = os.path.getsize(dst)
            else:
                first_byte = 0
            if first_byte >= file_size:
                return file_size
            header = {"Range": f"bytes={first_byte}-{file_size}"}
            pbar = tqdm(
                total=file_size, initial=first_byte,
                unit='B', unit_scale=True, desc=dst)
            await fetch(session, url, dst, pbar=pbar, headers=header)

上面的代碼功能和我們的同步代碼一樣的者吁,不同的是這里是異步的窘俺。

image.gif

并發(fā)下載演示

我們首先要拿到MP4的鏈接,然后進行下面的代碼即可

 task = [asyncio.ensure_future(async_download_from_url(url, f"{i}.mp4")) for i in range(1, 12)]
         loop = asyncio.get_event_loop()
         loop.run_until_complete(asyncio.wait(task))
         loop.close()

這里我同時下載了11次上面的那個視頻,命令為1-11,方便演示效果,好了下面我們就來看效果。

image

可以發(fā)現(xiàn)開始并發(fā)的下載了复凳。
完整代碼如下:

# -*- coding: utf-8 -*-
# @Time : 2019/2/13 8:17 PM
# @Author : cxa
# @File : mp4downloders.py
# @Software: PyCharm
import requests
from tqdm import tqdm
import os
import aiohttp
import asyncio

try:
    import uvloop

    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
except ImportError:
    pass


async def fetch(session, url, dst, pbar=None, headers=None):
    if headers:
        async with session.get(url, headers=headers) as req:
            with(open(dst, 'ab')) as f:
                while True:
                    chunk = await req.content.read(1024)
                    if not chunk:
                        break
                    f.write(chunk)
                    pbar.update(1024)
            pbar.close()
    else:
        async with session.get(url) as req:
            return req


async def async_download_from_url(url, dst):
    '''異步'''
    async with aiohttp.ClientSession() as session:
        req = await fetch(session, url, dst)

        file_size = int(req.headers['content-length'])
        print(f"獲取視頻總長度:{file_size}")
        if os.path.exists(dst):
            first_byte = os.path.getsize(dst)
        else:
            first_byte = 0
        if first_byte >= file_size:
            return file_size
        header = {"Range": f"bytes={first_byte}-{file_size}"}
        pbar = tqdm(
            total=file_size, initial=first_byte,
            unit='B', unit_scale=True, desc=dst)
        await fetch(session, url, dst, pbar=pbar, headers=header)


def download_from_url(url, dst):
    '''同步'''
    response = requests.get(url, stream=True)
    file_size = int(response.headers['content-length'])
    if os.path.exists(dst):
        first_byte = os.path.getsize(dst)
    else:
        first_byte = 0
    if first_byte >= file_size:
        return file_size
    header = {"Range": f"bytes={first_byte}-{file_size}"}
    pbar = tqdm(
        total=file_size, initial=first_byte,
        unit='B', unit_scale=True, desc=dst)
    req = requests.get(url, headers=header, timeout=60, stream=True)
    with(open(dst, 'ab')) as f:
        for chunk in req.iter_content(chunk_size=1024):
            if chunk:
                f.write(chunk)
                pbar.update(1024)
    pbar.close()
    return file_size


if __name__ == '__main__':
    # 異步方式下載
    url = "http://v11-tt.ixigua.com/7da2b219bc734de0f0d04706a9629b61/5c77ed4b/video/m/220d4f4e99b7bfd49efb110892d892bea9011612eb3100006b7bebf69d81/?rc=am12NDw4dGlqajMzNzYzM0ApQHRAbzU6Ojw8MzQzMzU4NTUzNDVvQGgzdSlAZjN1KWRzcmd5a3VyZ3lybHh3Zjc2QHFubHBfZDJrbV8tLTYxL3NzLW8jbyMxLTEtLzEtLjMvLTUvNi06I28jOmEtcSM6YHZpXGJmK2BeYmYrXnFsOiMzLl4%3D"
    task = [asyncio.ensure_future(async_download_from_url(url, f"{i}.mp4")) for i in range(1, 12)]
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(task))
    loop.close()
    # 注釋部分是同步方式下載瘤泪。
    # url = "http://v11-tt.ixigua.com/7da2b219bc734de0f0d04706a9629b61/5c77ed4b/video/m/220d4f4e99b7bfd49efb110892d892bea9011612eb3100006b7bebf69d81/?rc=am12NDw4dGlqajMzNzYzM0ApQHRAbzU6Ojw8MzQzMzU4NTUzNDVvQGgzdSlAZjN1KWRzcmd5a3VyZ3lybHh3Zjc2QHFubHBfZDJrbV8tLTYxL3NzLW8jbyMxLTEtLzEtLjMvLTUvNi06I28jOmEtcSM6YHZpXGJmK2BeYmYrXnFsOiMzLl4%3D"
    #
    # download_from_url(url, "夏目友人帳第一集.mp4")
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市育八,隨后出現(xiàn)的幾起案子对途,更是在濱河造成了極大的恐慌,老刑警劉巖髓棋,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件实檀,死亡現(xiàn)場離奇詭異,居然都是意外死亡按声,警方通過查閱死者的電腦和手機膳犹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來签则,“玉大人须床,你說我怎么就攤上這事〗チ眩” “怎么了豺旬?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長柒凉。 經(jīng)常有香客問我族阅,道長,這世上最難降的妖魔是什么膝捞? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任坦刀,我火速辦了婚禮,結(jié)果婚禮上蔬咬,老公的妹妹穿的比我還像新娘求泰。我一直安慰自己,他們只是感情好计盒,可當我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布渴频。 她就那樣靜靜地躺著,像睡著了一般北启。 火紅的嫁衣襯著肌膚如雪卜朗。 梳的紋絲不亂的頭發(fā)上拔第,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天,我揣著相機與錄音场钉,去河邊找鬼蚊俺。 笑死,一個胖子當著我的面吹牛逛万,可吹牛的內(nèi)容都是我干的泳猬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼宇植,長吁一口氣:“原來是場噩夢啊……” “哼得封!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起指郁,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤忙上,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后闲坎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疫粥,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年腰懂,在試婚紗的時候發(fā)現(xiàn)自己被綠了梗逮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡绣溜,死狀恐怖库糠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涮毫,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布贷屎,位于F島的核電站罢防,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏唉侄。R本人自食惡果不足惜咒吐,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望属划。 院中可真熱鬧恬叹,春花似錦、人聲如沸同眯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽硅确。三九已至目溉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間菱农,已是汗流浹背缭付。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留循未,地道東北人陷猫。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像的妖,于是被迫代替她去往敵國和親绣檬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內(nèi)容

  • 網(wǎng)絡(luò)下載是我們在項目中經(jīng)常要用到的功能羔味,如果是小文件的下載河咽,比如圖片和文字之類的,我們可以直接請求源地址赋元,然后一次...
    liang_1閱讀 2,451評論 0 53
  • 小文件下載如果文件比較小忘蟹,下載方式會比較多直接用NSData的+ (id)dataWithContentsOfUR...
    醉葉惜秋閱讀 857評論 0 0
  • API定義規(guī)范 本規(guī)范設(shè)計基于如下使用場景: 請求頻率不是非常高:如果產(chǎn)品的使用周期內(nèi)請求頻率非常高,建議使用雙通...
    有涯逐無涯閱讀 2,553評論 0 6
  • 本篇文章已授權(quán)微信公眾號 dasu_Android(大蘇)獨家發(fā)布 這次想來講講斷點續(xù)傳搁凸,以前沒相關(guān)需求媚值,所以一直...
    請叫我大蘇閱讀 4,323評論 1 19
  • 歡迎關(guān)注幼兒說褥芒,用簡書的媽咪,都是有品味的母親 最近嫡良,幼兒說小編在網(wǎng)絡(luò)上觀看了一個外國的視頻锰扶,看完后不禁感嘆,真的...
    幼兒說閱讀 425評論 0 0