上一篇文章只介紹了抖音的爬取過程,還有最後一步,那就是把目標影片下載下來。這裡來系統的講下媒體檔案的下載技巧,媒體檔案主要是 文字,圖片,影片等。這篇文章的目標就是把 文字,圖片, 影片儲存到本地,還有 多影片併發下載的方式。

文字儲存到本地

import json

import requests

from requests。exceptions import RequestException

from pyquery import PyQuery as pq

def get_response(url):

try:

response = requests。get(url)

if response。status_code == 200:

return response。text

return None

except RequestException as e:

print(‘err: %s’ % e)

def write_to_file(content):

with open(‘result。txt’,‘a’,encoding=‘utf-8’) as f:

f。write(json。dumps(content, ensure_ascii=False) + ‘\n’)

f。close()

def read_file():

with open(“。/result。txt”, encoding=‘UTF-8’) as file:

data = file。read()

return data

f。close()

def parse(html):

doc = pq(html)

lis = doc(‘。col-md-8 。quote’)

for item in lis:

text = pq(item)。find(‘。text’)。text()

author = pq(item)。find(‘。author’)。text()

tags = pq(item)。find(‘。tags 。tag’)。text()

yield {

‘text’: text,

‘author’: author,

‘tags’: tags

}

def write():

html = get_response(‘http://quotes。toscrape。com/’)

content = [item for item in parse(html)]

write_to_file(content)

def read():

res = json。loads(read_file())

for item in res:

print(item)

if __name__ == ‘__main__’:

write()

# read()

我這裡爬取Quotes to Scrape這個網站,簡單解析下,然後把json儲存到本地的result。txt檔案 ,因為有中文字元,需要指定encoding=‘utf-8’

def write_to_file(content):

with open(‘result。txt’,‘a’,encoding=‘utf-8’) as f:

f。write(json。dumps(content, ensure_ascii=False) + ‘\n’)

f。close()

a-開啟一個檔案用於追加。如果該檔案已存在,檔案指標將會放在檔案的結尾。也就是說,新的內容將會被寫入到已有內容之後。如果該檔案不存在,建立新檔案進行寫入。json。dumps序列化時對中文預設使用的ascii編碼。想輸出真正的中文需要指定ensure_ascii=False

然後是讀取部分,

def read_file():

with open(“。/result。txt”, encoding=‘UTF-8’) as file:

data = file。read()

return data

f。close()

讀取也要指定encoding=‘UTF-8’,不然會報錯 ‘gbk’ codec can‘t decode byte,json。loads可以把讀取下來的字元轉換成字典格式

圖片下載

import requests

from requests。exceptions import RequestException

from hashlib import md5

import os

# 儲存檔案到本地

def save_image(content):

file_path = ’{0}/{1}。{2}‘。format(os。getcwd() + ’\image‘,md5(content)。hexdigest(),’jpg‘)

if not os。path。exists(file_path):

with open(file_path,’wb‘) as f:

f。write(content)

f。close()

# 下載圖片

headers = {

’User-Agent‘: ’Mozilla/5。0 (Windows NT 6。1; WOW64) AppleWebKit/537。36 (KHTML, like Gecko) Chrome/27。0。1453。94 Safari/537。36‘

}

def download_image(url):

try:

response = requests。get(url,headers=headers)

if response。status_code == 200:

# 返回二進位制內容

save_image(response。content)

return None

except RequestException:

print(“請求失敗~”)

return None

url = ’https://ss0。baidu。com/6ONWsjip0QIZ8tyhnq/it/u=4033011597,599836400&fm=173&app=25&f=JPEG?w=218&h=146&s=2D548B4F5B630486C865491303001092‘

os。system(’mkdir image‘)

download_image(url)

圖片下載是從返回的二進位制碼中儲存到本地,需要確定圖片的格式,在下載之前先用系統命令在當前位置建立一個空檔案,然後把圖片下到這個檔案裡面,用md5算出一個唯一的字元作為圖片的檔名

影片下載

關於影片的下載,這裡不涉及任何加密的影片,流處理切片影片等,這裡只介紹可以直接在瀏覽器開啟並播放的影片。那影片的方式能不能像圖片那樣下載二進位制流呢?我們先來測試下,

import requests

from requests。exceptions import RequestException

from hashlib import md5

import os

# 儲存檔案到本地

def save_image(content):

file_path = ’{0}/{1}。{2}‘。format(os。getcwd() + ’/video‘,md5(content)。hexdigest(),’mp4‘)

if not os。path。exists(file_path):

with open(file_path,’wb‘) as f:

f。write(content)

f。close()

# 下載圖片

headers = {

’User-Agent‘: ’Mozilla/5。0 (Windows NT 6。1; WOW64) AppleWebKit/537。36 (KHTML, like Gecko) Chrome/27。0。1453。94 Safari/537。36‘

}

def download_video(url):

try:

response = requests。get(url,headers=headers)

print(response)

if response。status_code == 200:

# 返回二進位制內容

save_image(response。content)

return None

except RequestException:

print(“請求失敗~”)

return None

url = ’https://f。us。sinaimg。cn/0023l0JOlx07poxa2nSo01040200BiHS0k010。mp4?label=mp4_ld&template=640x360。28。0&Expires=1550723065&ssig=eh6SSz1inx&KID=unistore,video‘

os。system(’mkdir video‘)

download_video(url)

我找了個影片,實測成功!當然了,短影片這麼下載沒問題。 單檔案下載我已介紹完畢,接下來是重頭戲-就是如何進行多影片併發下載,多影片下載如果迴圈單檔案下載的方式,很容易造成io阻塞,我們需要更高效的解決方案, 解決這個之前我們需要來學習下aiohttp。

使用協程的非同步請求以其低時消耗和對硬體的高利用而著稱,協程在進行爬蟲以及高頻網路請求時的耗時比單多程序和單多執行緒還要好 。 aiohttp是一個為Python提供非同步HTTP 客戶端/服務端程式設計,基於asyncio(Python用於支援非同步程式設計的標準庫)的非同步庫。當然啦,aiohttp用來爬取也是很優秀的

我們下載主要用到的是基於客戶端的請求傳送,先來幾個例子熟悉下

import

aiohttp

asyncio

async

def

main

():

async

with

aiohttp

ClientSession

()

as

session

async

with

session

get

’http://httpbin。org/get‘

as

resp

print

resp

status

res

=

await

resp

text

()

return

res

loop

=

asyncio

get_event_loop

()

res

=

loop

run_until_complete

main

())

print

res

這段程式碼是aiohttp跟asyncio配合的例子,我們也分這2塊來理解

首先是main函數里面的aiohttp,現在有了一個會話(session)物件,由ClientSession物件賦值而來,還有一個變數resp,它其實是ClientResponse物件。我們可以從這個響應物件中獲取我們任何想要的資訊。協程方法 ClientSession。get()的主要引數接受一個HTTP URL。

aiohttp發起的這個非同步請求由asyncio這個庫來進行呼叫。asyncio是Python 3。4版本引入的標準庫,直接內建了對非同步IO的支援。關於asyncio的用法,以後在專欄裡會詳細講解,現在先來個具體例子體會下,

import asyncio

@asyncio。coroutine

def hello():

print(“Hello world!”)

# 非同步呼叫asyncio。sleep(1):

r = yield from asyncio。sleep(1)

print(“Hello again!”)

# 獲取EventLoop:

loop = asyncio。get_event_loop()

# 執行coroutine

loop。run_until_complete(hello())

loop。close()

理解這段程式碼,先來介紹幾個概念

event_loop: 事件迴圈,相當於一個無限迴圈,我們可以把一些函式註冊到這個事件迴圈上,

當滿足條件發生的時候,就會呼叫對應的處理方法。

coroutine:中文翻譯叫協程,在 Python 中常指代為協程物件型別,我們可以將協程物件註冊到時間迴圈中

,它會被事件迴圈呼叫。我們可以使用 async 關鍵字來定義一個方法,這個方法在呼叫時不會立即被執行,

而是返回一個協程物件。

由此我們來解釋上面的程式碼,@asyncio。coroutine把一個generator標記為coroutine型別,然後,

我們就把這個coroutine扔到EventLoop中執行。把asyncio。sleep(1)看成是一個耗時1秒的IO操作,

在此期間,主執行緒並未等待,而是去執行EventLoop中其他可以執行的coroutine了,因此可以實現併發執行。

可能很多人不熟悉非同步的寫法,這裡只有牢記一點,就是在 await的地方外面的函式加上async的關鍵字,意思是函式體存在需要非同步執行的程式碼。

aiohttp簡單理解就是一個協程物件,透過asyncio的事件迴圈來進行呼叫,從而實現多個請求非同步發出,不需要等待其中一個請求完成再進行下一個請求。我們先用這種方式來實現單影片檔案的下載

import re

import time

import aiohttp, asyncio

import random

# 單影片下載

async def get_video(url):

async with aiohttp。ClientSession() as session:

async with session。get(url) as resp:

filename = str(random。randint(1,100)) + str(int(time。time()))

video_info = resp。headers[’Content-Type‘]

pattern = re。compile(’video/(。*)‘, re。S)

results = re。findall(pattern,video_info)

nail = results[0]

with open(’{}。{}‘。format(filename,nail), ’wb‘) as f:

while True:

chunk = await resp。content。read()

if not chunk:

break

f。write(chunk)

def main():

