Иногда я люблю почитать журналы. Но я уже давно их не покупал. Наверное последний купленный мой журнал был «Хакер» 2009 года. Уж не помню даже, какой выпуск. Помню, что тогда к журналам прилагался диск с программами и в этом номере был диск с дистрибутивом Linux Mandriva. Красивая была ось. Ну да суть не в этом. В последующее время я все больше предпочитал читать журналы, да и книги в электронном виде, потому, что бумажные издания несколько кусались по цене, да и кусаются сейчас. Так что, в этом плане особо ничего не изменилось. И если раньше было очень много сайтов и групп в том же ВК, где можно было скачать любой журнал почти за любой год, то с приходом авторского права все резко изменилось. Стало труднее найти свежие журналы, сайты если и находились, то через какое-то время переставали выкладывать новые из-за обращений правообладателя. А впоследствии и вовсе стали блокироваться. И я стал предпочитать скачивать журналы себе на жесткий диск. Благо места хватало. Тем более, что после прочтения его можно было сжать в архив и отправить в облако на долгосрочное хранение. Но, это, так сказать, преамбула.
Захотел я почитать свежий выпуск журнала. Не суть какого. Тут важно то, что я даже нашел сайт, где его можно было почитать.
Наш подопытный — это сайт zhurnala.ru. От остальных сайтов того же содержания отличается мало, ну, разве что, интерфейс немного получше.
Так вот, можно почитать. Онлайн. И даже скачать. Вот только ссылки на скачивание вели все на разные файлообменники. А я их, почему-то, не люблю, наверное, с того самого времени, как только в первый раз столкнулся с ними. Ладно, подумал я. Качать пока не буду. Почитаю прямо на сайте. Благо, там встроенный плеер для просмотра картинок из журнала. Но, тут тоже меня ждало разочарование. В самом плеере журнала не было. А была просто ссылка на сайт issuu.com, который у меня заблокирован провайдером. Ну не ставить же VPN из-за такой малости.
Можно было бы махнуть рукой, но я решил, а почему бы не скачать журнал себе на диск использовав для этого Python? Вдруг получиться. И я решил исследовать страницу. Для начала я пробежался по коду страницы, но, как и ожидалось, ничего особо интересного там не было. Кроме нескольких нужных ссылок. Тогда я полез в запросы.
Чуток покопался и нашел GET-запрос, в ответ на который прилетает JSON со всеми ссылками на картинки страниц журнала. Это запрос reader3_4.json. Вот ссылка:
Конечно же, для того, чтобы скачать только лишь один журнал этих данных вполне достаточно, но, я решил немного поработать над тем, чтобы можно было скачивать любой журнал с данного сайта. Для того, чтобы получить JSON со ссылками нужно передать в ссылке название журнала. А где его можно взять? То есть, вот эта вот часть — itnews_032022 — название журнала и есть. В ссылке на страницу с журналом ничего похоже не наблюдалось. Тогда я решил все же покопаться в коде страницы. А именно в том фрейме, где загружается плеер. Именно там я заметил ссылку, в которой содержалось нужное мне название. Ну и заодно прихватить из кода название бумажного журнала. Что же, давайте приступим к реализации кода.
Что потребуется?
Нужно установить библиотеку requests, bs4, lxml, PIL и img2pdf. Сделать это можно с помощью команды:
Для чего последние две библиотеки я поясню уже по ходу статьи. И импортировать в модуль библиотеки os.path, time.
Вот так вот выглядит полный блок импорта:
Двигаемся дальше. Теперь, чтобы корректно делать запросы, желательно установить заголовки запроса. Вот, что у меня получилось:
Получение сегмента ссылки для загрузки JSON
Давайте создадим функцию, в которую будем передавать ссылку на страницу с журналом, а на выходе получать нужный нам сегмент ссылки для будущей загрузки JSON, а так же название журнала. Создадим функцию get_linksegment_on_json(url).
Делаем запрос на страницу журнала, после чего создаем объект BeautifulSoup, в который передаем содержимое запроса и указываем парсер, с помощью которого будем парсить страницу. Ну, а далее, возвращаем найденное значение сегмента и найденное название журнала. Таким образом у нас будет возвращаться список из двух элементов. Саму функцию поиска сегмента вызовем в функции main().
Получаем JSON со ссылками и загружаем картинки
Необходимый сегмент, который по сути и является идентификатором журнала получен. Теперь можно приступать к получению JSON и загрузке картинок. Создадим для начала папку, в которую будем складывать загруженные картинки. Далее создаем список, в который будем записывать папку и название картинки. Это будем нужно нам для дальнейшего использования. И делаем запрос на получение JSON со вставкой идентификатора журнала. Затем пробегаемся по разделу со ссылками. Для этого используем цикл for в котором перебираем элементы JSON, при этом узнаем количество этих элементов, которое равно длине раздела pages.
Выводим в терминал принт, чтобы не было скучно и понятно, на каком этапе мы находимся:
Загружаем картинку по ссылке полученной из JSON, после чего сохраняем картинку в созданную для картинок папку и добавляем ссылку на картинку в список. После того, как все картинки будут загружены, функция возвращает список со ссылками на картинки.
Объединяем картинки в PDF
После того, как картинки загружены, их надо собрать в файл PDF. Для этого будем использовать библиотеку img2pdf, в которую передается для конвертации список картинок. Таким образом, открываем файл на запись в побайтовом режиме. Указываем название файла, которое мы получили ранее со страницы. И в функции записи файла передаем функцию img2pdf с параметром convert, куда передаем полученный нами список картинок.
После конвертации выводим сообщение, что все закончено и пробегаемся в цикле по ссылкам на картинки, в котором их удаляем, чтобы не мусорить на диске. После чего удаляем и саму папку, в которую картинки загружали.
Сжимаем изображения перед конвертацией в PDF
Я немного подумал и решил, что неплохо было бы еще и немного сжать полученные картинки перед тем, как собирать их в PDF, для того, чтобы уменьшить размер получаемого файла. Качество страдает не особо. Можно сказать, что невооруженным глазом долго придется присматриваться к тому, что же изменилось на картинке. Но, справедливости ради надо отметить, что конвертация не всегда одинаково полезна для всех картинок. Бывает, что исходные картинки загружаются уже не особо высокого качества, а их сжатие чуть-чуть его еще ухудшает. Но, читаемо, в принципе. Так что, на вкус и цвет все фломастеры разные. Поэтому, я добавил еще и выбор для пользователя, в котором он будет решать, конвертировать ли ему картинки или нет.
Для конвертации картинок будем использовать библиотеку Pillow из которой загрузим модуль Image. Создаем в функции список со ссылками на конвертированные файлы, чтобы их впоследствии собрать в PDF, после создаем папку для сжатых картинок. Перебираем список со ссылками и каждую картинку передаем для открытия в Image. После чего, стразу же сохраняем с опцией optimize=True и качеством 40 от исходной картинки — quality=40. Затем добавляем ссылку на картинку в словарь. После того, как конвертация завершиться, удаляем исходные картинки и возвращаем словарь, в котором сохраняли ссылки на конвертированные картинки.
Вот в принципе и все. Теперь, с помощью данного скрипта можно скачать любой журнал с сайта zhurnala.ru. Копируем ссылку на страницу с журналом, передаем ее в скрипт и получаем на выходе собранные в файла PDF картинки страниц журнала.
Ну, а вот пример собранных журналов, один, тот, что больше размером, без сжатия изображений, а второй со сжатием.
Как видите, разница в размере присутствует. А теперь полный код скрипта:
Спасибо за внимание. Надеюсь, что данная статья кому-то будет полезной
Захотел я почитать свежий выпуск журнала. Не суть какого. Тут важно то, что я даже нашел сайт, где его можно было почитать.
Наш подопытный — это сайт zhurnala.ru. От остальных сайтов того же содержания отличается мало, ну, разве что, интерфейс немного получше.
И да, все, что вы прочитаете в данной статье предоставлено исключительно в общеобразовательных целях и для прокачки навыков и умений. Это, если, конечно, вы этого не умеете )
Так вот, можно почитать. Онлайн. И даже скачать. Вот только ссылки на скачивание вели все на разные файлообменники. А я их, почему-то, не люблю, наверное, с того самого времени, как только в первый раз столкнулся с ними. Ладно, подумал я. Качать пока не буду. Почитаю прямо на сайте. Благо, там встроенный плеер для просмотра картинок из журнала. Но, тут тоже меня ждало разочарование. В самом плеере журнала не было. А была просто ссылка на сайт issuu.com, который у меня заблокирован провайдером. Ну не ставить же VPN из-за такой малости.
Чуток покопался и нашел GET-запрос, в ответ на который прилетает JSON со всеми ссылками на картинки страниц журнала. Это запрос reader3_4.json. Вот ссылка:
Ссылка скрыта от гостей
Конечно же, для того, чтобы скачать только лишь один журнал этих данных вполне достаточно, но, я решил немного поработать над тем, чтобы можно было скачивать любой журнал с данного сайта. Для того, чтобы получить JSON со ссылками нужно передать в ссылке название журнала. А где его можно взять? То есть, вот эта вот часть — itnews_032022 — название журнала и есть. В ссылке на страницу с журналом ничего похоже не наблюдалось. Тогда я решил все же покопаться в коде страницы. А именно в том фрейме, где загружается плеер. Именно там я заметил ссылку, в которой содержалось нужное мне название. Ну и заодно прихватить из кода название бумажного журнала. Что же, давайте приступим к реализации кода.
Что потребуется?
Нужно установить библиотеку requests, bs4, lxml, PIL и img2pdf. Сделать это можно с помощью команды:
pip install requests bs4 lxml Pillow img2pdf
Для чего последние две библиотеки я поясню уже по ходу статьи. И импортировать в модуль библиотеки os.path, time.
Вот так вот выглядит полный блок импорта:
Python:
import os.path
import time
import requests
from bs4 import BeautifulSoup
from PIL import Image
import img2pdf
Двигаемся дальше. Теперь, чтобы корректно делать запросы, желательно установить заголовки запроса. Вот, что у меня получилось:
Python:
headers = {
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.174 '
'YaBrowser/22.1.3.942 Yowser/2.5 Safari/537.36',
'accept': '*/*'
}
Получение сегмента ссылки для загрузки JSON
Давайте создадим функцию, в которую будем передавать ссылку на страницу с журналом, а на выходе получать нужный нам сегмент ссылки для будущей загрузки JSON, а так же название журнала. Создадим функцию get_linksegment_on_json(url).
Python:
def get_linksegment_on_json(url):
req = requests.get(url=url, headers=headers)
soup = BeautifulSoup(req.text, 'lxml')
return soup.find('div', class_='entry-content').find_all('p')[1].\
find('iframe', class_='lazy lazy-hidden')['data-src'].split("=")[-1], soup.find('h1', class_='entry-title').text
Делаем запрос на страницу журнала, после чего создаем объект BeautifulSoup, в который передаем содержимое запроса и указываем парсер, с помощью которого будем парсить страницу. Ну, а далее, возвращаем найденное значение сегмента и найденное название журнала. Таким образом у нас будет возвращаться список из двух элементов. Саму функцию поиска сегмента вызовем в функции main().
Python:
def main():
segment = get_linksegment_on_json(input('[+] Введите ссылку на страницу с журналом: '))
Получаем JSON со ссылками и загружаем картинки
Необходимый сегмент, который по сути и является идентификатором журнала получен. Теперь можно приступать к получению JSON и загрузке картинок. Создадим для начала папку, в которую будем складывать загруженные картинки. Далее создаем список, в который будем записывать папку и название картинки. Это будем нужно нам для дальнейшего использования. И делаем запрос на получение JSON со вставкой идентификатора журнала. Затем пробегаемся по разделу со ссылками. Для этого используем цикл for в котором перебираем элементы JSON, при этом узнаем количество этих элементов, которое равно длине раздела pages.
Выводим в терминал принт, чтобы не было скучно и понятно, на каком этапе мы находимся:
print(f"[+] Качаю картинку {num + 1}/{len(resp['document']['pages'])}…")
Загружаем картинку по ссылке полученной из JSON, после чего сохраняем картинку в созданную для картинок папку и добавляем ссылку на картинку в список. После того, как все картинки будут загружены, функция возвращает список со ссылками на картинки.
Python:
def get_download_image(segment):
if not os.path.isdir('pages'):
os.mkdir('pages')
image_list = []
resp = requests.get(url=f'https://reader3.isu.pub/borov665/{segment}/reader3_4.json', headers=headers).json()
for num in range(0, len(resp['document']['pages'])):
print(f"[+] Качаю картинку {num + 1}/{len(resp['document']['pages'])}...")
req = requests.get(url=f"https://{resp['document']['pages'][num]['imageUri']}", headers=headers)
with open(f'{os.path.join("pages", f"page_{num + 1}.jpg")}', 'wb') as file:
file.write(req.content)
image_list.append(f'{os.path.join("pages", f"page_{num + 1}.jpg")}')
print('\n[+] Загрузка картинок завершена!')
return image_list
Объединяем картинки в PDF
После того, как картинки загружены, их надо собрать в файл PDF. Для этого будем использовать библиотеку img2pdf, в которую передается для конвертации список картинок. Таким образом, открываем файл на запись в побайтовом режиме. Указываем название файла, которое мы получили ранее со страницы. И в функции записи файла передаем функцию img2pdf с параметром convert, куда передаем полученный нами список картинок.
После конвертации выводим сообщение, что все закончено и пробегаемся в цикле по ссылкам на картинки, в котором их удаляем, чтобы не мусорить на диске. После чего удаляем и саму папку, в которую картинки загружали.
Python:
# создание файла для записи в побайтовом режиме
# конвертация списка картинок и запись в файл
# удаление картинок из папки загрузок, а также удаление самой папки
def merge_journal_page(title, image_list):
with open(f'{title}.pdf', 'wb') as file:
file.write(img2pdf.convert(image_list))
print(f'[INFO] PDF файл "{title}" создан!')
for img in image_list:
os.remove(img)
if os.path.isdir(os.path.join('pages', 'img_compress')):
os.removedirs(os.path.join('pages', 'img_compress'))
else:
os.removedirs('pages')
Сжимаем изображения перед конвертацией в PDF
Я немного подумал и решил, что неплохо было бы еще и немного сжать полученные картинки перед тем, как собирать их в PDF, для того, чтобы уменьшить размер получаемого файла. Качество страдает не особо. Можно сказать, что невооруженным глазом долго придется присматриваться к тому, что же изменилось на картинке. Но, справедливости ради надо отметить, что конвертация не всегда одинаково полезна для всех картинок. Бывает, что исходные картинки загружаются уже не особо высокого качества, а их сжатие чуть-чуть его еще ухудшает. Но, читаемо, в принципе. Так что, на вкус и цвет все фломастеры разные. Поэтому, я добавил еще и выбор для пользователя, в котором он будет решать, конвертировать ли ему картинки или нет.
Для конвертации картинок будем использовать библиотеку Pillow из которой загрузим модуль Image. Создаем в функции список со ссылками на конвертированные файлы, чтобы их впоследствии собрать в PDF, после создаем папку для сжатых картинок. Перебираем список со ссылками и каждую картинку передаем для открытия в Image. После чего, стразу же сохраняем с опцией optimize=True и качеством 40 от исходной картинки — quality=40. Затем добавляем ссылку на картинку в словарь. После того, как конвертация завершиться, удаляем исходные картинки и возвращаем словарь, в котором сохраняли ссылки на конвертированные картинки.
Python:
def compress_img(image_list):
compress_img_list = []
if not os.path.isdir(os.path.join('page', 'img_compress')):
os.mkdir(os.path.join('pages', 'img_compress'))
for num, img in enumerate(image_list):
Image.open(img).save(os.path.join('pages', 'img_compress', f'page_{num+1}_com.jpg'), optimize=True, quality=40)
compress_img_list.append(os.path.join('pages', 'img_compress', f'page_{num+1}_com.jpg'))
for img in image_list:
os.remove(img)
return compress_img_list
Вот в принципе и все. Теперь, с помощью данного скрипта можно скачать любой журнал с сайта zhurnala.ru. Копируем ссылку на страницу с журналом, передаем ее в скрипт и получаем на выходе собранные в файла PDF картинки страниц журнала.
Ну, а вот пример собранных журналов, один, тот, что больше размером, без сжатия изображений, а второй со сжатием.
Как видите, разница в размере присутствует. А теперь полный код скрипта:
Python:
"""Скрипт для загрузки журналов с сайта https://zhurnala.ru/.
Для загрузки копируем ссылку на журнал, вставляем в скрипт и на выходе
получаем файл PDF с нужным журналом.
Нужно понимать, что в данном случае получается PDF без текстового слоя,
так что копировать из него текст не получиться, если только распознать.
Но для чтения данный PDF вполне пригоден, так как скачиваются картинки
в высоком качестве.
Скрипт создан в качестве практики, в процессе обучения, для получения
опыта в парсинге данных сайтов."""
import os.path
import time
import requests
from bs4 import BeautifulSoup
from PIL import Image
import img2pdf
# заголовки для запросов
headers = {
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.174 '
'YaBrowser/22.1.3.942 Yowser/2.5 Safari/537.36',
'accept': '*/*'
}
# получение сегмента ссылки для последующей загрузки JSON
# и также получение названия журнала
def get_linksegment_on_json(url):
req = requests.get(url=url, headers=headers)
soup = BeautifulSoup(req.text, 'lxml')
return soup.find('div', class_='entry-content').find_all('p')[1].\
find('iframe', class_='lazy lazy-hidden')['data-src'].split("=")[-1], soup.find('h1', class_='entry-title').text
# создание папки для загрузки изображений
# загрузка JSON и получение из него в цикле ссылок на загрузку изображений
# загрузка изображений и сохранение в ранее созданную папку
# возвращение списка загруженных картинок
def get_download_image(segment):
if not os.path.isdir('pages'):
os.mkdir('pages')
image_list = []
resp = requests.get(url=f'https://reader3.isu.pub/borov665/{segment}/reader3_4.json', headers=headers).json()
for num in range(0, len(resp['document']['pages'])):
print(f"[+] Качаю картинку {num + 1}/{len(resp['document']['pages'])}...")
req = requests.get(url=f"https://{resp['document']['pages'][num]['imageUri']}", headers=headers)
with open(f'{os.path.join("pages", f"page_{num + 1}.jpg")}', 'wb') as file:
file.write(req.content)
image_list.append(f'{os.path.join("pages", f"page_{num + 1}.jpg")}')
print('\n[+] Загрузка картинок завершена!')
return image_list
# создание папки для сохранения сжатых картинок
# перебор в цикле картинок и сжатие с сохранением в созданной папке
# добавление путей к картинкам в список
# удаление загруженных фото из папки с возвращением списка сжатых картинок
def compress_img(image_list):
compress_img_list = []
if not os.path.isdir(os.path.join('page', 'img_compress')):
os.mkdir(os.path.join('pages', 'img_compress'))
for num, img in enumerate(image_list):
Image.open(img).save(os.path.join('pages', 'img_compress', f'page_{num+1}_com.jpg'), optimize=True, quality=40)
compress_img_list.append(os.path.join('pages', 'img_compress', f'page_{num+1}_com.jpg'))
for img in image_list:
os.remove(img)
return compress_img_list
# создание файла для записи в побайтовом режиме
# конвертация списка картинок и запись в файл
# удаление картинок из папки загрузок, а также удаление самой папки
def merge_journal_page(title, image_list):
with open(f'{title}.pdf', 'wb') as file:
file.write(img2pdf.convert(image_list))
print(f'[INFO] PDF файл "{title}" создан!')
for img in image_list:
os.remove(img)
if os.path.isdir(os.path.join('pages', 'img_compress')):
os.removedirs(os.path.join('pages', 'img_compress'))
else:
os.removedirs('pages')
# вызов функции для получения сегмента, а также получение пользовательского ввода ссылки на страницу с журналом
# получение списка изображений и вызов функции для загрузки картинок
# запрос пользователя сжимать или не сжимать картинки
# в зависимости от выбора сжатие картинок и объединение в PDF
# или просто объединение загруженных картинок в PDF
def main():
start = time.monotonic()
segment = get_linksegment_on_json(input('[+] Введите ссылку на страницу с журналом: '))
image_list = get_download_image(segment[0])
if input('\n[+] Сжать загруженные картинки y/n?').lower() == 'y':
compress_img_list = compress_img(image_list)
merge_journal_page(segment[1], compress_img_list)
else:
merge_journal_page(segment[1], image_list)
print(f'\nВремя загрузки картинок и создания журнала с учетом пользовательского выбора: '
f'{round(time.monotonic() - start)} секунд')
if __name__ == "__main__":
main()
Спасибо за внимание. Надеюсь, что данная статья кому-то будет полезной
Последнее редактирование: