@Sun6321111

Почему парсер не собирает данные и записывает пустые значения?

Мне для анализа данных нужно очень много колонок. Я использую библиотеку cianparser. Задача заключается в том чтобы спарсить объявления по продаже. Но, по какой-то причине, именно то, что мне нужно (находится в списке processed_data) абсолютно не хочет парсится. Пробовала вывести ключи: смотрю, а их там тупо нет, хотя with_extra_data = True, по идее, должно собирать то что после if with_extra_data:, но нет.

ключи:

{'author': 'Абсолют Недвижимость', 'author_type': 'developer', 'url': 'https://www.cian.ru/sale/flat/307207783/', 'location': 'Москва', 'deal_type': 'sale', 'accommodation_type': 'flat', 'floor': 14, 'floors_count': 19, 'rooms_count': 1, 'total_meters': 37.6, 'price_per_month': -1, 'commissions': 0, 'price': 9743081, 'district': '', 'street': '', 'house_number': '', 'underground': '', 'residential_complex': 'Город-парк Первый Московский'}

сам код (это еще не все, там ниже будет):

import csv
import cianparser
import os
import time
import random
import requests
import logging

# Настройка логирования
logging.basicConfig(filename='parsing_errors.log', level=logging.ERROR,
                    format='%(asctime)s:%(levelname)s:%(message)s')

def save_data_to_csv(data, file_name, mode='a'):
    if not data:
        return

    fieldnames = data[0].keys()
    file_exists = os.path.isfile(file_name)

    with open(file_name, mode, newline='', encoding='utf-8') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        if not file_exists or mode == 'w':
            writer.writeheader()
        writer.writerows(data)

def get_random_proxy():
    proxy_list = [
        '117.250.3.58:8080',
        '115.96.208.124:8080',
        '152.67.0.109:80',
        '45.87.68.2:15321',
        '68.178.170.59:80',
        '20.235.104.105:3729',
        '195.201.34.206:80'
    ]
    proxy = random.choice(proxy_list)
    return {"http": f"http://{proxy}", "https": f"http://{proxy}"}

csv_name = 'moscow_4.csv'
def collect_real_estate_data(locations, deal_type="sale", rooms=(1), start_page=1, end_page=2,
                             file_name=csv_name, with_extra_data=True):
    all_data = []

    user_agents = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36",
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/85.0",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15",
    ]

    for location in locations:
        parser = cianparser.CianParser(location=location)
        print(f"Сбор данных для {location}...")

        for page in range(start_page, end_page + 1):
            user_agent = random.choice(user_agents)
            proxy = get_random_proxy()

            session = requests.Session()
            session.proxies.update(proxy)
            session.headers.update({"User-Agent": user_agent, "Referer": "https://cian.ru"})

            retries = 3  # Количество попыток
            for attempt in range(retries):
                try:
                    data = parser.get_flats(deal_type=deal_type, rooms=rooms,
                                            additional_settings={"start_page": page, "end_page": page})
                    break  # Если данные успешно получены, выход из цикла
                except requests.exceptions.RequestException as e:
                    print(f"Ошибка запроса страницы {page} для {location}: {e}. Попытка {attempt + 1}/{retries}")
                    time.sleep(5 + random.uniform(0, 5))  # Пауза перед повтором
                    if attempt == retries - 1:
                        logging.error(f"Не удалось получить данные с {page}-й страницы для {location}: {e}")
                        continue

            processed_data = []
            for flat in data:
                processed_flat = {
                    "author": flat.get("author", "Не указано"),  # Автор
                    "author_type": flat.get("author_type", "Не указано"),  # Тип автора
                    "url": flat.get("url", "Не указано"),
                    "price": flat.get("price", "Не указано"),  # Цена (для продажи)
                    "location": flat.get("location", "Не указано"),  # Город
                    "district": flat.get("district", "Не указано"),  # Район
                    "street": flat.get("street", "Не указано"),  # Улица
                    "underground": flat.get("underground", "Не указано"),  # Метро
                    "total_meters": flat.get("total_meters", "Не указано"),  # Общая площадь
                    "rooms_count": flat.get("rooms_count", "Не указано"),  # Количество комнат
                    "floor": flat.get("floor", "Не указано"),  # Этаж
                    "floors_count": flat.get("floors_count", "Не указано"),  # Общее количество этажей
                    "residential_complex": flat.get("residential_complex", "Не указано"),  # Название жилого комплекса

                }

                if with_extra_data:
        # все то что так упорно не парсится
                    processed_flat["object_type"] = flat.get("object_type", "Не указано")  # Тип жилья (вторичка/новостройка)
                    processed_flat["class"] = flat.get("class", "Не указано")
                    processed_flat["finish_type"] = flat.get("finish_type", "Не указано")  # Отделка
                    processed_flat["heating_type"] = flat.get("heating_type", "Не указано")
                    processed_flat["house_material_type"] = flat.get("house_material_type", "Не указано")  # Тип дома
                    processed_flat["metro_foot_minute"] = flat.get("metro_foot_minute", "Не указано")  # Сколько минут до метро пешком
                    processed_flat["living_meters"] = flat.get("living_meters", "Не указано")  # Жилая площадь
                    processed_flat["kitchen_meters"] = flat.get("kitchen_meters", "Не указано")  # Площадь кухни
                    processed_flat["ceiling_height"] = flat.get("ceiling_height", "Не указано")  # Высота потолка
                    processed_flat["year_construction"] = flat.get("year_construction", "Не указано")  # Год постройки здания
                    processed_flat["min_house_year"] = flat.get("min_house_year", "Не указано")  # Год постройки дома от
                    processed_flat["parking_type"] = flat.get("parking_type", "Не указано")  # Тип парковки
                    processed_flat["builder"] = flat.get("builder", "Не указано")  # Застройщик
                    processed_flat["have_loggia"] = flat.get("have_loggia", "Не указано")  # Наличие лоджии
                    processed_flat["min_balconies"] = flat.get("min_balconies", "Не указано")  # Минимальное количество балконов
                    processed_flat["max_house_year"] = flat.get("max_house_year", "Не указано")  # Год постройки дома до
                print(flat)
                processed_data.append(processed_flat)

            all_data.extend(processed_data)

            if len(all_data) >= 100:
                save_data_to_csv(all_data[:100], file_name)
                all_data = all_data[100:]

            time.sleep(random.uniform(2, 4))  # Увеличиваем паузу

        if all_data:
            save_data_to_csv(all_data, file_name)
            all_data = []

        print(f"Данные для {location} успешно собраны и сохранены в файл {file_name}")

