Как правильно написать парсер на 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 шт):
Для того чтобы искать несколько тегов в одном блоке нужно сначала определить этот блок, а потом уже в нем циклом искать нужные теги. Сохраняем картинки при помощи библиотеки 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
Если я правильно понял задачу, то правильный парсер, с моей точки зрения, мог бы выглядеть так:
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. Если учесть, что последняя страница, на момент загрузки данных, не полная, то парсер ничего не пропустил, а сайт дает неверные данные
