使用Aiohttp下载文件

最近老爸很喜欢看小说, 也很喜欢听书, 基本上就是看累了就听

至于听书, 很多网站会限制收费什么的, 老爸一直问我为什么这又要钱那又要钱, 花了钱还是听不了完整的, 终究花了不少冤枉钱...

好吧, 刚好早些天写了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()

如果本文对你有启发,或者对本文有疑问或者功能/方法建议,可以在下方做出评论,或者直接联系我,谢谢您的观看和支持!

添加新评论

本站现已启用评论投票,被点踩过多的评论将自动折叠。与本文无关评论请发留言板。请不要水评论,谢谢。

已有 0条评论