locations = ["Москва"]

collect_real_estate_data(locations=locations, deal_type="sale", rooms=(1), start_page=1, end_page=50,
                                 file_name=csv_name, with_extra_data=True)

Это был первый вариант кода, а вот второй, в котором если просто поставить with_extra_data=True то он прекрасно парсит часть данных(но не все) которых не хватает. Но object_type при этом все равно, сколько бы он не проходился по объявлениям записывает просто -1, то есть значения нет.

ключи:

{'author': 'VESPER', 'author_type': 'developer', 'url': 'https://www.cian.ru/sale/flat/307211222/', 'location': 'Москва', 'deal_type': 'sale', 'accommodation_type': 'flat', 'floor': 8, 'floors_count': 9, 'rooms_count': 1, 'total_meters': 57.6, 'price': 117936000, 'year_of_construction': '2023', <b>'object_type': -1</b>, 'house_material_type': 'Монолитный', 'heating_type': -1, 'finish_type': 'Без отделки, чистовая с мебелью', 'living_meters': -1, 'kitchen_meters': -1, 'phone': '+74951387184', 'district': 'Тверской', 'street': '1-я Тверская-Ямская ', 'house_number': '2', 'underground': 'Маяковская', 'residential_complex': 'Vesper Tverskaya'}

ну и сам второй вариант кода:

import cianparser

def print_all_keys(data):
    for flat in data:
        print(flat)

# Создаем экземпляр парсера
parser = cianparser.CianParser(location="Москва")

# Получаем данные
data = parser.get_flats(deal_type="sale", rooms=(1), additional_settings={"start_page": 1, "end_page": 1}, with_extra_data=True, with_saving_csv=True)

# Выводим все ключи
print_all_keys(data)

P.S. Может быть проблемы с соединением? Или не заходит глубже по объявлениям или я как-то неправильно использую сами признаки и методы.
  • Вопрос задан
  • 215 просмотров
Решения вопроса 1
@dim5x
ЗИ, ИБ. Помогли? Поблагодарите. Отметьте ответом.
Есть несколько моментов:
1. Про длинный кусок кода: он и не будет работать так, как вы хотите, потому что вы передаёте аргумент with_extra_data=True в свою самописную функцию, а в parser.get_flats() - нет. Соответственно он и не парсит дополнительные поля, отсюда и: "пробовала вывести ключи: смотрю, а их там тупо нет".
Должно быть:
data = parser.get_flats(deal_type=deal_type, rooms=rooms, with_extra_data=True,  additional_settings={"start_page": page, "end_page": page})


2. Если вы действительно использовали эти прокси (а не просто привели их для примера), то это бессмысленно - они нерабочие. Прокси из свободного доступа редко долго живут, а тут они вообще из примера на pypi.org / github.

3. Вёрстка сайтов постоянно изменяется, в том числе и для противодействия парсингу. Текущая версия циана не имеет<span> c текстом "Тип жилья", а именно такой селектор использовался в библиотеке парсера, следовательно он всегда будет отдавать -1 для object_type.

4. Если вы всё-таки хотите парсить сайт, используя cianparser, то вам нужно внести несколько изменений в файл библиотеки: "Путь до вашего проекта\venv\Lib\site-packages\cianparser\flat\page.py".
В функцию__parse_flat_offer_page_json__(self)после инициализации словаря page_data, добавить:
ot = self.offer_page_soup.select_one('[data-name="OfferSummaryInfoItem"] p:nth-of-type(2)').get_text()
page_data["object_type"] = ot

и закомментировать строчки:
# if "Тип жилья" == span.text:
#     page_data["object_type"] = spans[index + 1].text


Результат:
671dbfa43dc32277607933.png
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы