本章的主题是介绍如何利用爬虫的方法下载VOA每日广播的英文MP3文件,解决我们生活中遇到的实际问题。
9.1 应解决什么问题?首先介绍一个练习英语听力的好网站:https://learningenglish.voanews.com/。它有满足不同聆听需求的部分。每天还有30分钟的广播,可以帮助我们练习沉浸式听力。是美式英语播音员,速度比较慢,适合学习者练习。还有一些视频教程,我个人觉得不错,推荐给大家(缺点是需要通过代理访问网站)。
我在使用它时遇到的一个问题是我每次都在线收听。在手机上听会比较麻烦,所以就想过下载,但又觉得一一点击下载太麻烦了。所以我决定写一个Python程序来帮助我下载它。这就是了解如何使用Python的好处。
在开始一个实际的程序之前,我们需要思考我们的程序想要实现什么功能?
从上述网站下载mp3文件;下载设定日期内所有播放的mp3文件;支持在命令行传递开始和结束日期,如20190101 20190530;如果在命令行上传递日期,则该日期将用作开始日期,当天的时间用作截止日期;如果命令行中没有传递日期,则将下载上次到当天之间的mp3文件。如果没有最后一次,则只下载当天的mp3文件;代理设置,我们如何使用代理呢?需要正确设置对应的url和端口,然后将其赋值给下载功能中使用的proxy参数;基于我们想要实现的功能,我们开始思考如何实现。
9.2 实现思路这里我们简单想象一下如何实现。总体思路如下:
解析参数,确定下载日期,存入列表;将下载日期和下载链接拼接成下载链接;如果链接合法,则下载,否则创建一个以日期为名称的txt文件,并将下载链接存储在其中;保存当天的日期;需要先解析下载地址,先手动找到下载地址。
http://av.voanews.com/clips/VLE/2019/06/15/20190615-003000-VLE122-program_hq.mp3 download=1,就是这个样子,可以看到里面包含了2019/06/15/20190615这样的字符string,我可以推断任意一天的下载地址只是另一个固定字符串加上日期。随机找了一天去验证一下,发现确实如此。那么就简单了,不需要爬行。
下载过程中,由于每个文件大约30MB,下载需要几秒钟的时间(当然也取决于网速),所以我们的下载必须使用流式下载,所以在下面的函数中,stream=True就是使用流式下载下载中。
def download_file(file_url, file_name):'''下载文件'''with requests.get(file_url, stream=True, proxies=None) 作为响应, open(file_name, 'wb') as local_file:shutil.copyfileobj(response.raw ,local_file)9.3 相关模块的安装及介绍本节将介绍程序中用到的相关模块。
9.3.1shutil模块shutil模块主要用于文件处理,比如最基本的文件操作、删除、移动、复制、压缩解压等,如果涉及到文件相关的操作,首先应该想到这个模块。
我们这里使用该模块的copyfileobject方法将流数据对象的内容复制到我们创建的MP3文件中。
def download_file(file_url, file_name):'''下载文件'''with requests.get(file_url, stream=True, proxies=None) 作为响应, open(file_name, 'wb') as local_file:shutil.copyfileobj(response.raw , local_file) 9.3.2 datetime 模块datetime 模块用于处理与日期相关的事务。这里我们将使用该模块的strptime() 接口来构造一个日期字符串。
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 在我们的程序中,我们需要获取起始日期范围内的所有日期字符串。如果人工构造起来非常困难,但是基于datetime的timedelta()就可以相对容易地完成。同时,您也可以根据自己的需要设置间隔时间。这里我们需要每天,所以设置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 模块是一个比较实用的模块,我们使用它打开文件并像字典一样读写数据。如果有程序需要保存一些临时数据,或者数据不大,可以使用这个模块。例如,我们在程序中使用此模块来存储最后的日期,以防止用户再次重新输入。
def store_current_date(file, date):'''将当前日期存储为下一个开始日期''' s=shelve.open(file, writeback=True) s[last_date_key]=date s.close() 上面的代码是什么我们使用来存储日期,下面看看当我们需要使用日期时如何获取日期。
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 时间模块时间模块提供了各种与时间相关的函数。
time.asctime() 函数可以将结构体struct_time 表示的时间转换为类似'Sun Jun 19 13:31:15 1994' 的字符串。我们可以通过time.localtime()函数获取结构体struct_time。
time.sleep()函数的输入参数单位为秒。如果需要挂起线程,可以通过调用该函数来达到目的。这里的输入参数还可以是小数,表示更精确的睡眠时间。我们将使用此函数进行必要的等待,以确保另一件事完成。
time.strftime() 函数也用于格式化时间。
导入时间time.strftime('%Y%m%d',time.localtime(time.time()))'20190714' time.strftime('%Y-%m-%d')'2019-07-14 '另一个常见的操作是使用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 模块是内置模块,不需要单独安装。
sys模块提供了对Python解释器使用的一些变量的访问,并且可以进行一些修改,比如读取和修改环境变量PATH,并提供了一定的与解释器交互的函数,以便我们的程序可以与解释器进行交互。
例如sys.argv会将命令行参数以列表的形式传递给Python脚本,sys.argv[0]是脚本的名称,sys.argv[1]是第一个参数,依此类推。
sys.exit()表示退出程序,也可以带参数表示退出码。如果有其他程序调用该程序,则可以通过返回的数字确定被调用程序的退出原因。
sys.implementation 查看当前运行的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 标准输入、标准输出和错误的解释器。
9.3.6 os 模块os 模块是一个比较常用的模块。从名字就可以看出它与操作系统有关。例如,当需要获取当前工作目录时,可以使用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 ') 我们这里的程序将使用os 模块来判断文件是否存在。代码如下。
path=r'E:\第09章爬虫下载voa每日广播英文MP3文件\auto-download-voa-broadcast.py' os.path.exists(path)True9.3.7 requests模块requests模块是用于访问网络模块,我们这里的程序需要先进行用户认证。通过用户名和密码的认证后,运行我们的程序来访问数据库,并对数据库进行相应的读写操作。
requests 模块还可以用于登录网页。这方面将在其他相关程序示例中介绍。这里我们只关注当前示例程序需要使用的内容。
以下代码片段是我们程序中用于确定链接有效性的函数。我们将链接和代理参数传递给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 以下函数展示了如何通过requests 模块下载文件,将Stream 参数设置为True,并在必要时为您自己的代理配置设置代理参数proxies。
def download_file(file_url, file_name):'''下载文件'''with requests.get(file_url, stream=True, proxies=None) 作为响应, open(file_name, 'wb') as local_file:shutil.copyfileobj(response.raw ,本地文件)
9.4 代码实现介绍完了相关模块,我们来看看代码是如何实现的。
9.4.1 编写伪代码我们首先编写必要的伪代码来了解程序的整体结构。
# 定义函数is_valid_link 来判断链接是否有效# 定义函数is_valid_date 来判断给定的字符是否是有效日期# 定义函数download() 来下载文件# 定义函数get_file_list() 来获取一天内的每一天给定日期范围日期字符串# 定义函数get_last_date() 用于从配置文件中获取最后日期# 定义函数store_current_date() 用于存储日期# 定义函数handle_parameters() 用于处理程序的参数if '__main__'==__name__: 利用handle_parameters()处理程序参数通过get_file_list()获取日期字符串列表,遍历日期字符串列表,并将其转换为url链接。如果url有效,则下载,否则创建txt文件,记录失败原因。 9.4.2 Python代码Python代码实现如下。
# -*-coding: utf-8 -*-'''此工具帮助每天自动下载voa广播。1。下载从上次到当天的所有mp3 文件。2. export http_proxy=''如何实现:#获取最后一次下载的日期#准备下载该时间段内的所有mp3文件#检查mp3的每个链接是否有效#如果有效则下载,或者使用txt文件代替#保存当前日期创建于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) 作为响应, 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):'''将当前日期存储为下一个开始日期''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(参数)=3:begin_date=datetime.datetime.strptime(参数[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 本章小结一般来说,思路比较简单,就是将要下载的日期构造成下载链接,然后一一下载。 Python是一门非常好的语言,可以极大的帮助我们节省开发时间。
学以致用才是正确的出路。这也是编程的乐趣所在。
欢迎关注、转发、点赞
Python编程入门实践案例:第一章Python概述以及为什么学习Python
Python编程入门实践案例:第二章字符串
Python实用案例编程简介:第3章列表和元组
Python实用案例编程入门:第4章字典和文件
Python实用案例编程入门:第6章控制流语句
Python实用案例编程入门:第五章函数和类
Python编程入门实战案例:第七章调试方法
Python实用案例编程入门:第八章如何自动连接WIFI