Как, используя Python, загрузить все изображения с веб-страницы

Наверняка, хоть раз в жизни вам хотелось загрузить все изображения с понравившейся веб‑страницы? Сейчас вы узнаете, как создать парсер Python, который извлекает все изображения с веб‑страницы по ее URL‑адресу и загружает их с помощью библиотек requests и BeautifulSoup.

Для начала нам понадобится довольно много зависимостей, давайте установим их:

pip3 install requests bs4 tqdm

Откройте новый файл с именем download_images.py и импортируйте необходимые модули:

import requests
import os
from tqdm import tqdm
from bs4 import BeautifulSoup as bs
from urllib.parse import urljoin, urlparse

Первое, что мы сделаем, создадим валидатор URL‑адреса, который проверяет, является ли переданный URL‑адрес действительным, поскольку есть некоторые веб‑сайты, которые помещают закодированные данные вместо URL‑адреса, поэтому нам нужно пропустить их:

def is_valid(url):
    """
    Проверяет, является ли url допустимым URL
    """
    parsed = urlparse(url)
    return bool(parsed.netloc) and bool(parsed.scheme)

Функция urlparse() разбирает URL‑адрес на шесть составных частей, а нам просто нужно увидеть, есть ли там netloc (имя домена) и scheme (протокол).
Во-вторых, я собираюсь написать основную функцию, которая определет все URL‑адреса изображений на веб‑странице:

def get_all_images(url):
    """
    Возвращает все URL‑адреса изображений по одному `url`
    """
    soup = bs(requests.get(url).content, "html.parser")

HTML-содержимое веб‑страницы находится в объекте soup, чтобы извлечь все теги img в HTML, нам нужно использовать метод soup.find_all("img"). Посмотрим на него в действии:

    urls = []
    for img in tqdm(soup.find_all("img"), "Extracting images"):
        img_url = img.attrs.get("src")
        if not img_url:
            # если img не содержит атрибута src, просто пропустите
            continue

Это получит все элементы img в виде списка Python.

Я обернул его в объект tqdm, чтобы напечатать индикатор выполнения. Чтобы получить URL‑адрес тега img, есть атрибут src. Однако есть некоторые теги, которые не содержат атрибута src, мы пропускаем, используя оператор continue.

Теперь нам нужно убедиться, что URL‑адрес является абсолютным:

        # сделать URL абсолютным, присоединив домен к только что извлеченному URL
        img_url = urljoin(url, img_url)

Встречаются URL‑адреса, содержащие пары ключ-значение метода GET HTTP‑протокола, которые нам не нравятся (заканчиваются чем-то вроде этого «/image.png?c=3.2.5«), удалим их:

        try:
            pos = img_url.index("?")
            img_url = img_url[:pos]
        except ValueError:
            pass

Получаем позицию символа «?«, а затем после него всё удаляем, а если его нет, то возникает исключение ValueError, поэтому я заключил его в блок try/except (конечно, вы можете реализовать сей момент лучше, поделитесь своим решением в комментарии).

Теперь давайте убедимся, что каждый URL‑адрес действителен и возвращает все URL‑адреса изображений:

        # наконец, если URL действителен
        if is_valid(img_url):
            urls.append(img_url)
    return urls

Теперь, когда у нас есть функция, которая получает все URL‑адреса изображений, нам нужна функция для загрузки файлов из Интернета, я использовал следующую функцию:

def download(url, pathname):
    """
    Загружает файл по URL‑адресу и помещает его в папку `pathname`
    """
    # если путь не существует, сделать этот путь dir
    if not os.path.isdir(pathname):
        os.makedirs(pathname)
    # загружаем тело ответа по частям, а не сразу
    response = requests.get(url, stream=True)
    # get the total file size
    file_size = int(response.headers.get("Content-Length", 0))
    # получаем имя файла
    filename = os.path.join(pathname, url.split("/")[-1])
    # индикатор выполнения, изменение единицы измерения на байты вместо итераций (по умолчанию tqdm)
    progress = tqdm(response.iter_content(1024), f"Downloading {filename}", total=file_size, unit="B", unit_scale=True, unit_divisor=1024)
    with open(filename, "wb") as f:
        for data in progress.iterable:
            # записываем прочитанные данные, в файл
            f.write(data)
            # обновить вручную индикатор выполнения
            progress.update(len(data))

Вышеупомянутая функция принимает URL‑адрес файла для загрузки и путь к папке, в которую этот файл будет сохранен.

Наконец, вот основная функция:

def main(url, path):
    # получить все изображения
    imgs = get_all_images(url)
    for img in imgs:
        # для каждого изображения, загрузите его
        download(img, path)

Посмотрите код файла download_images.py целиком:

import requests
import os
from tqdm import tqdm
from bs4 import BeautifulSoup as bs
from urllib.parse import urljoin, urlparse


def is_valid(url):
    """
    Проверяем, является ли url действительным URL
    """
    parsed = urlparse(url)
    return bool(parsed.netloc) and bool(parsed.scheme)


def get_all_images(url):
    """
    Возвращает все URL‑адреса изображений по одному `url`
    """
    soup = bs(requests.get(url).content, "html.parser")
    urls = []
    for img in tqdm(soup.find_all("img"), "Получено изображение"):
        img_url = img.attrs.get("src")
        if not img_url:
            # если img не содержит атрибута src, просто пропускаем
            continue
        # сделаем URL абсолютным, присоединив имя домена к только что извлеченному URL
        img_url = urljoin(url, img_url)
        # удалим URL‑адреса типа '/hsts-pixel.gif?c=3.2.5'
        try:
            pos = img_url.index("?")
            img_url = img_url[:pos]
        except ValueError:
            pass
        # наконец, если URL действителен
        if is_valid(img_url):
            urls.append(img_url)
    return urls


def download(url, pathname):
    """
    Загружает файл по URL‑адресу и помещает его в папку `pathname`
    """
    # если путь не существует, создать dir
    if not os.path.isdir(pathname):
        os.makedirs(pathname)
    # загружаем тело ответа по частям, а не сразу
    response = requests.get(url, stream=True)

    # получить общий размер файла
    file_size = int(response.headers.get("Content-Length", 0))

    # получаем имя файла
    filename = os.path.join(pathname, url.split("/")[-1])

    # индикатор выполнения, изменение единицы измерения на байты вместо итераций (по умолчанию tqdm)
    progress = tqdm(response.iter_content(1024), f"Загружен {filename}", total=file_size, unit="B", unit_scale=True, unit_divisor=1024)
    with open(filename, "wb") as f:
        for data in progress.iterable:
            # записываем прочитанные данные в файл
            f.write(data)
            # обновление индикатора выполнения вручную
            progress.update(len(data))


def main(url, path):
    # получить все изображения
    imgs = get_all_images(url)
    for img in imgs:
        # скачать для каждого img
        download(img, path)
    


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="Этот скрипт загружает все изображения с веб‑страницы.")
    parser.add_argument("url", help="URL‑адрес веб‑страницы, с которой вы хотите загрузить изображения.")
    parser.add_argument("-p", "--path", help="Каталог, в котором вы хотите хранить изображения, по умолчанию - это домен переданного URL.")
    
    args = parser.parse_args()
    url = args.url
    path = args.path

    if not path:
        # если путь не указан, используйте доменное имя этого url в качестве имени папки
        path = urlparse(url).netloc
    
    main(url, path)

Получение всех URL‑адресов изображений с этой страницы и загрузка каждого из них по одному. Давайте проверим:

main("https://waksoft.susu.ru/", "waksoft-susu-ru")

Будут загружены все изображения с указанного URL‑адреса и сохранены в папке «waksoft‑susu‑ru», которая будет создана автоматически.

