python编程案例_Python实用案例编程入门:第九章 爬虫下载VOA每日广播英语MP3

本章的主题为介绍如何爬虫的方式下载VOA每日广播英语MP3文件,解决我们生活中的遇到的实际问题。

9792d1467c2f36c66823f9e33e4e963e.png

9.1 要解决什么问题

先介绍一个练习英语听力的好网站https://learningenglish.voanews.com/,有针对不同听力需求的部分,也有每天30分钟的broadcast,可以帮助我们沉浸式练习听力,是美语播音员,且速度比较慢,适合学习者练习。也有一部分视频教程,个人觉得不错,推荐给大家(有个不好地方就是该网站需要通过代理访问)。

在用的过程中有一个问题,就是我每次都是在线听。如果想在手机上听就比较麻烦,所以想能不能下载下来,但是又觉得一个一个去点击下载太过麻烦了。于是就决定写个Python程序帮我下载,这就是会用Python的好处。

在开始现实程序之前我们需要想想,我们的程序要实现哪些功能?

  • 下载mp3文件从上面提到的网站;
  • 下载所有设定日期内的broadcast mp3文件;
  • 支持在命令行传入起始和截止日期,比如20190101 20190530;
  • 如果命令行传入一个日期则该日期作为起始日期,当天时间作为截止日期;
  • 如果命令行没传入日期,则下载上一次到当天之间的mp3,若没有上一次的时间则只下载当天的mp3文件;
  • 代理的设置,如何我们使用了代理,需要将相应的url和port设置正确,然后将其赋值给下载函数中使用到的proxies参数即可;

基于我们想要实现的这些功能,下面我们开始想想如何实现它。

9.2 实现思路

这里我们简单构思下如何实现,大致思路如下:

  • 解析参数,确定下载的日期,并存入list;
  • 将下载日期和下载链接进行拼接,构成下载链接;
  • 如果链接合法,则下载,否则以日期名为名创建txt文件并将下载链接存入其内;
  • 保存当天日期;

先需要解析下载地址,先手动找到一个下载地址。

http://av.voanews.com/clips/VLE/2019/06/15/20190615-003000-VLE122-program_hq.mp3?download=1,就是这样子的,可以看出其中包含了2019/06/15/20190615这样的字符串,我可以推测任意一天的下载地址就是其他固定字符串加日期即可。随机找一天验证,发现确实如此,那就简单了,不需要去爬虫了。

下载过程,由于每个文件约30MB,所以下载需要持续几秒(当然跟网速也有关系),因此我们的下载必须采用流式下载,因此下面的函数中,stream=True就是为了使用流式下载。

def download_file(file_url, file_name):"""downdload file"""with requests.get(file_url, stream=True, proxies=None) as response, open(file_name, 'wb') as local_file:shutil.copyfileobj(response.raw, local_file)

9.3 相关模块的安装及介绍

本节将介绍程序中使用到的相关模块。

9.3.1 shutil模块

shutil模块主要用于文件处理,比如最基本的文件操作,删除,移动,复制,压缩和解压缩等。如果涉及文件相关的操作,我们应该首先想到该模块。

我们这里使用该模块的copyfileobject方法将流数据对象的内容复制到我们创建的MP3文件中。

def download_file(file_url, file_name):"""downdload file"""with requests.get(file_url, stream=True, proxies=None) as response, 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 there is a last time, we use it as begin_date, otherwise we use 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):"""get all file what we want to download"""  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_list

9.3.3 shelve模块

shelve模块是一个比较实用的模块,我们用它打开一个文件,然后像操作字典一样进行数据的读写。如果有程序需要保存一些临时数据,或者数据不大,可以使用此模块进行。比如,我们的程序中用该模块来存储最后的日期,避免用户再次重复输入。

def store_current_date(file, date):"""store current date as the next begin date"""  s = shelve.open(file, writeback=True)  s[last_date_key] = date  s.close()

