Как правильно написать парсер на python?

Я написал код нахождения заголовков и скачивания изображений. Только по отдельности коды работают, а вместе нет. Мне нужно сделать чтобы название было заголовком и к нему скачивалось его изображение.

Вот сам код:

from bs4 import BeautifulSoup as bs
import requests

page = 1 

while page <=2:
    url = 'https://ananasposter.ru/catalogue' + '?page=' + str(page)
    req = requests.get(url)
    soup = bs(req.text, 'lxml')

    names = soup.find_all('h3', class_='name-product padding-product-meta')

    img = soup.find('img', class_='main_image img-responsive')
    link = img['src']
    print('название изображения:{}'.format(link))
    with open(names.format(link), 'wb') as f:
        f.write(requests.get(link).content)
    
    page += 1

Ответы (2 шт):

Автор решения: Typeoflife

Для того чтобы искать несколько тегов в одном блоке нужно сначала определить этот блок, а потом уже в нем циклом искать нужные теги. Сохраняем картинки при помощи библиотеки urllib.

from bs4 import BeautifulSoup as bs
import requests
import urllib


page = 1

while page <=2:

    url = 'https://ananasposter.ru/catalogue' + '?page=' + str(page)
    req = requests.get(url)
    soup = bs(req.text, 'html.parser')

    items = soup.find_all('div', class_='product-item product-layout product-grid col-lg-3 col-md-3 col-sm-6 col-xs-12')
    for item in items:

        name = item.find('h3', class_='name-product padding-product-meta').get_text().partition('|')[0]
        img = item.find('img', class_='main_image img-responsive')['src']
        print(f'{name}: {img}')

        img = urllib.request.urlopen(img).read()
        out = open(f"{name}.jpg", "wb")
        out.write(img)
        out.close

    page += 1
   
→ Ссылка
Автор решения: Namerek

Если я правильно понял задачу, то правильный парсер, с моей точки зрения, мог бы выглядеть так:

pip install fake-useragent requests tqdm

from tqdm.auto import tqdm
import re

from requests import Session
from requests.adapters import HTTPAdapter
from urllib3.util import Retry

from fake_useragent import UserAgent
from multiprocessing.pool import ThreadPool as Pool
from bs4 import BeautifulSoup as Soup, Tag
from pathlib import Path

# Определим папку, куда мы будем сохранять изображения
downloads = Path('downloads')

# Создадим эту папку если ее не существует
# exists_ok: позволит не поднимать исключение если
# папка уже существует
# parents: позволит создать весь путь до конечной
# папки в случае его отсутствия
downloads.mkdir(exist_ok=True, parents=True)

ua = UserAgent()

s = Session()

# Для некоторых особо капризных серверов желательно добавить в сессию адаптер,
# для корректной обработки запросов. В данном случае в этом нет необходимости.
# Сайт не капризничает и отдает все нормально
s.mount(
    'https://ananasposter.ru',
    HTTPAdapter(
        max_retries=Retry(
            connect=5,
            read=10,
            total=25,
            backoff_factor=.005,
            allowed_methods=['GET'],
        )
    )
)

# Добавим в заголовок сессии User-Agent, чтобы сайт думал что мы FireFox
s.headers.update(
    {'User-Agent': ua.firefox}
)
url = 'https://ananasposter.ru/catalogue'

# Не все символы разрешено использовать для названия файлов,
# поэтому создадим словарь для подмены запрещенных символов
subs = {
    '\\': '-',
    '/': '-',
    ':': '~',
    '*': '#',
    '?': '',
    '<': '[',
    '>': ']',
    '"': "'",
    '|': '~',
    '\n': ' '
}


def get_image(img: Tag):
    # Получим ссылку на изображение из тега img
    link = img.get('src')

    # Используя регулярное выражение определим системное
    # имя и расширение файла изображения,
    # опустив его размер
    idx, extension = m.groups() if (
        m := re.search(r'(?<=/)(\w+)-\d+x\d+\.(\w+)$', link)
    ) else ('unknown', 'jpg')

    # Получим имя файла из параметра `title` попутно заменив
    # запрещенные символы
    # на соответствующие им разрешенные используя словарь subs
    # В случае если title будет иметь тип отличный от str то
    # будем использовать системное имя файла
    title = ''.join(
        [subs.get(i, i) for i in tt]
    ).strip().rstrip('.') + f'.{extension}' if isinstance(
        tt := img.get('title'), str
    ) else f'{idx}.{extension}'

    downloads.joinpath(title).write_bytes(
        s.get(link).content
    )


def process_page(page_num: int):
    # Получим response от страницы с указанным номером
    # Все, что идет после знака вопроса в url лучше указывать в качестве параметров,
    # а не придумывать способы их конкатенации
    resp = s.get(
        url,
        params={
            'page': page_num
        }
    )
    soup = Soup(resp.content, 'html.parser')

    # Если номер обрабатываемой страницы равен 1, то вычисляем номер последней страницы
    # (в противном случае в этом нет необходимости и данную операцию можно пропустить)
    # для этого находим все теги a, параметр href которых
    # соответствует регулярному выражению
    # получаем из них параметр href, удаляем из полученного url
    # все кроме номера страницы
    # и находим максимальный по ключу int
    # В случае если список необходимых url окажется пустым,
    # вернем единицу как максимальное значение из списка [1]

    pages = None if page_num - 1 else int(max(
        [
            a.get('href').removeprefix('https://ananasposter.ru/catalogue?page=')
            for a in soup.find_all('a', href=re.compile(r'(?<=page=)\d+$'))
        ] or [1],
        key=int
    ))

    # Каждый найденный тег img класса main_image
    for image in soup.find_all('img', class_='main_image'):
        # Отдадим на обработку в функцию get_image
        get_image(image)

    # Функция вернет вычисленный выше номер последней страницы
    return pages


# Запустим обработку первой страницы и получим номер последней для
# использования его в создании цикла
pages_total = process_page(1)

# Ввиду немалого количества страниц предлагаю использовать пул из 20-ти потоков
with Pool(20) as pool:
    # Использование бара tqdm дает возможно отслеживать процесс обработки
    for _ in tqdm(
            # imap_unordered даст возможность задействовать освободившиеся
            # потоки вне очереди
            pool.imap_unordered(
                process_page,
                # Начинаем обработку со второй страницы, так-как
                # первую мы уже обработали
                range(2, pages_total)
            ),
            total=pages_total,
            initial=2
    ):
        # Поскольку функция process_page уже не возвращает
        # ничего нужного, здесь мы ничего не делаем
        pass

# 100%|██████████| 1303/1303 [22:40<00:00,  1.05s/it]

UPD:

В общем я оказался прав. Дело было в одинаковых названиях:

Добавьте импорт:

from uuid import uuid4

И замените

    downloads.joinpath(title).write_bytes(
        s.get(link).content
    )

на

    downloads.joinpath(f'{uuid4()}_{title}').write_bytes(
        s.get(link).content
    )

введите сюда описание изображения

Куда у них девается еще 32 изображения? Возможно неверно считает сайт, поскольку 1303 x 32 = 41696. Если учесть, что последняя страница, на момент загрузки данных, не полная, то парсер ничего не пропустил, а сайт дает неверные данные

→ Ссылка