Далее будет разбор кода проекта. Для парсинга используется библиотека 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))