Парсер на Python — сбор книг на Litres с публикацией в WordPress



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


Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: