Асинхронная загрузка файлов

Следующий код представляет собой асинхронное исследование для начинающих асинхронных загрузок файлов, чтобы попытаться улучшить время загрузки файлов с определенного веб-сайта.


Задания:

Задачи следующие:

  1. Посещение https://digital.nhs.uk/data-and-information/publications/statistical/mental-health-services-monthly-statistics

  2. Извлеките ссылку на последнюю публикацию. Это верхняя ссылка под Последняя статистика и является первым совпадением, возвращаемым селектором css .cta__button.

    введите описание изображения здесь

NB Каждый месяц эта ссылка обновляется, поэтому в следующей ежемесячной публикации, например, 8 апреля 2021 года, ссылка будет обновлена ​​до той, которая связана с Ежемесячная статистика служб охраны психического здоровья Январь, предварительный февраль 2021 г..

  1. Посетите извлеченную ссылку: https://digital.nhs.uk/data-and-information/publications/statistical/mental-health-services-monthly-statistics/performance-de December-2020-provisional-january-2021. и оттуда извлеките ссылки для скачивания для всех файлов, перечисленных в Ресурсы; на данный момент 17 файлов.

    введите описание изображения здесь

  2. Наконец, загрузите все эти файлы, используя полученные URL-адреса, и сохраните их в месте, указанном folder Переменная.


Настраивать:

Python 3.9.0 64-битная Windows 10


Запрос:

Я был бы признателен за любые предлагаемые улучшения этого кода, например, если бы я реорганизовал сопрограмму fetch_download_links на две совместные процедуры, каждая с ClientSession, где одна сопрограмма получает начальную ссылку на то, где можно получить ресурсы, а вторая сопрограмма для получения фактических ссылок на ресурсы?


mhsmsAsynDownloads.py

import time
import os
from bs4 import BeautifulSoup as bs
import aiohttp
import aiofiles
import asyncio
import urllib

async def fetch_download_links(url:str) -> list:   
    async with aiohttp.ClientSession() as session:
        r = await session.get(url, ssl = False)
        html = await r.text()
        soup = bs(html, 'lxml')
        link = 'https://digital.nhs.uk' + soup.select_one('.cta__button')['href']
        r = await session.get(link, ssl = False)
        html = await r.text()
        soup = bs(html, 'lxml')
        files = [i['href'] for i in soup.select('.attachment a')]

        return files


async def place_file(source: str) -> None:
    async with aiohttp.ClientSession() as session:
        file_name = source.split("https://codereview.stackexchange.com/")[-1]
        file_name = urllib.parse.unquote(file_name)
        r = await session.get(source, ssl = False)
        content = await r.read()
    
    async with aiofiles.open(folder + file_name, 'wb') as f:
        await f.write(content)

    
async def main():
    tasks = []
    urls = await fetch_download_links('https://digital.nhs.uk/data-and-information/publications/statistical/mental-health-services-monthly-statistics')
    
    for url in urls:

        tasks.append(place_file(url))   
    
    await asyncio.gather(*tasks)

        
folder="C:/Users/<User>/OneDrive/Desktop/testing/"

if __name__ == '__main__':
   
    t1 = time.perf_counter()   
    print("process started...")   
    asyncio.get_event_loop().run_until_complete(main())
    os.startfile(folder[:-1])
    t2 = time.perf_counter()
    print(f'Completed in {t2-t1} seconds.')

Ссылки / Примечания:

  1. Я написал это после просмотра асинхронный учебник на YouTube Андрея Думитреску. Пример выше мой собственный
  2. https://docs.aiohttp.org/en/v0.20.0/client.html
  3. https://docs.aiohttp.org/en/stable/client_advanced.html
  4. Данные общедоступны

1 ответ
1

Мне кажется, вы могли бы избавиться от некоторых повторений, но это не так уж важно для небольшого скрипта.

Тем не менее, я бы повторно использовал сеанс, встроил несколько переменных, исправил именование (константы должны быть в верхнем регистре) и использовать некоторые стандартные инструменты библиотеки, такие как os.path чтобы сделать сценарий более защищенным от ошибок. В list Тип возвращаемого значения также может быть более конкретным и указать, например, что это список строк.

Кроме того, что более важно, я бы предпочел, чтобы во всем HTTP-ответе не было такого скрипта! Кто знает, насколько велики файлы, плюс это расточительно … лучше передать ответ прямо на диск! К счастью, API существуют, с использованием .content.iter_any (или же _chunked если вы предпочитаете иметь фиксированный размер блока), вы можете просто перебрать каждый блок данных чтения и записать их в файл следующим образом:

import time
import os
import os.path
from bs4 import BeautifulSoup as bs
import aiohttp
import aiofiles
import asyncio
import urllib
import typing


STATISTICS_URL = 'https://digital.nhs.uk/data-and-information/publications/statistical/mental-health-services-monthly-statistics'
OUTPUT_FOLDER = 'C:/Users/<User>/OneDrive/Desktop/testing/'


async def fetch_download_links(session: aiohttp.ClientSession, url: str) -> typing.List[str]:
    r = await session.get(url, ssl=False)
    soup = bs(await r.text(), 'lxml')
    link = 'https://digital.nhs.uk' + soup.select_one('.cta__button')['href']

    r = await session.get(link, ssl=False)
    soup = bs(await r.text(), 'lxml')
    return [i['href'] for i in soup.select('.attachment a')]


async def place_file(session: aiohttp.ClientSession, source: str) -> None:
    r = await session.get(source, ssl=False)

    file_name = urllib.parse.unquote(source.split("https://codereview.stackexchange.com/")[-1])
    async with aiofiles.open(os.path.join(OUTPUT_FOLDER, file_name), 'wb') as f:
        async for data in r.content.iter_any():
            await f.write(data)


async def main():
    async with aiohttp.ClientSession() as session:
        urls = await fetch_download_links(session, STATISTICS_URL)
        await asyncio.gather(*[place_file(session, url) for url in urls])


if __name__ == '__main__':
    t1 = time.perf_counter()
    print('process started...')
    asyncio.get_event_loop().run_until_complete(main())
    os.startfile(OUTPUT_FOLDER[:-1])
    t2 = time.perf_counter()
    print(f'Completed in {t2-t1} seconds.')

  • 1

    Большое спасибо. Мне понадобится немного времени, чтобы переварить, но я очень ценю то, что я уже видел +

    — QHarr

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *