Перенос данных парсинга в Excel выполняется частично

Спарсил все страницы сайта, но почему-то экспортируется в Excel только первая. Как исправить код, чтобы сохранялись все найденные объявления?

import requests
import pandas as pd
from bs4 import BeautifulSoup
from time import sleep


headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"}

for i in range(0, 28):
    sleep(3)
    url = f"https://habarovsk.cian.ru/cat.php?deal_type=sale&engine_version=2&from_developer=1&object_type%5B0%5D=2&offer_type=flat&only_flat=1&p={i}&region=5039&year%5B0%5D=2025&year%5B1%5D=2026&year%5B2%5D=2027&year%5B3%5D=2028&yeargte=2029"

    response = requests.get(url, headers=headers)

    soup = BeautifulSoup(response.text, "lxml")  # html.parser

    data = soup.find_all("div", class_="_93444fe79c--container--kZeLu _93444fe79c--link--DqDOy")
    results = []
    for i in data:
        name = i.find("div", class_="_93444fe79c--row--kEHOK").text.replace("\n", "")
        address = i.find("a", class_="_93444fe79c--jk--dIktL").text
        data = i.find("div", class_="_93444fe79c--labels--L8WyJ").text
        cena = i.find("p", class_="_93444fe79c--color_gray60_100--r_axa _93444fe79c--lineHeight_20px--fX7_V _93444fe79c--fontWeight_normal--JEG_c _93444fe79c--fontSize_14px--reQMB _93444fe79c--display_block--KYb25 _93444fe79c--text--e4SBY _93444fe79c--text_letterSpacing__normal--tfToq").text
        results.append([name, address, data, cena])

df = pd.DataFrame(results, columns=['name', 'address', 'data', 'cena'])
df.to_excel('results.xlsx', index=False)

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

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

Полагаю, у вас ошибка в алгоритме. Перепишем код, оставив ключевые моменты:

number_of_pages = ...
for page in range(number_of_pages):
    url = f"...&p={page}&..."
    response = requests.get(url, ...)
    soup = BeautifulSoup(response.text, ...)
    data = soup.find_all(...)

    results = []

    for item in data:
        name = item.find(...).text
        ...
        results.append([name, ...])

df = pd.DataFrame(results, ...)

Обратите внимание, что у вас results обнуляется для каждой страницы, не успев сохраниться. То есть, в таблицу df попадут данные только последней обработанной страницы (не первой). Попробуйте вынести инициализацию results за пределы цикла:

results = []
for page in range(number_of_pages):
    ...
    for item in soup.find_all(...):
        ...
        results.append([...])
df = pd.DataFrame(results, ...)

Пара замечаний. Во-первых, в вашем коде переменные i и data указывают на разные объекты во внутреннем и внешнем циклах. В пределах текущего кода это не критично. Но если вы продолжите работу во внешнем цикле после выхода из внутреннего, возникнет неопределенность, на что именно они указывают. Речь не столько о легкости чтения кода (что тоже важно), сколько о случае, когда цикл пустой и эти переменные после цикла останутся прежними.

Во вторых, итерироваться по фиксированному количеству страниц - не лучшая идея. Лучше исключить необходимость проверять, сколько их там. И уж тем более - исключить необходимость самому конструировать ссылки. В таких случаях есть смысл опираться на функционал, который предоставлен на странице. В вашем случае - это кнопка Дальше внизу страницы, которая, если активна, расположена внутри ссылки на следующую страницу. Попробуем это использовать:

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"}
base_url = 'https://habarovsk.cian.ru'
base_page = r'/cat.php'
base_request = (r'?deal_type=sale&engine_version=2&from_developer=1&'
                r'object_type%5B0%5D=2&offer_type=flat&only_flat=1&region=5039'
                r'&year%5B0%5D=2025&year%5B1%5D=2026&year%5B2%5D=2027&year%5B3%5D=2028&yeargte=2029')
delay = 3
EMPTY = None
results = []

url = base_url + base_page + base_request
while isinstance(url, str) and url.startswith(base_url):
    response = requests.get(url, headers=headers)
    soup = BeautifulSoup(response.text, "lxml")
    ads = soup.find_all("div", class_="_93444fe79c--container--kZeLu _93444fe79c--link--DqDOy")
    for item in ads:
        name = item.find("div", class_="_93444fe79c--row--kEHOK") 
        address = item.find("a", class_="_93444fe79c--jk--dIktL")
        data = item.find("div", class_="_93444fe79c--labels--L8WyJ")
        price = item.find("p", class_="_93444fe79c--color_gray60_100--r_axa _93444fe79c--lineHeight_20px--fX7_V _93444fe79c--fontWeight_normal--JEG_c _93444fe79c--fontSize_14px--reQMB _93444fe79c--display_block--KYb25 _93444fe79c--text--e4SBY _93444fe79c--text_letterSpacing__normal--tfToq")
        results.append([x.text if x else EMPTY for x in [name, address, data, price]])
    next_button = soup.find(name='span', attrs='_93444fe79c--text--V2xLI', string='Дальше')
    url = next_button.parent.get('href') if next_button else None
    if url and url.startswith(base_page):
        url = base_url + url
    sleep(delay)

И ещё одно, я бы не стал как-либо обрабатывать данные на этапе их сбора. Речь о строке name = ... .text.replace("\n", ""). Полагаю, манипуляции с данными лучше выносить в другой блок.

→ Ссылка