Далее будет разбор кода проекта. Для парсинга используется библиотека Beautiful Soup, для публикации на сайт — библиотека python-wordpress-xmlrpc.
Общий принцип работы:
Список категорий на Литрес загружается из заранее подготовленного текстового файла в список для обхода. Только новинки, первая страница в каждой категории с сортировкой по дате. Список можно быстро адаптировать для сбора самых популярных книг или работать только с определенными категориями. Для полного сбора можно добавить еще один внешний цикл для навигации по страницам.
Открывается категория — в новый список собираются все ссылки на книги для нового обхода (вложенный цикл). Со страницы текущей книги собираются все необходимые данные и сразу публикуется новая запись, парсинг в CSV не осуществляется. К публикации добавляется реферальная ссылка на официальный сайт.
import requests from bs4 import BeautifulSoup from time import sleep import os from wordpress_xmlrpc import Client, WordPressPost from wordpress_xmlrpc.compat import xmlrpc_client from wordpress_xmlrpc.methods import media, posts
Для доступа к сайту на WordPress необходимо указать путь к файлу xmlrpc.php, логин и пароль администратора. Часто данный файл заблокирован для доступа, достаточно сделать копию с другим именем и скорректировать ссылку:
client = Client('https://mob25.com/ xmlrpc.php', 'логин', 'пароль')
Переменные. Счетчик опубликованных книг (только для вывода в консоль), параметры для ссылки на категорию — собираем текстовые и аудиокниги на русском языке. Для получения другого набора достаточно зайти в любой раздел на литрес, выставить фильтры и скопировать набор параметров из ссылки. Третий параметр — RefID для формирования партнерской ссылки.