Однако обратите внимание, что есть некоторые веб‑сайты, которые загружают свои данные с помощью Javascript, в этом случае вы должны вместо этого использовать библиотеку requests_html. Я уже сделал другой скрипт, который вносит некоторые изменения в исходный и обрабатывает рендеринг Javascript, и записал его в файл download_images_js.py, посмотрите:

from requests_html import HTMLSession
import requests
from tqdm import tqdm
from bs4 import BeautifulSoup as bs
from urllib.parse import urljoin, urlparse

import os


def is_valid(url):
    """
    Проверяем, является ли url действительным URL
    """
    parsed = urlparse(url)
    return bool(parsed.netloc) and bool(parsed.scheme)


def get_all_images(url):
    """
    Возвращает все URL‑адреса изображений по одному `url`
    """
    # инициализировать сеанс
    session = HTMLSession()
    # делаем HTTP‑запрос и получаем ответ
    response = session.get(url)
    # выполнить Javascript с таймаутом 20 секунд
    response.html.render(timeout=20)
    # создаем парсер soup
    soup = bs(response.html.html, "html.parser")
    urls = []
    for img in tqdm(soup.find_all("img"), "Извлечено изображение"):
        img_url = img.attrs.get("src") or img.attrs.get("data-src") or img.attrs.get("data-original")
        print(img_url)
        if not img_url:
            # если img не содержит атрибута src, просто пропустим
            continue
        # сделаем URL абсолютным, присоединив имя домена к только что извлеченному URL
        img_url = urljoin(url, img_url)
        # удалим URL‑адреса типа '/hsts-pixel.gif?c=3.2.5'
        try:
            pos = img_url.index("?")
            img_url = img_url[:pos]
        except ValueError:
            pass
        # наконец, если URL действителен
        if is_valid(img_url):
            urls.append(img_url)
    # закрыть сеанс, чтобы завершить процесс браузера
    session.close()
    return urls


def download(url, pathname):
    """
    Загружает файл по URL‑адресу и помещает его в папку `pathname`
    """
    # если папка не существует, создадим папку с именем dir
    if not os.path.isdir(pathname):
        os.makedirs(pathname)
    # загружаем тело ответа по частям, а не сразу
    response = requests.get(url, stream=True)

    # получить общий размер файла
    file_size = int(response.headers.get("Content-Length", 0))

    # получаем имя файла
    filename = os.path.join(pathname, url.split("/")[-1])

    # индикатор выполнения, изменяем единицы измерения на байты вместо итераций (по умолчанию tqdm)
    progress = tqdm(response.iter_content(1024), f"Downloading {filename}", total=file_size, unit="B", unit_scale=True, unit_divisor=1024)
    with open(filename, "wb") as f:
        for data in progress.iterable:
            # записываем прочитанные данные в файл
            f.write(data)
            # обновим индикатор выполнения вручную
            progress.update(len(data))


def main(url, path):
    # получить все изображения
    imgs = get_all_images(url)
    for img in imgs:
        # скачать для каждого img
        download(img, path)
    


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="Этот скрипт загружает все изображения с веб‑страницы.")
    parser.add_argument("url", help="URL‑адрес веб‑страницы, с которой вы хотите загрузить изображения.")
    parser.add_argument("-p", "--path", help="Каталог, в котором вы хотите хранить изображения, по умолчанию - это домен переданного URL")
    
    args = parser.parse_args()
    url = args.url
    path = args.path

    if not path:
        # если путь не указан, в качестве имени папки используйте доменное имя rl
        path = urlparse(url).netloc
    
    main(url, path)

Хорошо, всё на сегодя!

How to Download All Images from a Web Page in Python

Print Friendly, PDF & Email

CC BY-NC 4.0 Как, используя Python, загрузить все изображения с веб-страницы, опубликовано К ВВ, лицензия — Creative Commons Attribution-NonCommercial 4.0 International.


Респект и уважуха

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