url = ’https://f。us。sinaimg。cn/000ITAJPlx07ryepM28U010412006mQc0E010。mp4?label=mp4_ld&template=640x360。28。0&Expires=1550735642&ssig=MuOMIEqeLh&KID=unistore,video‘

loop = asyncio。get_event_loop()

loop。run_until_complete(get_video(url))

if __name__ == ’__main__‘:

main()

注意的地方是 獲取二進位制響應內容 是 await resp。read() wb的方式的含義是: 只寫方式開啟或新建一個二進位制檔案,只允許寫資料。這裡的檔案字尾名是透過headers[’Content-Type‘]來獲取的

單檔案下載完之後我們來看看多檔案下載怎麼搞,我們將會用到asyncio的Semaphore來實現併發處理。

import time

import re

import aiohttp, asyncio

from utils import *

mongoCls_ins = mongoCls()

res = mongoCls_ins。read_one({’uid‘: ’101209612312‘})

lis = res[’video_list‘]

async def fn(item,sem):

async with sem:

async with aiohttp。ClientSession() as session:

async with session。get(item[’url‘]) as resp:

filename = item[’aweme_id‘]

# 獲取影片字尾

video_info = resp。headers[’Content-Type‘]

pattern = re。compile(’video/(。*)‘, re。S)

results = re。findall(pattern,video_info)

nail = results[0]

with open(’{}。{}‘。format(filename,nail), ’wb‘) as fd:

while True:

chunk = await resp。content。read()

if not chunk:

break

fd。write(chunk)

def main():

loop = asyncio。get_event_loop()

sem = asyncio。Semaphore(10)

tasks = [ asyncio。ensure_future(fn(item,sem)) for item in lis]

loop。run_until_complete(asyncio。wait(tasks))

if __name__ == ’__main__‘:

main()

asyncio。Semaphore(3)將併發數控制在3個,ensure_future 可以將 coroutine 封裝成 Task。任務列表的基本格式是

tasks = [

asyncio。ensure_future(func1()),

asyncio。ensure_future(func2())

loop。run_until_complete(asyncio。wait(tasks))

然後將任務列表交給run_until_complete來完成。

python下載多媒體檔案技巧

程式一執行,馬上就進行下載了!我們來最佳化下, 增加一個進度條來看清楚我們的下載進度。用到的庫是tqdm

import time

from tqdm import tqdm,trange

with tqdm(total=50) as pbar:

for i in range(5):

time。sleep(0。5)

pbar。update(10)

total是進度總量, update() 是更新幅度

python下載多媒體檔案技巧

稍微再難點

from tqdm import tqdm

import asyncio

import random

import datetime

import time

urls=[’www。baidu。com‘,’www。qq。com‘,’www。douyu。com‘]

@asyncio。coroutine

def crawl(url):

io_time = random。random()*3

yield from asyncio。sleep(io_time)

loop = asyncio。get_event_loop()

tasks = [ asyncio。ensure_future(crawl(item)) for item in urls ]

with tqdm(total=len(urls)) as pbar:

for task in tasks:

task。add_done_callback(lambda _: pbar。update(1))

loop。run_until_complete(asyncio。wait(tasks))

loop。close()

這裡用隨機時間等待來模擬請求, task封裝協程物件,並且有一個add_done_callback回撥函式,剛好可以讓我們在回撥的時候更新tqdm進度。

python下載多媒體檔案技巧

3個請求分別完成進度條的更新。

最後來實現多影片併發下載下的進度條。

import time

import re

import aiohttp, asyncio

from utils import *

from tqdm import tqdm

mongoCls_ins = mongoCls()

res = mongoCls_ins。read_one({’uid‘: ’91703324824‘})

lis = res[’video_list‘]

async def fn(item,sem):

async with sem:

async with aiohttp。ClientSession() as session:

async with session。get(item[’url‘][0]) as resp:

filename = item[’aweme_id‘]

# 獲取影片字尾

video_info = resp。headers[’Content-Type‘]

pattern = re。compile(’video/(。*)‘, re。S)

results = re。findall(pattern,video_info)

nail = results[0]

with open(’{}。{}‘。format(filename,nail), ’wb‘) as fd:

while True:

chunk = await resp。content。read()

if not chunk:

break

fd。write(chunk)

def main():

loop = asyncio。get_event_loop()

sem = asyncio。Semaphore(3)

tasks = [asyncio。ensure_future(fn(item,sem)) for item in lis]

with tqdm(total=len(lis)) as pbar:

for task in tasks:

task。add_done_callback(lambda _: pbar。update(1))

loop。run_until_complete(asyncio。wait(tasks))

if __name__ == ’__main__‘:

main()

影片自己先進行抓取到資料庫中,我這裡已成功下載

python下載多媒體檔案技巧

進度條正常執行

python下載多媒體檔案技巧

原始碼請參考

裡面的download檔案,影片地址需要自行爬取~