更新時(shí)間:2024-03-30 13:55作者:小樂
本章的主題是介紹如何利用爬蟲的方法下載VOA每日廣播的英文MP3文件,解決我們生活中遇到的實(shí)際問題。
9.1 應(yīng)解決什么問題?首先介紹一個(gè)練習(xí)英語聽力的好網(wǎng)站:https://learningenglish.voanews.com/。它有滿足不同聆聽需求的部分。每天還有30分鐘的廣播,可以幫助我們練習(xí)沉浸式聽力。是美式英語播音員,速度比較慢,適合學(xué)習(xí)者練習(xí)。還有一些視頻教程,我個(gè)人覺得不錯(cuò),推薦給大家(缺點(diǎn)是需要通過代理訪問網(wǎng)站)。
我在使用它時(shí)遇到的一個(gè)問題是我每次都在線收聽。在手機(jī)上聽會(huì)比較麻煩,所以就想過下載,但又覺得一一點(diǎn)擊下載太麻煩了。所以我決定寫一個(gè)Python程序來幫助我下載它。這就是了解如何使用Python的好處。
在開始一個(gè)實(shí)際的程序之前,我們需要思考我們的程序想要實(shí)現(xiàn)什么功能?
從上述網(wǎng)站下載mp3文件;下載設(shè)定日期內(nèi)所有播放的mp3文件;支持在命令行傳遞開始和結(jié)束日期,如20190101 20190530;如果在命令行上傳遞日期,則該日期將用作開始日期,當(dāng)天的時(shí)間用作截止日期;如果命令行中沒有傳遞日期,則將下載上次到當(dāng)天之間的mp3文件。如果沒有最后一次,則只下載當(dāng)天的mp3文件;代理設(shè)置,我們?nèi)绾问褂么砟??需要正確設(shè)置對應(yīng)的url和端口,然后將其賦值給下載功能中使用的proxy參數(shù);基于我們想要實(shí)現(xiàn)的功能,我們開始思考如何實(shí)現(xiàn)。
9.2 實(shí)現(xiàn)思路這里我們簡單想象一下如何實(shí)現(xiàn)。總體思路如下:
解析參數(shù),確定下載日期,存入列表;將下載日期和下載鏈接拼接成下載鏈接;如果鏈接合法,則下載,否則創(chuàng)建一個(gè)以日期為名稱的txt文件,并將下載鏈接存儲(chǔ)在其中;保存當(dāng)天的日期;需要先解析下載地址,先手動(dòng)找到下載地址。
http://av.voanews.com/clips/VLE/2019/06/15/20190615-003000-VLE122-program_hq.mp3 download=1,就是這個(gè)樣子,可以看到里面包含了2019/06/15/20190615這樣的字符string,我可以推斷任意一天的下載地址只是另一個(gè)固定字符串加上日期。隨機(jī)找了一天去驗(yàn)證一下,發(fā)現(xiàn)確實(shí)如此。那么就簡單了,不需要爬行。
下載過程中,由于每個(gè)文件大約30MB,下載需要幾秒鐘的時(shí)間(當(dāng)然也取決于網(wǎng)速),所以我們的下載必須使用流式下載,所以在下面的函數(shù)中,stream=True就是使用流式下載下載中。
def download_file(file_url, file_name):'''下載文件'''with requests.get(file_url, stream=True, proxies=None) 作為響應(yīng), open(file_name, 'wb') as local_file:shutil.copyfileobj(response.raw ,local_file)9.3 相關(guān)模塊的安裝及介紹本節(jié)將介紹程序中用到的相關(guān)模塊。
9.3.1shutil模塊shutil模塊主要用于文件處理,比如最基本的文件操作、刪除、移動(dòng)、復(fù)制、壓縮解壓等,如果涉及到文件相關(guān)的操作,首先應(yīng)該想到這個(gè)模塊。
我們這里使用該模塊的copyfileobject方法將流數(shù)據(jù)對象的內(nèi)容復(fù)制到我們創(chuàng)建的MP3文件中。
def download_file(file_url, file_name):'''下載文件'''with requests.get(file_url, stream=True, proxies=None) 作為響應(yīng), open(file_name, 'wb') as local_file:shutil.copyfileobj(response.raw , local_file) 9.3.2 datetime 模塊datetime 模塊用于處理與日期相關(guān)的事務(wù)。這里我們將使用該模塊的strptime() 接口來構(gòu)造一個(gè)日期字符串。
def handle_parameters(parameters):end_date=datetime.datetime.strptime(time.strftime('%Y%m%d',time.localtime(time.time())), '%Y%m%d')begin_date=end_date #begin_date=datetime.datetime.strptime('20190701', '%Y%m%d')if len(parameters)=3:begin_date=datetime.datetime.strptime(parameters[1], '%Y%m%d') if is_valid_data(parameters[1]) else exit(-1)end_date=datetime.datetime.strptime(parameters[2], '%Y%m%d') if is_valid_data(parameters[2]) else exit(-1) elif len(parameters)=2:begin_date=datetime.datetime.strptime(parameters[1], '%Y%m%d') if is_valid_data(sys.argv[1]) else exit(-1)else:#if 有上次,我們使用它作為begin_date,否則我們使用end_date as begin_date.begin_date=get_last_date(last_date_file, begin_date)return begin_date, end_date 在我們的程序中,我們需要獲取起始日期范圍內(nèi)的所有日期字符串。如果人工構(gòu)造起來非常困難,但是基于datetime的timedelta()就可以相對容易地完成。同時(shí),您也可以根據(jù)自己的需要設(shè)置間隔時(shí)間。這里我們需要每天,所以設(shè)置days=1。
def get_file_list(begin_date, end_date):'''獲取我們要下載的所有文件''' date_list=[] while begin_date=end_date: date_dir=begin_date.strftime('%Y/%m/%d/') date_str=begin_date.strftime('%Y%m%d') date_list.append(date_dir+date_str) begin_date +=datetime.timedelta(days=1)return date_list9.3.3 shelve 模塊shelve 模塊是一個(gè)比較實(shí)用的模塊,我們使用它打開文件并像字典一樣讀寫數(shù)據(jù)。如果有程序需要保存一些臨時(shí)數(shù)據(jù),或者數(shù)據(jù)不大,可以使用這個(gè)模塊。例如,我們在程序中使用此模塊來存儲(chǔ)最后的日期,以防止用戶再次重新輸入。
def store_current_date(file, date):'''將當(dāng)前日期存儲(chǔ)為下一個(gè)開始日期''' s=shelve.open(file, writeback=True) s[last_date_key]=date s.close() 上面的代碼是什么我們使用來存儲(chǔ)日期,下面看看當(dāng)我們需要使用日期時(shí)如何獲取日期。
def get_last_date(file, default_date):'''從文件中獲取最后日期''' date=default_date if os.path.exists(file): s=shelve.open(file, writeback=True) last_date=s[last_date_key] s.close() date=datetime.datetime.strptime(last_date, '%Y%m%d')return date9.3.4 時(shí)間模塊時(shí)間模塊提供了各種與時(shí)間相關(guān)的函數(shù)。
time.asctime() 函數(shù)可以將結(jié)構(gòu)體struct_time 表示的時(shí)間轉(zhuǎn)換為類似'Sun Jun 19 13:31:15 1994' 的字符串。我們可以通過time.localtime()函數(shù)獲取結(jié)構(gòu)體struct_time。
time.sleep()函數(shù)的輸入?yún)?shù)單位為秒。如果需要掛起線程,可以通過調(diào)用該函數(shù)來達(dá)到目的。這里的輸入?yún)?shù)還可以是小數(shù),表示更精確的睡眠時(shí)間。我們將使用此函數(shù)進(jìn)行必要的等待,以確保另一件事完成。
time.strftime() 函數(shù)也用于格式化時(shí)間。
導(dǎo)入時(shí)間time.strftime('%Y%m%d',time.localtime(time.time()))'20190714' time.strftime('%Y-%m-%d')'2019-07-14 '另一個(gè)常見的操作是使用time.strptime() 接口來確定給定的字符串是否是有效的日期。
def is_valid_data(date_str):'''檢查日期字符串是否有效'''try: time.strptime(date_str, '%Y%m%d') return True except:return False9.3.5 sys 模塊sys 模塊是內(nèi)置模塊,不需要單獨(dú)安裝。
sys模塊提供了對Python解釋器使用的一些變量的訪問,并且可以進(jìn)行一些修改,比如讀取和修改環(huán)境變量PATH,并提供了一定的與解釋器交互的函數(shù),以便我們的程序可以與解釋器進(jìn)行交互。
例如sys.argv會(huì)將命令行參數(shù)以列表的形式傳遞給Python腳本,sys.argv[0]是腳本的名稱,sys.argv[1]是第一個(gè)參數(shù),依此類推。
sys.exit()表示退出程序,也可以帶參數(shù)表示退出碼。如果有其他程序調(diào)用該程序,則可以通過返回的數(shù)字確定被調(diào)用程序的退出原因。
sys.implementation 查看當(dāng)前運(yùn)行的Python解釋器的版本信息。
sys.implementationnamespace(cache_tag='cpython-36', hexversion=50726384, name='cpython', version=sys.version_info(major=3,minor=6,micro=5,releaselevel='final',serial=0) )sys.stdin、sys.stddout、sys.stderr 標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和錯(cuò)誤的解釋器。
9.3.6 os 模塊os 模塊是一個(gè)比較常用的模塊。從名字就可以看出它與操作系統(tǒng)有關(guān)。例如,當(dāng)需要獲取當(dāng)前工作目錄時(shí),可以使用getcwd()接口。您還可以輕松分離路徑的文件夾部分和文件名部分。
import os os.getcwd()'C:\\Users\\just right' os.path.abspath('.')'C:\\Users\\just right' path=r'E:\第09章爬蟲每日下載voa廣播英文MP3文件\auto-download-voa-broadcast.py' os.path.split(path)('E: \\第09章爬蟲下載voa每日廣播英文MP3文件', 'auto-download-voa-broadcast.py ') 我們這里的程序?qū)⑹褂胦s 模塊來判斷文件是否存在。代碼如下。
path=r'E:\第09章爬蟲下載voa每日廣播英文MP3文件\auto-download-voa-broadcast.py' os.path.exists(path)True9.3.7 requests模塊requests模塊是用于訪問網(wǎng)絡(luò)模塊,我們這里的程序需要先進(jìn)行用戶認(rèn)證。通過用戶名和密碼的認(rèn)證后,運(yùn)行我們的程序來訪問數(shù)據(jù)庫,并對數(shù)據(jù)庫進(jìn)行相應(yīng)的讀寫操作。
requests 模塊還可以用于登錄網(wǎng)頁。這方面將在其他相關(guān)程序示例中介紹。這里我們只關(guān)注當(dāng)前示例程序需要使用的內(nèi)容。
以下代碼片段是我們程序中用于確定鏈接有效性的函數(shù)。我們將鏈接和代理參數(shù)傳遞給get接口,然后使用接口返回的文本來判斷鏈接是否有效。在我們的示例中,如果鏈接無效,頁面文本將包含字符串“404 - 未找到文件或目錄”。
def is_valid_link(file_url, invalid_msg):'''檢查mp3_url 是否有效''' text='' return True with requests.get(file_url, proxies=http_proxies) as response: print(response.text) text=response.textreturn True if invalid_msg in text else False 以下函數(shù)展示了如何通過requests 模塊下載文件,將Stream 參數(shù)設(shè)置為True,并在必要時(shí)為您自己的代理配置設(shè)置代理參數(shù)proxies。
def download_file(file_url, file_name):'''下載文件'''with requests.get(file_url, stream=True, proxies=None) 作為響應(yīng), open(file_name, 'wb') as local_file:shutil.copyfileobj(response.raw ,本地文件)
9.4 代碼實(shí)現(xiàn)介紹完了相關(guān)模塊,我們來看看代碼是如何實(shí)現(xiàn)的。
9.4.1 編寫偽代碼我們首先編寫必要的偽代碼來了解程序的整體結(jié)構(gòu)。
# 定義函數(shù)is_valid_link 來判斷鏈接是否有效# 定義函數(shù)is_valid_date 來判斷給定的字符是否是有效日期# 定義函數(shù)download() 來下載文件# 定義函數(shù)get_file_list() 來獲取一天內(nèi)的每一天給定日期范圍日期字符串# 定義函數(shù)get_last_date() 用于從配置文件中獲取最后日期# 定義函數(shù)store_current_date() 用于存儲(chǔ)日期# 定義函數(shù)handle_parameters() 用于處理程序的參數(shù)if '__main__'==__name__: 利用handle_parameters()處理程序參數(shù)通過get_file_list()獲取日期字符串列表,遍歷日期字符串列表,并將其轉(zhuǎn)換為url鏈接。如果url有效,則下載,否則創(chuàng)建txt文件,記錄失敗原因。 9.4.2 Python代碼Python代碼實(shí)現(xiàn)如下。
# -*-coding: utf-8 -*-'''此工具幫助每天自動(dòng)下載voa廣播。1。下載從上次到當(dāng)天的所有mp3 文件。2. export http_proxy=''如何實(shí)現(xiàn):#獲取最后一次下載的日期#準(zhǔn)備下載該時(shí)間段內(nèi)的所有mp3文件#檢查mp3的每個(gè)鏈接是否有效#如果有效則下載,或者使用txt文件代替#保存當(dāng)前日期創(chuàng)建于Fri Jun 15 14:17:40 2019 @author: ggang.liu'''import Shutilimport datetimeimport shelveimport timeimport sysimport osimport requestsserver_error='404 - 文件或目錄未找到'last_date_file='last_date'last_date_key='date'url_template='http://av .voanews.com/clips/VLE/{ 0}-003000-VLE122-program_hq.mp3 download=1'def is_valid_link(file_url, invalid_msg):'''檢查mp3_url 是否有效'''text=''return Truewith requests .get(file_url, proxies=http_proxies) as response:print(response.text)text=response.textreturn True if invalid_msg in text else Falsedef is_valid_data(date_str):'''檢查日期字符串是否有效'''try:time.strptime(date_str , '%Y%m%d') return True except:return Falsedef download_file(file_url, file_name):'''下載文件'''with requests.get(file_url, stream=True, proxies=None) 作為響應(yīng), open(file_name, 'wb') as local_file:shutil.copyfileobj(response .raw, local_file)def get_file_list(begin_date, end_date):'''獲取我們要下載的所有文件'''date_list=[]while begin_date=end_date:date_dir=begin_date.strftime( '%Y/%m/%d/')date_str=begin_date.strftime('%Y%m%d')date_list.append(date_dir+date_str)begin_date +=datetime.timedelta(days=1)return date_listdef get_last_date( file, default_date):'''從文件中獲取最后日期'''date=default_dateif os.path.exists(file):s=shelve.open(file, writeback=True)last_date=s[last_date_key]s.close() date=datetime.datetime.strptime(last_date, '%Y%m%d')return datedef store_current_date(file, date):'''將當(dāng)前日期存儲(chǔ)為下一個(gè)開始日期''s=shelve.open(file, writeback=True)s[last_date_key]=日期.close()def handle_parameters(parameters):end_date=datetime.datetime.strptime(time.strftime('%Y%m%d',time.localtime(time.time())) ), '%Y%m%d' )begin_date=end_date#begin_date=datetime.datetime.strptime('20190701', '%Y%m%d')if len(參數(shù))=3:begin_date=datetime.datetime.strptime(參數(shù)[1], '%Y%m %d') if is_valid_data(parameters[1]) else exit(-1)end_date=datetime.datetime.strptime(parameters[2], '%Y%m%d') if is_valid_data(parameters[2]) else exit (-1)elif len(parameters)=2:begin_date=datetime.datetime.strptime(parameters[1], '%Y%m%d') if is_valid_data(sys.argv[1] ]) else exit(-1)else:# 如果有最后一次,則將其用作begin_date,否則將end_date 用作begin_date.begin_date=get_last_date(last_date_file, begin_date)return begin_date, end_dateif '__main__'==__name__:begin_date, end_date=handle_parameters(sys.argv)#獲取日期listdate_list=get_file_list(begin_date, end_date)print(date_list)for date_list中的日期:file_url=url_template.format(date)if is_valid_link(file_url, server_error):print('正在下載:' + file_url)download_file( file_url, 'voa_broadcast_'+date[-8: ]+'.mp3')else:with open(date[-8:]+'.txt', 'w') as f:f.write(file_url)9.5 本章小結(jié)一般來說,思路比較簡單,就是將要下載的日期構(gòu)造成下載鏈接,然后一一下載。 Python是一門非常好的語言,可以極大的幫助我們節(jié)省開發(fā)時(shí)間。
學(xué)以致用才是正確的出路。這也是編程的樂趣所在。
歡迎關(guān)注、轉(zhuǎn)發(fā)、點(diǎn)贊
Python編程入門實(shí)踐案例:第一章Python概述以及為什么學(xué)習(xí)Python
Python編程入門實(shí)踐案例:第二章字符串
Python實(shí)用案例編程簡介:第3章列表和元組
Python實(shí)用案例編程入門:第4章字典和文件
Python實(shí)用案例編程入門:第6章控制流語句
Python實(shí)用案例編程入門:第五章函數(shù)和類
Python編程入門實(shí)戰(zhàn)案例:第七章調(diào)試方法
Python實(shí)用案例編程入門:第八章如何自動(dòng)連接WIFI