最近老爸很喜欢看小说, 也很喜欢听书, 基本上就是看累了就听
至于听书, 很多网站会限制收费什么的, 老爸一直问我为什么这又要钱那又要钱, 花了钱还是听不了完整的, 终究花了不少冤枉钱...
好吧, 刚好早些天写了aiohttp的小文章, 就用来下载听书文件把
首先我的目标网站: http://www.ting89.com
, 由于这里都是大量免费的, 但是又基于是网页形式, 老人家在手机不太会那么操作, 不太方便, 现在就来实现aiohttp异步下载听书文件, 给老爸安安心心的听
首先定位到要下载的小说页面:
实际上他分为两块, 上面在线听, 下面的是对应的下载页
在下载页可以找到对应的下载链接, 如: http://mp3-d.ting89.com:9090/官途/官途001.mp3
实际上这部小说所有的下载链接仅仅是集数编号不同, 稍作修改即可, 为了避免缺少集数, 还是把红框内的标签都获取一下
当然针对会出现存放在不同的文件服务器, 最好就是遍历所有的下载页来获取对应地址, 这里仅对下载链接进行集数编号修改
代码:
import asyncio, aiohttp, re, os
from lxml import etree
from tqdm import tqdm
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
}
start_url = 'http://www.ting89.com/books/1547.html' # 对应小说的信息页
mp3_url = 'http://mp3-d.ting89.com:9090/官途/官途{}' # 下载文件的链接
dir_name = '官途' # 保存文件的文件夹名
tasks = [] # 保存future的列表
async def download_mp3(url, name, semaphore, cookies):
async with semaphore:
conn = aiohttp.TCPConnector(limit=2)
async with aiohttp.ClientSession(headers=headers, cookies=cookies, connector=conn) as session:
async with session.get(url) as response:
pbar = tqdm(total=int(response.headers['Content-Length']))
if not os.path.exists(dir_name):
os.makedirs(dir_name)
print('正在下载: ', url)
with open('./{}/{}'.format(dir_name, name), 'wb') as f:
async for chunk, _ in response.content.iter_chunks():
f.write(chunk)
pbar.update(len(chunk))
async def fetch_page():
async with aiohttp.ClientSession(headers=headers) as session:
async with session.get(start_url) as response:
html = await response.read()
page = etree.HTML(html.decode('gb2312'))
compress_list = page.xpath('//div[@class="compress"]')[1]
a_list = compress_list.xpath('./ul/li/a')
# linux携程的最大数默认是1024,windows默认是509,超过了这个值,程序就开始报错
semaphore = asyncio.Semaphore(5)
cookies = response.cookies
for a in a_list:
name = re.search('\d+', a.text).group(0) + '.mp3'
url = mp3_url.format(name)
task = asyncio.ensure_future(download_mp3(url, name, semaphore, cookies))
tasks.append(task)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(fetch_page())
loop.run_until_complete(asyncio.gather(*tasks))
大功告成!!
当然...由于使用的是future, 在没用使用代理的情况下, 避免tasks过多导致被封IP, 也减少对服务器造成的压力, 毕竟是免费的, 使用asyncio.Semaphore(2)
和aiohttp.TCPConnector(limit=2)
来控制一下最大并发数和连接数, linux携程并发的最大数默认是1024,windows默认是509,超过了这个值,程序就开始报错, TCPConnector维持链接池,限制并行连接的总量,当池满了,有请求退出再加入新请求
关于下载文件有几种方式, 上方使用的是iter_chunks()
, 返回的是异步的chunks数据迭代器, 对象是一个元祖(字节, bool), 为了避免文件过大, 采取这种方式会减少对内存的占用, 也相对不会那么卡, 这里也提供一下其他的方式:
# 方式二:
async def fetch():
async with aiohttp.ClientSession(headers=headers) as session:
async with session.get(url) as response:
with open(file_path, 'wb') as f:
while True:
# read() 内的数字控制一次读取的字节大小
chunk = await response.content.read(16)
if not chunk:
break
f.write(chunk)
# 方式三: 第三方工具包aiofiles, 这个网上教程很少, 了解不多
async def fetch():
async with aiohttp.ClientSession(headers=headers) as session:
async with session.get(url) as response:
async with aiofiles.open('./test7.mp3', mode='wb') as f:
mp3 = await response.content.read()
await f.write(mp3)
await f.close()
个人推荐方式一和方式二都可以, 如果文件不大, 直接写入也是可以的, 任君选择, 更深入的chunks的配置使用, 就自行了解源码啦
小补充:
关于response.read()
和response.text()
两者的区别, 前者是不编码,直接读取的字节, 后者是直接转换成str
如果获取的是图像等无法编码文件, 推荐使用read()
, 而直接使用text()
在处理编码不规范的网页时会直接报错
如当前爬取的小说网:http://www.ting89.com/booklist/3.html, 这个链接时, 使用的是gb2312
的字符编码, 当我text(encoding='gb2312')
的时候还是报出UnicodeDecodeError: 'gb2312' codec can't decode byte 0x96 in position 13684: illegal multibyte sequence
的错误
造成原因好像是解码繁体字时出现了问题, 解决办法是text(encoding='gb18030')
使用gb18030来处理
同理text = await response.read()
, text.decode('gb2312')
的时候也会出现同样的问题
推荐工具eyed3:
在下载媒体文件多少都会出现下载的文件的标题,作者专辑显示不正常的问题, 如图:
由于这些是决定了你的文件在播放器里的排序, 会导致很乱, 这里使用eyed3
来遍历文件修改
import os
import eyed3
path = os.getcwd()
files = os.listdir('./mp3')
for file in files:
audio = eyed3.load(path)
audio.tag.title = file
audio.tag.artist = '作者'
audio.tag.album = '专辑'
audio.tag.save()
如果本文对你有启发,或者对本文有疑问或者功能/方法建议,可以在下方做出评论,或者直接联系我,谢谢您的观看和支持!