上面代码是我们用于存储日期,下面再看看当我们需要使用时如何获取该日期。

def get_last_date(file, default_date):"""get last date from file"""  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 date

9.3.4 time模块

time模块提供了时间相关的各种函数。

time.asctime()函数可以将结构体struct_time所代表的时间转换为这样的字符串'Sun Jun 19 13:31:15 1994'。我们可以通过time.localtime()函数得到结构体struct_time。

time.sleep()函数的入参单位是秒,如果线程需要被挂起,可以通过调用此函数达到该目的。这里的入参也可以是小数,表示更精确的睡眠时间。我们将会使用该函数进行必要的等待以确保另一件事情结束。

time.strftime()函数也用于格式化时间。

>>> import time>>> 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):"""Check if date string is valid"""try:  time.strptime(date_str, "%Y%m%d")  return Trueexcept:return False

9.3.5 sys模块

sys模块是一个内建模块,不需要单独安装。

sys模块提供了对Python解释器使用的一些变量的访问,并可以进行一些修改,例如对环境变量PATH的读取和修改,并提供了某些和解释器进行交互的函数以使我们的程序能够和解释器进行交互。

例如,sys.argv会将命令行参数以list的形式传递给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:甥敳獲刚刚好'>>> os.path.abspath('.')'C:甥敳獲刚刚好'>>>>>> 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)True>>>

9.3.7 requests模块

requests模块是一个用于访问网络的模块,我们这里的程序需要先进行用户鉴权。在通过用户名和密码进行鉴权通过后,我们的程序才被运行访问数据库,对数据库进行相应的读写操作。

requests模块也可以用于登陆网页,在其他相关程序例子中会涉及这方面的介绍,这里只关注当前实例程序需要使用到的内容。

下面的代码片段就是我们程序中使用到的用于链接有效性判断的函数,我们将链接和代理参数传递给get接口,然后通过该接口返回的文本进行判断链接是否有效。在我们的例子中,如果链接无效的页面文本中会包含"'404 - File or directory not found"字样的字符串。

def is_valid_link(file_url, invalid_msg):"""check if mp3_url is valid"""  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):"""downdload file"""with requests.get(file_url, stream=True, proxies=None) as response, open(file_name, 'wb') as local_file:shutil.copyfileobj(response.raw, local_file)

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 -*-"""This tool help to download voa broadcast automatically every day.1. Download all mp3 file from last time to current day.2. export http_proxy=''How to implement:#Getting the date of last download#Prepare to download all mp3 file which are in this duration#Check whether every link of mp3 is valid#Download it if it is valid, or use a txt file instead#Save current dateCreated on Fri Jun 15 14:17:40 2019@author: ggang.liu"""import shutilimport datetimeimport shelveimport timeimport sysimport osimport requestsserver_error = '404 - File or directory not found'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):"""check if mp3_url is valid"""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):"""Check if date string is valid"""try:time.strptime(date_str, "%Y%m%d")return Trueexcept:return Falsedef download_file(file_url, file_name):"""downdload file"""with requests.get(file_url, stream=True, proxies=None) as response, open(file_name, 'wb') as local_file:shutil.copyfileobj(response.raw, local_file)def get_file_list(begin_date, end_date):"""get all file what we want to download"""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):"""get last date from file"""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):"""store current date as the next begin date"""s = shelve.open(file, writeback=True)s[last_date_key] = dates.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(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 there is a last time, we use it as begin_date, otherwise we use end_date as 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)#get date listdate_list = get_file_list(begin_date, end_date)print(date_list)for date in date_list:file_url = url_template.format(date)if is_valid_link(file_url, server_error):print("Downloading: " + 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实用案例编程入门:第三章 列表和元组

Python实用案例编程入门:第四章 字典和文件

Python实用案例编程入门:第六章 控制流语句

Python实用案例编程入门:第五章 函数和类

Python实用案例编程入门:第七章 调式手段

Python实用案例编程入门:第八章 如何自动连接WIFI

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值