all_count = 0 options = '?art_types=text_book&art_types=audiobook&languages=ru' ref = '?lfrom=477110899'
Далее открываем два файла: new.txt (список категорий) и id.txt (список идентификаторов опубликованных книг). Содержимое заносим в соответствующие списки. Первый используется только для обхода, второй — для проверки на дубликаты (они пропускаются).
with open("new.txt", "r", encoding="utf-8") as file_cl: category_list = file_cl.read().split('\n') with open("id.txt", "r", encoding="utf-8") as file_id: all_id = file_id.read().split('\n') file_id.close()
Запускаем внешний цикл. Пауза, чтобы магазин не начал выкидывать капчу (если заголовки нормально заполнить, с такой паузой не блокирует). Также есть пауза после добавления книги. Далее собираем ссылку, отправляем запрос, загружаем содержимое страницы категории и сохраняем ее в виде файла current_category.html. Сразу считываем обратно в переменную src. Можно напрямую, но так удобнее при тестировании — можно один раз создать файл, закомментировать внешний запрос и запись файл — работать сразу с локальным файлом.
for category in category_list: sleep(20) req = requests.get(f'{category}{options}', headers=headers, cookies = cookies) src = req.text with open(f"temp_html/current_category.html", "w", encoding="utf-8") as file: file.write(src) with open(f"temp_html/current_category.html", encoding="utf-8") as file: src = file.read()
Загружаем содержимое страницы категории в объект парсера. Считываем название категории. Переменная name_category_clear — вариант без слешей только для создания файлов. Если такой папки нет — создаем ее (для сохранения кода страниц с описанием книг). Название совпадает с названием категории.
В переменную list_href парсим блоки с ссылками на книги.
soup = BeautifulSoup(src, "lxml") name_category = soup.find("div", class_="Genre-module__title__name_JuQ14").text name_category_clear = name_category.replace('/', '') if not os.path.exists(f'temp_html/{name_category_clear}/'): os.makedirs(f'temp_html/{name_category_clear}/') list_href = soup.find_all('a', {'data-test-id': ['art__title--desktop']})
Запускаем вложенный цикл по списку книг. Очищаем все переменные. Получаем чистую ссылку на книгу (переменная link). Из ссылки получаем id книги. Проверяем наличие id в списке опубликованных, если есть — сразу переход к следующей итерации цикла, книга не парсится и не публикуется. Если нет — добавляем id в список all_id и дописываем в общий файл id.txt.
for href in list_href: title, author, isbn, description, id, count_author = '', '', '', '', '', '' link = (f"{href.get('href')}") id = ((link.split('-'))[-1])[:-1] if id in all_id: print(f'дубль - {id}') continue sleep(20) all_id.append(id) with open("id.txt", "a", encoding="utf-8") as file_id: file_id.write(f'{id}\n')
Аналогично — собираем ссылку на страницу книги, отправляем запрос, загружаем содержимое в файл и переменную, создаем объект парсера для продолжения.
req = requests.get(f"https://www.litres.ru{link}", headers=headers, cookies = cookies) src = req.text with open(f"temp_html/{name_category_clear}/{id}.html", "w", encoding="utf-8") as file_book: file_book.write(src) with open(f"temp_html/{name_category_clear}/{id}.html", "r", encoding="utf-8") as file_book: src = file_book.read() soup = BeautifulSoup(src, "lxml")
Сбор данных в переменные. Некоторые элементы могут отсутствовать на странице, для исключения ошибки проверяется наличие через if.
count_author — надпись «Автор» или «Авторы» для корректного вывода. Авторов может быть несколько, собираются в список, который сразу преобразуется в строку с добавлением запятых. description — описание, список с параграфами для нормального разделения при выводе.
title = soup.find("h1").text if soup.find(class_="Authors-module__authors_2_blq"): count_author = soup.find(class_="Authors-module__authors_2_blq").find("h4").text isbn = soup.find("span", itemprop="isbn") if isbn: isbn = isbn.text if soup.find(class_="Authors-module__authors_2_blq"): author = soup.find("div", class_="Authors-module__authors__wrapper_1rZey").find_all("span", itemprop="name") for i in range(len(author)): print(author[i]) if author[i]: author[i] = author[i].text author = ', '.join(map(str, author)) description = soup.find(class_="BookCard-module__book__annotation_2ZIhf").find_all('p')
Скачивание картинки в папку image. Шаблон позволяет сразу получить прямую ссылку на уменьшенную копию картинки на основе идентификатора. Если убрать «_250», загрузится полная версия, но она имеет большой размер. В качестве имени также используется id.
p = requests.get(f'https://www.litres.ru/pub/c/cover_250/{id}.jpg') out = open(f'image/{id}.jpg', "wb") out.write(p.content) out.close()
Загружаем картинку на сайт и сразу получаем на нее прямую ссылку, для вставки в описание. Дополнительно проверяем ее наличие, чтобы не было ошибки (она просто не вставится).
Пример с комментариями из официальной документации: https://python-wordpress-xmlrpc.readthedocs.io/en/latest/examples/media.html#uploading-a-file
if os.path.exists(f'image/{id}.jpg'): image_name = f'{id}.jpg' filename = f'image/{image_name}' data = { 'name': image_name, 'type': 'image/jpeg',} with open(filename, 'rb') as img: data['bits'] = xmlrpc_client.Binary(img.read()) response = client.call(media.UploadFile(data)) attachment_url = response['url']
Собираем описание книги в переменную wpcontent. Также проверяем наличие объектов, чтобы не было пустых мест. Ниже фото с результатом.
wpcontent = '' wpcontent += f'<!-- wp:paragraph -->' wpcontent += ''.join(map(str, description)) wpcontent += f'<!-- /wp:paragraph -->' wpcontent += f'<!-- wp:paragraph -->' if count_author: wpcontent += f'{count_author}: {author}<br>' if isbn: wpcontent += f'ISBN: {isbn}<br>' wpcontent += f'<!-- /wp:paragraph -->' wpcontent += f'<!-- wp:paragraph -->' wpcontent += f'<br><a href="https://www.litres.ru{link}{ref}" target="_blank" rel="noreferrer noopener">Скачать книгу</a><br><br>' wpcontent += f'<!-- /wp:paragraph -->' if os.path.exists (f'image/{id}.jpg'): wpcontent += f'<figure class="wp-block-image size-full">' wpcontent += f'<img src="{attachment_url}">' wpcontent += f'</figure>'

Публикация на сайте. Пример также есть на странице официальной документации. На этапе тестирования можно закомментировать post.post_status. Записи будут добавляться как черновики.
post = WordPressPost() post.terms_names = {'category': [name_category],} post.title = title post.content = (wpcontent) post.post_status = 'publish' post.id = client.call(posts.NewPost(post))