Давайте сегодня подумаем о красоте. А именно, о красоте рабочего стола. А украсить его могут только «
Что понадобиться?
Для отправки запросов и скачивание картинок будем использовать библиотеку requests. Для того, чтобы распарсить полученные результаты и получить из них ссылки на картинки BeautifulSoup и lxml. Поэтому, для начала их нужно установить:
В работе скрипта так же потребуется библиотека для создания потоков threading, а так же библиотеки time, os и json. Поэтому перед началом работы давайте все это импортируем в наш скрипт.
Сайт, с которого будет происходить скачивание обоев,
Для этого щелкаем правой кнопкой мыши и в Яндекс.Браузере выбираем пункт «Исследовать элемент». Если же это будет Edge, то данный пункт называется «Проверить». Ну и так далее. Суть в том, что нужно попасть в инструменты разработчика.
Копируем их и вставляем в скрипт:
Функция получения пагинации
Давайте для начала создадим функцию получения пагинации со страницы из категории. Назову ее get_page_count(url). На вход она принимает только лишь один параметр, это ссылка на страницу категории. Далее выполняется запрос и полученные данные передаются в BeautifulSoup. После этого ищем блок с тэгом div, у которого id=pages. Получаем из этого блока все ссылки на страницы и забираем последнюю ссылку. Далее, разделяем ее и обрезаем лишние пробелы. Но, опытным путем было выявлено, что в некоторых категориях нет стрелки в тексте последней ссылки. Так как количество страниц с картинками помещается в блок пагинации целиком. И в этом случае скрипт падает с ошибкой. Для этого добавим блок try – except, чтобы эту ошибку отловить и просто забрать текст, без обрезки, из последней ссылки в блоке пагинации.
Получение ссылок на категории, названия категорий и количества страниц в каждой из них
Для того, чтобы названия категорий, ссылки на них и количество страниц в каждой категории хранились локально, нужно их во что-то сохранить. Я решил, что удобнее всего это будет сделать в файл JSON. А для того, чтобы ускорить получение ссылок на категории и прочих параметров, буду обрабатывать это в многопоточном режиме.
Для начала напишем функцию для нахождения названия категории, ссылки на нее и количества в ней страниц. Назвал я ее def get_link_category(url). На вход она получает ссылку на категорию. И дальше в коде для начала формируется общая ссылка на категорию. Потом ссылка, которую передали в функцию разделяется по слэшу и снова собирается для записи этой ссылки, в которой уже нет привязки к определенной странице, для записи в JSON. Это будет нужно для того, чтобы впоследствии брать ссылку из JSON и формировать из нее ссылку на загрузку пагинации и прочих параметров с определенной страницы.
Ну и последний я нахожу наименование категории. Именно оно будет служить ключом в словаре, который запишется в JSON. Формирую словарь и записываю в файл.
Чтобы было понятнее. В данной функции получаются данные только из одной категории. А вот каждая последующая ссылка на категорию передается из функции, в которой запускаются потоки. Назвал я ее def thread_func_category(). На входе она ничего не принимает. В ней есть ссылка, по которой делается запрос на страницу и собираются все ссылки на категории, которые потом записываются в словарь.
На следующем этапе запускается цикл для перебора значений полученного словаря. И в данном цикле формируется поток с указанием на функцию и параметрами, которые в данную функцию передаются. Ну, а дальше, потоки просто стартуют.
Полный код функции старта потоков загрузки ссылок на категории:
Загрузка картинок из категории, выбранной пользователем
Теперь можно перейти к важной части, а именно к загрузке картинок обоев из категории, которую указал пользователь. Назову ее get_pict_download(item, name_cat). Здесь на вход прилетает объект из найденных ссылок страницы, а так же имя категории. Оно будет нужно для того, чтобы указать папку с именем категории и загрузить в нее картинки. На первом этапе ищется имя картинки, под которым она будет сохранена и это имя очищается от мусора в виде всяких специальных символов, которые не совместимы с сохранением файлов в операционной системе.
Затем делается проверка на наличие картинки с данным названием в папке. Если такого названия нет, то картинка загружается. Если же есть, то не делается ничего. Это позволяет не дублировать загрузку одних и тех же картинок с перезаписью. Что позволяет, в случае, если вы не докачали категорию, а вы можете ее не докачать, так как картинок там о-о-очень много, ускорить работу скрипта. Ну и дальше формируется ссылка на картинку, после чего происходит ее загрузка и запись на диск.
Код загрузки картинки:
А дальше нужно создать функцию, в которой будут запускаться потоки для скачивания картинок. Назовем ее thread_func(url_cat, count_cat, name_cat). На входе данная функция принимает ссылку на категорию, пагинацию и имя категории.
Затем получаем время старта функции, для измерения скорости загрузки картинок. После выводим сообщение о том, что загружается категория такая-то, такая-то. И количество в ней страниц. От вывода сообщения о загрузке определенной картинки я отказался, потому, что порою вывод просто не успевает отобразиться в терминале. И получается перемешанная каша. Так как в итоге отображается все. Поэтому здесь я ограничился выводом сообщения о загрузке определенной страницы. Далее в цикле запускаем запрос, в котором получаем ссылки на картинки с первой и последующих страниц. Затем данные из запроса передаются в объект супа. Проверяется, если ли папка с именем категории. Если нет, создается. И в цикле запускаются потоки, которые привязаны к функции загрузки картинки и передают в нее необходимые параметры. А после того, как скрипт завершит свою работу, выводиться время загрузки картинок.
Осталась только функция main(). В ней для начала запускается функция обновления словаря. Мало ли что, может быть за время, пока не использовался скрипт появились новые категории или добавились обои и поэтому количество страниц может измениться. Тут есть один нюанс, который заключается в том, что запись категорий в словарь будет каждый раз производиться в произвольном порядке, а именно в порядке завершения работы потока. Поэтому отображение категорий всегда будет выводиться по-разному. Ну, или почти всегда. Далее делается небольшая пауза, чтобы дать сохраниться файлу на диск. Так как если этой паузы не делать, скрипт продолжает свою работу, файл еще не успевает сохраниться. А так как на следующем этапе этот словарь открывается для чтения, скрипт вываливается с ошибкой, что файл не найден. Потом формируется словарь из открытого JSON, с помощью которого определяется название категории, а так же ссылка и количество страниц.
Затем выводятся названия категорий на экран с определенными номерами. После чего выводиться сообщение с просьбой ввести номер категории для загрузки. Выполняется проверка, есть ли данный номер словаре. Если нет, ничего не делаем. Если есть, запускаем загрузку картинок.
На этом все. Нужно сказать, что я немного поэкспериментировал и первоначальную версию парсера обоев сделал без использования многопоточной загрузки. И попробовал загрузить какую-либо категорию. Надо сказать, что с такой скоростью загрузки, когда все выполняется последовательно, категорию можно грузить сутками. После чего было решено добавить многопоточность. И о чудо, скорость загрузки увеличилась в разы.
Я пробовал загрузить самую маленькую категорию, в которой шесть страниц. На каждой странице по двенадцать картинок. То есть, получается, что загружается 69. Скорее всего, потому, что некоторые картинки просто повторяются на страницах. Будем считать, что так, я просто не стал проверять. Так как это слишком долго и муторно. Так вот скрипт без потоков работает примерно от одной до двух минут. В зависимости от загруженности сети. А вот скрипт с потоками от пяти до семи секунд.
Вот небольшое видео, в котором я даю некоторые пояснения по сбору данных со страницы, а так же сравниваю скорость работы скрипта:
Спасибо за внимание. Надеюсь, что данная информация будет кому-нибудь полезна
Ссылка скрыта от гостей
». Привет, Денис Попов . Ну, а если более серьезно, то скачаем картинки с обоями с сайта, на котором их очень и очень много. Конечно же, для того, чтобы скачать картинки мы будем использовать Python, а загрузка картинок будет происходить в многопоточном режиме. Ну и наиболее полезная часть данной статьи состоит в том, что мы немного попрактикуемся в парсинге.Что понадобиться?
Для отправки запросов и скачивание картинок будем использовать библиотеку requests. Для того, чтобы распарсить полученные результаты и получить из них ссылки на картинки BeautifulSoup и lxml. Поэтому, для начала их нужно установить:
pip install bs4 requests lxml
В работе скрипта так же потребуется библиотека для создания потоков threading, а так же библиотеки time, os и json. Поэтому перед началом работы давайте все это импортируем в наш скрипт.
Python:
import json
import os.path
import threading
import time
import requests
from bs4 import BeautifulSoup
Сайт, с которого будет происходить скачивание обоев,
Ссылка скрыта от гостей
, на самом деле очень лоялен ко всякого рода попыткам его парсить. Он не сбрасывает соединение, не психует и не нервничает, когда к нему прилетает слишком много запросов. Идеальный пациент, можно сказать. Думаю, что он будет отдавать все данные даже в том случае, если мы не станем указывать заголовки запроса. Но, все же, чтобы работать по правилам и попрактиковаться, создадим их. Идем на сайт и смотрим заголовки в любом запросе. Забираем user-agent и accept.Для этого щелкаем правой кнопкой мыши и в Яндекс.Браузере выбираем пункт «Исследовать элемент». Если же это будет Edge, то данный пункт называется «Проверить». Ну и так далее. Суть в том, что нужно попасть в инструменты разработчика.
Копируем их и вставляем в скрипт:
Python:
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.119 '
'YaBrowser/22.3.0.2434 Yowser/2.5 Safari/537.36',
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
'application/signed-exchange;v=b3;q=0.9 '
}
Функция получения пагинации
Давайте для начала создадим функцию получения пагинации со страницы из категории. Назову ее get_page_count(url). На вход она принимает только лишь один параметр, это ссылка на страницу категории. Далее выполняется запрос и полученные данные передаются в BeautifulSoup. После этого ищем блок с тэгом div, у которого id=pages. Получаем из этого блока все ссылки на страницы и забираем последнюю ссылку. Далее, разделяем ее и обрезаем лишние пробелы. Но, опытным путем было выявлено, что в некоторых категориях нет стрелки в тексте последней ссылки. Так как количество страниц с картинками помещается в блок пагинации целиком. И в этом случае скрипт падает с ошибкой. Для этого добавим блок try – except, чтобы эту ошибку отловить и просто забрать текст, без обрезки, из последней ссылки в блоке пагинации.
Python:
def get_page_count(url):
req = requests.get(url=url, headers=headers)
soup = BeautifulSoup(req.text, 'lxml')
try:
page_count = int(soup.find('div', id='pages').find_all('a')[-1].text.split(" ")[1].strip())
except:
page_count = int(soup.find('div', id='pages').find_all('a')[-1].text.strip())
return page_count
Получение ссылок на категории, названия категорий и количества страниц в каждой из них
Для того, чтобы названия категорий, ссылки на них и количество страниц в каждой категории хранились локально, нужно их во что-то сохранить. Я решил, что удобнее всего это будет сделать в файл JSON. А для того, чтобы ускорить получение ссылок на категории и прочих параметров, буду обрабатывать это в многопоточном режиме.
Для начала напишем функцию для нахождения названия категории, ссылки на нее и количества в ней страниц. Назвал я ее def get_link_category(url). На вход она получает ссылку на категорию. И дальше в коде для начала формируется общая ссылка на категорию. Потом ссылка, которую передали в функцию разделяется по слэшу и снова собирается для записи этой ссылки, в которой уже нет привязки к определенной странице, для записи в JSON. Это будет нужно для того, чтобы впоследствии брать ссылку из JSON и формировать из нее ссылку на загрузку пагинации и прочих параметров с определенной страницы.
Ну и последний я нахожу наименование категории. Именно оно будет служить ключом в словаре, который запишется в JSON. Формирую словарь и записываю в файл.
Python:
def get_link_category(url):
url_cats = 'https://w-dog.ru' + url.find('div', class_='word').find('a')['href']
url_cat = str('https://w-dog.ru' + url.find('div', class_='word').find('a')['href']).split("/")
url_cat_s = f'{url_cat[0]}//{url_cat[2]}/{url_cat[3]}/{url_cat[4]}/{url_cat[5]}'
name_category = url.find('div', class_='word').find('a').text.strip()
p_count = get_page_count(url_cats)
category_dict[name_category] = {
'url_category': url_cat_s,
'page_count': p_count
}
with open('category_res.json', 'w', encoding='utf-8') as file:
json.dump(category_dict, file, indent=4, ensure_ascii=False)
Чтобы было понятнее. В данной функции получаются данные только из одной категории. А вот каждая последующая ссылка на категорию передается из функции, в которой запускаются потоки. Назвал я ее def thread_func_category(). На входе она ничего не принимает. В ней есть ссылка, по которой делается запрос на страницу и собираются все ссылки на категории, которые потом записываются в словарь.
На следующем этапе запускается цикл для перебора значений полученного словаря. И в данном цикле формируется поток с указанием на функцию и параметрами, которые в данную функцию передаются. Ну, а дальше, потоки просто стартуют.
Полный код функции старта потоков загрузки ссылок на категории:
Python:
def thread_func_category():
url = 'https://w-dog.ru/'
req = requests.get(url=url, headers=headers)
soup = BeautifulSoup(req.text, 'lxml')
all_category = soup.find_all('div', class_='wpitem category')
for url in all_category:
t = threading.Thread(target=get_link_category, kwargs={'url': url})
t.start()
Загрузка картинок из категории, выбранной пользователем
Теперь можно перейти к важной части, а именно к загрузке картинок обоев из категории, которую указал пользователь. Назову ее get_pict_download(item, name_cat). Здесь на вход прилетает объект из найденных ссылок страницы, а так же имя категории. Оно будет нужно для того, чтобы указать папку с именем категории и загрузить в нее картинки. На первом этапе ищется имя картинки, под которым она будет сохранена и это имя очищается от мусора в виде всяких специальных символов, которые не совместимы с сохранением файлов в операционной системе.
Затем делается проверка на наличие картинки с данным названием в папке. Если такого названия нет, то картинка загружается. Если же есть, то не делается ничего. Это позволяет не дублировать загрузку одних и тех же картинок с перезаписью. Что позволяет, в случае, если вы не докачали категорию, а вы можете ее не докачать, так как картинок там о-о-очень много, ускорить работу скрипта. Ну и дальше формируется ссылка на картинку, после чего происходит ее загрузка и запись на диск.
Код загрузки картинки:
Python:
def get_pict_download(item, name_cat):
name_pict = item.find('b', class_='word').text.strip().replace("/", " ").replace('"', ''). \
replace("'", "").replace(".", "")
if not os.path.isfile(os.path.join(name_cat, f'{name_pict}.jpg')):
url_pict = 'https://w-dog.ru' + item.find('div', class_='action-buttons').find('a')['href']
req = requests.get(url=url_pict, headers=headers)
with open(os.path.join(name_cat, f'{name_pict}.jpg'), 'wb') as file:
file.write(req.content)
А дальше нужно создать функцию, в которой будут запускаться потоки для скачивания картинок. Назовем ее thread_func(url_cat, count_cat, name_cat). На входе данная функция принимает ссылку на категорию, пагинацию и имя категории.
Затем получаем время старта функции, для измерения скорости загрузки картинок. После выводим сообщение о том, что загружается категория такая-то, такая-то. И количество в ней страниц. От вывода сообщения о загрузке определенной картинки я отказался, потому, что порою вывод просто не успевает отобразиться в терминале. И получается перемешанная каша. Так как в итоге отображается все. Поэтому здесь я ограничился выводом сообщения о загрузке определенной страницы. Далее в цикле запускаем запрос, в котором получаем ссылки на картинки с первой и последующих страниц. Затем данные из запроса передаются в объект супа. Проверяется, если ли папка с именем категории. Если нет, создается. И в цикле запускаются потоки, которые привязаны к функции загрузки картинки и передают в нее необходимые параметры. А после того, как скрипт завершит свою работу, выводиться время загрузки картинок.
Python:
def thread_func(url_cat, count_cat, name_cat):
start_time = time.monotonic()
print(f'[+] Загружаю категорию "{name_cat}". Количество страниц: {count_cat}\n')
if not os.path.isdir(name_cat):
os.mkdir(name_cat)
for nc in range(1, count_cat + 1):
print(f'[+] Загружаю >> Страница: {nc}/{count_cat}...')
req = requests.get(url=f"{url_cat}{nc}/best/", headers=headers)
soup = BeautifulSoup(req.text, 'lxml')
all_url_page = soup.find_all('div', class_='wpitem')
for item in all_url_page:
t = threading.Thread(target=get_pict_download, kwargs={'item': item, 'name_cat': name_cat})
t.start()
print(f'\nВремя загрузки файлов: {time.monotonic() - start_time}')
Осталась только функция main(). В ней для начала запускается функция обновления словаря. Мало ли что, может быть за время, пока не использовался скрипт появились новые категории или добавились обои и поэтому количество страниц может измениться. Тут есть один нюанс, который заключается в том, что запись категорий в словарь будет каждый раз производиться в произвольном порядке, а именно в порядке завершения работы потока. Поэтому отображение категорий всегда будет выводиться по-разному. Ну, или почти всегда. Далее делается небольшая пауза, чтобы дать сохраниться файлу на диск. Так как если этой паузы не делать, скрипт продолжает свою работу, файл еще не успевает сохраниться. А так как на следующем этапе этот словарь открывается для чтения, скрипт вываливается с ошибкой, что файл не найден. Потом формируется словарь из открытого JSON, с помощью которого определяется название категории, а так же ссылка и количество страниц.
Затем выводятся названия категорий на экран с определенными номерами. После чего выводиться сообщение с просьбой ввести номер категории для загрузки. Выполняется проверка, есть ли данный номер словаре. Если нет, ничего не делаем. Если есть, запускаем загрузку картинок.
Python:
def main():
print('[+] Обновляю словарь...\n')
thread_func_category()
time.sleep(2)
with open('category_res.json', 'r', encoding='utf-8') as file:
cat_dict = json.load(file)
dict_cat = {}
for num, cat in enumerate(cat_dict):
print(f'{num}. {cat} | {cat_dict[cat]["page_count"]} страниц...')
dict_cat[num] = {
'url_category': cat_dict[cat]["url_category"],
'page_count': cat_dict[cat]["page_count"],
'name_cat': cat
}
num_cat = int(input('\n[+] - Введите номер категории для загрузки: '))
# передача данных для запуска потоков загрузки картинок
if num_cat in dict_cat:
thread_func(f"{dict_cat[num_cat]['url_category']}/", dict_cat[num_cat]['page_count'], dict_cat[num_cat]['name_cat'])
else:
print('[-] Вы ввели неверный номер категории для загрузки.')
exit(0)
На этом все. Нужно сказать, что я немного поэкспериментировал и первоначальную версию парсера обоев сделал без использования многопоточной загрузки. И попробовал загрузить какую-либо категорию. Надо сказать, что с такой скоростью загрузки, когда все выполняется последовательно, категорию можно грузить сутками. После чего было решено добавить многопоточность. И о чудо, скорость загрузки увеличилась в разы.
Я пробовал загрузить самую маленькую категорию, в которой шесть страниц. На каждой странице по двенадцать картинок. То есть, получается, что загружается 69. Скорее всего, потому, что некоторые картинки просто повторяются на страницах. Будем считать, что так, я просто не стал проверять. Так как это слишком долго и муторно. Так вот скрипт без потоков работает примерно от одной до двух минут. В зависимости от загруженности сети. А вот скрипт с потоками от пяти до семи секунд.
Вот небольшое видео, в котором я даю некоторые пояснения по сбору данных со страницы, а так же сравниваю скорость работы скрипта:
Python:
import json
import os.path
import threading
import time
import requests
from bs4 import BeautifulSoup
# заголовки для запроса
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.119 '
'YaBrowser/22.3.0.2434 Yowser/2.5 Safari/537.36',
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
'application/signed-exchange;v=b3;q=0.9 '
}
category_dict = {}
# получение пагинации
# находим последнюю станицу и чистим от мусора
# исключение добавлено потому, что есть разделы, у которых
# меньше 7 страниц. В этом случае пагинация немного отличается
def get_page_count(url):
req = requests.get(url=url, headers=headers)
soup = BeautifulSoup(req.text, 'lxml')
try:
page_count = int(soup.find('div', id='pages').find_all('a')[-1].text.split(" ")[1].strip())
except:
page_count = int(soup.find('div', id='pages').find_all('a')[-1].text.strip())
return page_count
# получаем ссылки на категории и сохраняем с JSON
# получение ссылок происходит при каждом запуске программы
# так как считывается так же и количество стараниц в каждой категории
# и по прошествии времени оно может изменяться
def get_link_category(url):
url_cats = 'https://w-dog.ru' + url.find('div', class_='word').find('a')['href']
url_cat = str('https://w-dog.ru' + url.find('div', class_='word').find('a')['href']).split("/")
url_cat_s = f'{url_cat[0]}//{url_cat[2]}/{url_cat[3]}/{url_cat[4]}/{url_cat[5]}'
name_category = url.find('div', class_='word').find('a').text.strip()
p_count = get_page_count(url_cats)
category_dict[name_category] = {
'url_category': url_cat_s,
'page_count': p_count
}
with open('category_res.json', 'w', encoding='utf-8') as file:
json.dump(category_dict, file, indent=4, ensure_ascii=False)
def thread_func_category():
url = 'https://w-dog.ru/'
req = requests.get(url=url, headers=headers)
soup = BeautifulSoup(req.text, 'lxml')
all_category = soup.find_all('div', class_='wpitem category')
for url in all_category:
t = threading.Thread(target=get_link_category, kwargs={'url': url})
t.start()
# загрузка картинок из категории
# получение названия категории
# создание папки с именем категории куда будут загружаться картинки
# поиск всех ссылок настранице, скачивание их в цикле
# и сохранение в созданную папку
def get_pict_download(item, name_cat, count_cat):
name_pict = item.find('b', class_='word').text.strip().replace("/", " ").replace('"', ''). \
replace("'", "").replace(".", "")
if not os.path.isfile(os.path.join(name_cat, f'{name_pict}.jpg')):
url_pict = 'https://w-dog.ru' + item.find('div', class_='action-buttons').find('a')['href']
req = requests.get(url=url_pict, headers=headers)
with open(os.path.join(name_cat, f'{name_pict}.jpg'), 'wb') as file:
file.write(req.content)
def thread_func(url_cat, count_cat, name_cat):
start_time = time.monotonic()
print(f'[+] Загружаю категорию "{name_cat}". Количество страниц: {count_cat}\n')
for nc in range(1, count_cat + 1):
print(f'[+] Загружаю >> Страница: {nc}/{count_cat}...')
req = requests.get(url=f"{url_cat}{nc}/best/", headers=headers)
soup = BeautifulSoup(req.text, 'lxml')
name_cat = soup.find('div', id='content-top').find('h2').text.strip()
if not os.path.isdir(name_cat):
os.mkdir(name_cat)
all_url_page = soup.find_all('div', class_='wpitem')
for item in all_url_page:
t = threading.Thread(target=get_pict_download, kwargs={'item': item, 'name_cat': name_cat,
'count_cat': count_cat})
t.start()
print(f'\nВремя загрузки файлов: {time.monotonic() - start_time}')
def main():
print('[+] Обновляю словарь...\n')
thread_func_category()
time.sleep(2)
with open('category_res.json', 'r', encoding='utf-8') as file:
cat_dict = json.load(file)
dict_cat = {}
for num, cat in enumerate(cat_dict):
print(f'{num}. {cat} | {cat_dict[cat]["page_count"]} страниц...')
dict_cat[num] = {
'url_category': cat_dict[cat]["url_category"],
'page_count': cat_dict[cat]["page_count"],
'name_cat': cat
}
num_cat = int(input('\n[+] - Введите номер категории для загрузки: '))
# передача данных для запуска потоков загрузки картинок
if num_cat in dict_cat:
thread_func(f"{dict_cat[num_cat]['url_category']}/", dict_cat[num_cat]['page_count'], dict_cat[num_cat]['name_cat'])
else:
print('[-] Вы ввели неверный номер категории для загрузки.')
exit(0)
if __name__ == "__main__":
main()
Спасибо за внимание. Надеюсь, что данная информация будет кому-нибудь полезна