Как правильно парсить страницу с помощью BeautifulSoup?

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

import asyncio

import aiohttp
from bs4 import BeautifulSoup as BS

BASE_URL = "https://nubip.edu.ua/"


async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get(BASE_URL) as response:
            r = await aiohttp.StreamReader.read(response.content)
            soup = BS(r,"html.parser")

            items = soup.find_all("div",{"class": "block"})

            for item in items:
                time  = soup.find("p", {"class": "datetime"})
                span  = soup.find("span", {"class": "b-selection-em"})
                print(time)
                print(span)
if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Вывод

<p class="datetime"><span>5 лютого 2023 року</span></p>
<span class="b-selection-em">НУБіП піднявся в міжнародному рейтинзі WEBOMETRICS і посів четверте місце в Україні!</span>
<p class="datetime"><span>5 лютого 2023 року</span></p>
<span class="b-selection-em">НУБіП піднявся в міжнародному рейтинзі WEBOMETRICS і посів четверте місце в Україні!</span>
<p class="datetime"><span>5 лютого 2023 року</span></p>
<span class="b-selection-em">НУБіП піднявся в міжнародному рейтинзі WEBOMETRICS і посів четверте місце в Україні!</span>


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

Автор решения: 9evoldpy

Есть ещё один вариант парсинга, но он сложнее по структуре и иногда может не видеть данные из html-данных сайта. Вот пример парсинга евро:

from bs4 import BeautifulSoup
import requests
eur = 'https://quote.rbc.ru/ticker/59090'
response = requests.get(eur)
bs = BeautifulSoup(response.text, "lxml")
euro = bs.find('span', class_='chart__info__sum')
print(euro.text) # .text выполняет функцию выделения только текста

Результат: введите сюда описание изображения

→ Ссылка
Автор решения: Сергей Ш

Например так:

items = soup.find(id="news-block").find_all(attrs={"class": "item"})

for item in items:
    time, *span = item.stripped_strings
    link = item.a['href']
    print(time)
    print(' '.join(span))
    print(link)
→ Ссылка
Автор решения: Igor

Проблема в вашем коде связана с тем, что внутри цикла вы ищете элементы не внутри текущего item, а в целом в soup. Поэтому для каждого блока div.block вы каждый раз получаете первый элемент <p class="datetime"> и <span class="b-selection-em">, а не тот, который относится к текущему item.

import asyncio
import aiohttp
from bs4 import BeautifulSoup as BS

BASE_URL = "https://nubip.edu.ua/"

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get(BASE_URL) as response:
            r = await response.text()  # правильный способ получить HTML
            soup = BS(r, "html.parser")

            items = soup.find_all("div", {"class": "block"})

            for item in items:
                # ищем внутри конкретного блока
                time_tag = item.find("p", {"class": "datetime"})
                span_tag = item.find("span", {"class": "b-selection-em"})
                
                if time_tag and span_tag:
                    time_text = time_tag.get_text(strip=True)
                    span_text = span_tag.get_text(strip=True)
                    print(time_text, "-", span_text)

if __name__ == '__main__':
    asyncio.run(main())
→ Ссылка