diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f3430c3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.py] +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index b150842..47241b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -venv/ images/ diff --git a/stanis-tits.py b/stanis-tits.py index fb285e1..9809999 100755 --- a/stanis-tits.py +++ b/stanis-tits.py @@ -7,24 +7,30 @@ import requests from bs4 import BeautifulSoup __author__ = "Alexander Popov" -__version__ = "1.0.1" +__version__ = "1.0.2" __license__ = "Unlicense" -DOWNLOAD_DIR = "./images" +# Путь к директории для загрузки изображений +DOWNLOAD_DIRECTORY = "./images" -def checkResumeFile(): +def resume_load(): + """Возвращает список последних 20 загруженных файлов""" + + # Проверяет наличие файла .resume if not os.path.exists( "{0}/.resume".format( - DOWNLOAD_DIR, + DOWNLOAD_DIRECTORY, ) ): - if not os.path.exists(DOWNLOAD_DIR): - os.mkdir(DOWNLOAD_DIR) + # Создаёт директорию для загрузки сисек + if not os.path.exists(DOWNLOAD_DIRECTORY): + os.mkdir(DOWNLOAD_DIRECTORY) + # Создаёт файл .resume и пишет в него 0 with open( "{0}/.resume".format( - DOWNLOAD_DIR, + DOWNLOAD_DIRECTORY, ), "w", ) as f: @@ -33,50 +39,64 @@ def checkResumeFile(): else: with open( "{0}/.resume".format( - DOWNLOAD_DIR, + DOWNLOAD_DIRECTORY, ), "r", ) as f: - lines = [line.split("\n")[0] for line in f][-20:] + lines = [int(line.split("\n")[0]) for line in f][-20:] return lines -def saveResume(resumeList): - resumeList.sort() +def resume_save(donwloaded_list): + """Сохраняет список последних 20 загруженных файлов""" + + donwloaded_list.sort() with open( "{0}/.resume".format( - DOWNLOAD_DIR, + DOWNLOAD_DIRECTORY, ), "w", encoding="utf-8", ) as f: - for item in resumeList[-20:]: + for item in donwloaded_list[-20:]: f.write("{0}\n".format(item)) -def getImagesLinks(page): - URL = lambda page: "http://blog.stanis.ru/?back={0}".format( - page, - ) - COOKIES = dict(block="951") +def get_images_links(page): + """В качестве аргумента получает номер страницы + и возвращает списком адреса всех изображений""" - r = requests.get(URL(page), cookies=COOKIES) + # На сайте фильтр изображений настроен через cookies + # Так что устанавливаем их в нужное положение + cookies = dict(block="951") + + # Загружаем страницу и подсовываем её парсеру с необходимыми параметрами + r = requests.get( + "http://blog.stanis.ru/?back={0}".format( + page, + ), + cookies=cookies, + ) soup = BeautifulSoup( r.text.encode("cp1251"), "html.parser", from_encoding="windows-1251" ) - imagesData = soup.findAll("img", src=re.compile("img/*")) + # Получаем все теги на странице + img_tags = soup.findAll("img", src=re.compile("img/*")) - imagesUrl = list() + # Получаем все адреса изображений и сохраняем их в список img_links + img_links = list() + for image in img_tags: + img_links.append(image["src"].split("/")[1]) - for image in imagesData: - imagesUrl.append(image["src"].split("/")[1]) - - return imagesUrl + return img_links -def imageDownload(image): +def image_download(image): + """В качестве аргумента получает уникальное имя изображения, + скачивает и сохраняет его на диск""" + response = requests.get( "https://blog.stanis.ru/imgs/{0}".format( image, @@ -84,33 +104,82 @@ def imageDownload(image): stream=True, ) - with open("{0}/{1}".format(DOWNLOAD_DIR, image), "wb") as out_image: - shutil.copyfileobj( - response.raw, - out_image, - ) + image_size = int(response.headers.get("Content-Length", 0)) + + if ( + os.path.exists("{0}/{1}".format(DOWNLOAD_DIRECTORY, image)) + and int(os.path.getsize("{0}/{1}".format(DOWNLOAD_DIRECTORY, image))) + == image_size + ): + pass + else: + with open("{0}/{1}".format(DOWNLOAD_DIRECTORY, image), "wb") as out_image: + shutil.copyfileobj( + response.raw, + out_image, + ) if __name__ == "__main__": - resumeFiles = checkResumeFile() + """Главный цикл программы""" - LOOP = True - downloadPage = 0 + resume_files = resume_load() - while LOOP: - imagesLinks = getImagesLinks(downloadPage) - imagesLinks.sort() + current_page = 0 + downloaded_counter = 0 - for image in imagesLinks: - if not image.split(".")[0] in resumeFiles: - imageDownload(image) - resumeFiles.insert( - 0, - image.split(".")[0], - ) - else: - LOOP = False + WHILE_BREAK = False - downloadPage += 1 + while True: + try: + # Получаем адреса изображений и сортируем их в порядке возрастания + images = get_images_links(current_page) + images.sort() - saveResume(resumeFiles) + # Костыль + if WHILE_BREAK: + break + + # По очереди скачиваем изображения + for image in images: + # На сайте могут обновляться изображения, + # когда находятся варианты более лучшего + # качества и разрешения. + # К именам таких файлов добавляется _NUM, + # где NUM - количество изменений, + # например 1356_3.jpg + + # Файл .resume хранит последние 20 загруженных файлов. + # Бывает иногда, что изображение удаляется + # и может получиться так, что сохраненное имя в .resume + # будет отсутсвовать на странице, что будет в холостую + # продолжать работу скрипта. + + # Файлы на странице расположены в обратном порядке, + # для этого перед их загрузкой и сохранением в .resume + # списки сортируются от меньшего к большему. + + # По этому было принято решение, удалять часть _NUM + # из имени файла при сохранение в .resume. + # Один хуй никто не будет парсить сайт с самого начала, + # чтобы скачать сиськи в более высоком качестве. + + # Если в списке resume присутствует текущий загружаемый файл + # тогда цикл программы останавливается. + # P.S. смотри на костыль WHILE_BREAK + if not int(image.split(".")[0].split("_")[0]) in resume_files: + image_download(image) + resume_files.insert( + 0, + int(image.split(".")[0].split("_")[0]), + ) + downloaded_counter += 1 + resume_save(resume_files) + else: + WHILE_BREAK = True + break + + current_page += 1 + except KeyboardInterrupt: + print("Загружено {0} файлов.".format(downloaded_counter)) + quit()