In [167]:
import requests
import re
from bs4 import BeautifulSoup
import pandas as pd
import time
import numpy as np

In [2]:
def html_stripper(text):
    return re.sub('<[^<]+?>', '', str(text))

In [3]:
page = 1

ЦАО


In [4]:
district = 'http://www.cian.ru/cat.php?deal_type=sale&district%5B0%5D=13&district%5B1%5D=14&district%5B2%5D=15&district%5B3%5D=16&district%5B4%5D=17&district%5B5%5D=18&district%5B6%5D=19&district%5B7%5D=20&district%5B8%5D=21&district%5B9%5D=22&engine_version=2&offer_type=flat&p={}&room1=1&room2=1&room3=1&room4=1&room5=1&room6=1'

Собираем ссылки на все квартиры первых тридцати страниц выдачи


In [6]:
links = []

for page in range(1, 30):
    page_url =  district.format(page)

    search_page = requests.get(page_url)
    search_page = search_page.content
    search_page = BeautifulSoup(search_page, 'lxml')

    flat_urls = search_page.findAll('div', attrs = {'ng-class':"{'serp-item_removed': offer.remove.state, 'serp-item_popup-opened': isPopupOpen}"})
    flat_urls = re.split('http://www.cian.ru/sale/flat/|/" ng-class="', str(flat_urls))

    for link in flat_urls:
        if link.isdigit():
            links.append(link)

Cтандартный блок, в котором мы получаем по ссылке текст страницы в удобном формате


In [9]:
flat_url = 'http://www.cian.ru/sale/flat/' + str(links[0]) + '/'
#flat_url = 'http://www.cian.ru/sale/flat/150531912/' 
flat_page = requests.get(flat_url)
flat_page = flat_page.content
flat_page = BeautifulSoup(flat_page, 'lxml')

Prices

Функция для извлечения цены квартиры из странички, взято из лекции


In [567]:
def getPrice(flat_page):
    price = flat_page.find('div', attrs={'class':'object_descr_price'})
    price = re.split('<div>|руб|\W', str(price))
    price = "".join([i for i in price if i.isdigit()][-4:])
    return int(price)

Функция для сбора всех цен с сайта в выбранном диапазоне ссылок ( l < r )


In [583]:
def getAllPrices(l, r):
    prices = []
    for i in range(l, r):
        flat_url = 'http://www.cian.ru/sale/flat/' + str(links[i]) + '/'
        flat_page = requests.get(flat_url)
        flat_page = flat_page.content
        flat_page = BeautifulSoup(flat_page, 'lxml')
        prices.append(getPrice(flat_page))
    return prices

Теперь используем эти функции и составим колонку для всех цен


In [589]:
prices = getAllPrices(0, len(links))

Dist

Извлечем координаты так же, как это было продемонстировано на лекции


In [591]:
flat_url = 'http://www.cian.ru/sale/flat/' + str(links[0]) + '/'
#flat_url = 'http://www.cian.ru/sale/flat/150531912/' 
flat_page = requests.get(flat_url)
flat_page = flat_page.content
flat_page = BeautifulSoup(flat_page, 'lxml')

coords = flat_page.find('div', attrs={'class':'map_info_button_extend'}).contents[1]
coords = re.split('&amp|center=|%2C', str(coords))

In [592]:
coords


Out[592]:
['<a href="http://map.cian.ru/?deal_type=2',
 ';flats=yes',
 ';',
 '55.762586',
 '37.597809',
 ';room0=1',
 ';room1=1',
 ';room2=1',
 ';room3=1',
 ';room4=1" rel="nofollow" target="_blank"><i></i>Посмотреть предложения рядом</a>']

In [593]:
coords_list = []
for item in coords:
    if item[0].isdigit():
        coords_list.append(item)
lat = float(coords_list[0])
lon = float(coords_list[1])

In [594]:
lat


Out[594]:
55.762586

In [595]:
lon


Out[595]:
37.597809

Определим функцию получения координат из текста страницы


In [596]:
def getCoords_at(flat_page):
    coords = flat_page.find('div', attrs={'class':'map_info_button_extend'}).contents[1]
    coords = re.split('&amp|center=|%2C', str(coords))
    coords_list = []
    for item in coords:
        if item[0].isdigit():
            coords_list.append(item)
    lat = float(coords_list[0])
    lon = float(coords_list[1])
    return lat, lon

И функцию для получения всех координат в выбранном диапазоне ссылок (такая схема из двух функций будет повторяться везде)


In [812]:
def getAllCoordinates(l, r):
    coordinates = []

    for i in range(l, r):
        flat_url = 'http://www.cian.ru/sale/flat/' + str(links[i]) + '/'
        flat_page = requests.get(flat_url)
        flat_page = flat_page.content
        flat_page = BeautifulSoup(flat_page, 'lxml')
        coordinates.append(getCoords(flat_page))
        
    return coordinates

In [599]:
coordinates = getAllCoordinates(0, len(links))

Теперь нам предстоит использовать полученные координаты для вычисления расстояния до центра города. Воспользуемся для этого формулой для вычисления расстояния между двумя точками, расположенными на сфере. Запишем отдельную функцию


In [649]:
from math import radians, cos, sin, asin, sqrt
AVG_EARTH_RADIUS = 6371

def haversine(point1, point2):

    # извлекаем долготу и широту
    lat1, lng1 = point1
    lat2, lng2 = point2

    # переводим все эти значения в радианы
    lat1, lng1, lat2, lng2 = map(radians, (lat1, lng1, lat2, lng2))

    # вычисляем расстояние по формуле
    lat = lat2 - lat1
    lng = lng2 - lng1
    d = sin(lat * 0.5) ** 2 + cos(lat1) * cos(lat2) * sin(lng * 0.5) ** 2
    h = 2 * AVG_EARTH_RADIUS * asin(sqrt(d))

    return h

In [651]:
MSC_POINT_ZERO = (55.755831, 37.617673)
distance = []
for i in range(0, len(coordinates)):
    distance.append(haversine(MSC_POINT_ZERO, coordinates[i]))

Rooms

Перейдем к числу комнат, тоже сделано по аналогии с лекционным ноутбуком


In [612]:
flat_url = 'http://www.cian.ru/sale/flat/' + str(links[2]) + '/'
#flat_url = 'http://www.cian.ru/sale/flat/150844464/' 
flat_page = requests.get(flat_url)
flat_page = flat_page.content
flat_page = BeautifulSoup(flat_page, 'lxml')

In [613]:
rooms_n = flat_page.find('div', attrs={'class':'object_descr_title'})
rooms_n = html_stripper(rooms_n)
rooms_n


Out[613]:
'\n                \n    \n        2-комн. кв.\xa0\n    \n\n                \n                    \n                \n                '

In [614]:
re.split('-|\n', rooms_n)


Out[614]:
['',
 '                ',
 '    ',
 '        2',
 'комн. кв.\xa0',
 '    ',
 '',
 '                ',
 '                    ',
 '                ',
 '                ']

Страницы о многокомнатных квартирах ( > 5 ) не содержат информации о количестве комнат в явном виде, такие квартиры фигурируют как "многокомнатные". Так что соответствующие объекты у меня получат в столбец Rooms значение 'mult'. Это более достоверная информация, чем, например, 6.


In [617]:
def getRoom(flat_page):
    rooms_n = flat_page.find('div', attrs={'class':'object_descr_title'})
    rooms_n = html_stripper(rooms_n)
    room_number = ''
    flag = 0
    for i in re.split('-|\n', rooms_n):
        if 'много' in i:
            flag = 1
            break
        elif 'комн' in i:
            break
        else:
            room_number += i
    
    if (flag):
        room_number = 'mult'
    room_number = "".join(room_number.split())
    return room_number

In [623]:
def getAllRooms(l, r):
    rooms = []

    for i in range(l, r):
        flat_url = 'http://www.cian.ru/sale/flat/' + str(links[i]) + '/'
        flat_page = requests.get(flat_url)
        flat_page = flat_page.content
        flat_page = BeautifulSoup(flat_page, 'lxml')
        rooms.append(getRoom(flat_page))
        
    return rooms

In [625]:
rooms = getAllRooms(0, len(links))

Metrdist и Walking

Теперь начнем разбираться с расстоянием до метро, заодно и со способом до него добраться (пешком или на машине)


In [626]:
#flat_url = 'http://www.cian.ru/sale/flat/' + str(links[2]) + '/'
flat_url = 'http://www.cian.ru/sale/flat/150387502/' 
flat_page = requests.get(flat_url)
flat_page = flat_page.content
flat_page = BeautifulSoup(flat_page, 'lxml')

In [629]:
metro = flat_page.find('div', attrs={'class':'object_descr_metro'})
metro = re.split('metro_name|мин', str(metro))
metro


Out[629]:
['<div class="object_descr_metro">\n<p class="objects_item_metro_prg">\n<span class="metro_icon metro_icon_region_1" style="background-color: #F58232"></span>\n<a class="object_item_',
 '" href="/cat.php?engine_version=2&amp;metro%5B0%5D=47" rel="nofollow" target="_blank">Китай-город,</a>\n<span class="object_item_metro_comment">\n                10\n                ',
 '.\n                \n                    пешком\n                \n            </span>\n</p>\n</div>']

In [630]:
re.split('metro_name|мин', str(metro))


Out[630]:
['[\'<div class="object_descr_metro">\\n<p class="objects_item_metro_prg">\\n<span class="metro_icon metro_icon_region_1" style="background-color: #F58232"></span>\\n<a class="object_item_\', \'" href="/cat.php?engine_version=2&amp;metro%5B0%5D=47" rel="nofollow" target="_blank">Китай-город,</a>\\n<span class="object_item_metro_comment">\\n                10\\n                \', \'.\\n                \\n                    пешком\\n                \\n            </span>\\n</p>\\n</div>\']']

Тут у нас появляется отличие от того, что было раньше: некоторые поля на Циане являются обязательными для заполнения, а некоторые нет, и вот эти поля обязательными не являются. Класть в данные по квартире, по которой нет этой информации, дефолтный 0 не будет правильным, потому что люди могли не указать как 3 минуты пешком (ну мало ли, лень было вписывать что-то в ячейки, квартира и так продастся), а могли не указать 30 минут на машине. Если уравнивать такие вещи (и тем более все их забивать нулями), то позже это плохо скажется на модели. Так что будем класть в такие ячейки nan'ы.

Metrdist

Функция для извлечения расстояния до метро в минутах


In [640]:
def getMetroDistance(flat_page):
    metro = flat_page.find('div', attrs={'class':'object_descr_metro'})
    metro = re.split('metro_name|мин', str(metro))
    if (len(metro) > 2): # если оба поля не были заполнены, то предыдущий сплит даст размерность 2

        metro_dist = 0
        power = 0
        
        # кусок metro[1] после сплита будет содержать в конце кучу хлама, потом количество минут (если есть)
        flag = 0
        for i in range(0, len(metro[1])):
            if metro[1][-i-1].isdigit():
                flag = 1
                metro_dist += int(metro[1][-i-1]) * 10 ** power
                power += 1
            elif (flag == 1):
                break
    else:
        metro_dist = np.nan

    return metro_dist

In [641]:
def getAllMetroDistances(l, r):
    metro_distance = []

    for i in range(l, r):
        flat_url = 'http://www.cian.ru/sale/flat/' + str(links[i]) + '/'
        flat_page = requests.get(flat_url)
        flat_page = flat_page.content
        flat_page = BeautifulSoup(flat_page, 'lxml')
        metro_distance.append(getMetroDistance(flat_page))
        
    return metro_distance

In [643]:
metro_distances = getAllMetroDistances(0, len(links))

Walking

Аналогично поступим с колонкой walking


In [644]:
def getMetroWalking(flat_page):
    metro = flat_page.find('div', attrs={'class':'object_descr_metro'})
    metro = re.split('metro_name|мин', str(metro))
    if (len(metro) > 2): # если оба поля не были заполнены, то предыдущий сплит даст размерность 2

        if 'пешк' in metro[2]:
            walking = 1
        elif 'машин' in metro[2]:
            walking = 0
        else:
            walking = np.nan # да, проверка на то, отсутствовали ли оба поля была. мне лично не попадались ситуации, где бы не 
                             # было заполнено только значение поля "пешком/на машине", но вдруг они есть? на такой случай проверка
    else:
        walking = np.nan

    return walking

In [645]:
def getAllMetroWalking(l, r):
    metro_walking = []

    for i in range(l, r):
        flat_url = 'http://www.cian.ru/sale/flat/' + str(links[i]) + '/'
        flat_page = requests.get(flat_url)
        flat_page = flat_page.content
        flat_page = BeautifulSoup(flat_page, 'lxml')
        metro_walking.append(getMetroWalking(flat_page))
        
    return metro_walking

In [647]:
walking = getAllMetroWalking(0, len(links))

Теперь перейдем к работе с данными, которые мы будем извлекать из таблицы. Это будущие колонки:

Bricks - кирпичный ли дом, монолитный (варианта жб на Циане нет)/иное

New - новостройка/вторичка

Totsp, Livsp, Kitsp - общая, жилая и площадь кухни соответственно

Tel - наличие телефона

Bal - наличие балкона

Floor - этаж

Nfloors - всего этажей в доме.

Среди вышеперечисленного обязательны к заполнению только поля Totsp, Livsp и Floor. В остальных местах могут быть nan'ы.

Bricks


In [656]:
#flat_url = 'http://www.cian.ru/sale/flat/' + str(links[2]) + '/'
flat_url = 'http://www.cian.ru/sale/flat/150387502/' 
flat_page = requests.get(flat_url)
flat_page = flat_page.content
flat_page = BeautifulSoup(flat_page, 'lxml')
table = flat_page.find('table', attrs = {'class':'object_descr_props'})
table = html_stripper(table)
table


Out[656]:
'\n\nОбщая информация:\n\n\nЭтаж:\n\n                3\xa0/\xa010\n                \n            \n\n\nТип дома:\n\n            вторичка, \n            кирпичный\n        \n\n\nТип продажи:\nсвободная\n\n\nОбщая площадь:\n\n34\xa0м2\n\n\n\nПлощадь комнат:\n\n19\xa0м2\n\n\n\nЖилая площадь:\n\n19\xa0м2\n\n\n\nПлощадь кухни:\n\n6\xa0м2\n\n\n\nСовмещенных санузлов:\n1\n            \n\n\nБалкон:\n1 балк.\n        \n\n\nЛифт:\n1 пасс.\n\n\nПарковка:\nназемная\n\n\nТелефон:\nда\n\n\nВид из окна:\nдвор\n\n\nРемонт:\nкосметический\n\n'

Информация по Brick и New содержится между следующими двумя обязательными полями: "Этаж" и "Тип продажи" (то есть на наличие этих слов в этом месте можно положиться)


In [658]:
building_block = re.split('Этаж|Тип продажи', table)[1]
building_block


Out[658]:
':\n\n                3\xa0/\xa010\n                \n            \n\n\nТип дома:\n\n            вторичка, \n            кирпичный\n        \n\n\n'

Ситуация следующая: поле "Тип дома" может быть, его может не быть (тогда brick = nan). Если оно есть, то оно может содержать информацию по полю Brick, а может и не содержать. Поле Brick может задавать два значения: 1- если это кирпичный или монолитный (или кирпично-монолитный), и 0 - если это что-то другое (и мы заранее можем посмотреть на сайте на то, какие еще бывают варианты: панельный, блочный, деревянный и сталинский). Запишем эти размышления в функцию


In [694]:
def getBrick(flat_page):
    table = flat_page.find('table', attrs = {'class':'object_descr_props'})
    table = html_stripper(table)
    
    brick = np.nan
    
    building_block = re.split('Этаж|Тип продажи', table)[1]
    if 'Тип дом' in building_block:
        if (('кирпич' in building_block) | ('монолит' in building_block)):
            brick = 1
        elif (('панельн' in building_block) | ('деревян' in building_block) | ('сталин' in building_block) | 
              ('блочн' in building_block)):
            brick = 0

            
    return brick

In [695]:
def getAllBricks(l, r):
    
    bricks = []
    
    for i in range(l, r):
        flat_url = 'http://www.cian.ru/sale/flat/' + str(links[i]) + '/'
        flat_page = requests.get(flat_url)
        flat_page = flat_page.content
        flat_page = BeautifulSoup(flat_page, 'lxml')
        bricks.append(getBrick(flat_page))
        
    return bricks

In [697]:
bricks = getAllBricks(0, len(links))

New

Теперь перейдем к New - тут либо один вариант из двух (1 - новостройка, 0 - вторичка), либо nan.


In [690]:
def getNew(flat_page):
    table = flat_page.find('table', attrs = {'class':'object_descr_props'})
    table = html_stripper(table)
    
    new = np.nan
    
    building_block = re.split('Этаж|Тип продажи', table)[1]
    if 'Тип дом' in building_block:
        if 'новостр' in building_block:
            new = 1
        elif 'втор' in building_block:
            new = 0

            
    return new

In [691]:
def getAllNew(l, r):
    
    new = []
    
    for i in range(l, r):
        flat_url = 'http://www.cian.ru/sale/flat/' + str(links[i]) + '/'
        flat_page = requests.get(flat_url)
        flat_page = flat_page.content
        flat_page = BeautifulSoup(flat_page, 'lxml')
        new.append(getNew(flat_page))
        
    return new

In [693]:
new = getAllNew(0, len(links))

Теперь переходим дальше

Floor, Nfloors


In [698]:
#flat_url = 'http://www.cian.ru/sale/flat/' + str(links[2]) + '/'
flat_url = 'http://www.cian.ru/sale/flat/150387502/' 
flat_page = requests.get(flat_url)
flat_page = flat_page.content
flat_page = BeautifulSoup(flat_page, 'lxml')
table = flat_page.find('table', attrs = {'class':'object_descr_props'})
table = html_stripper(table)
table


Out[698]:
'\n\nОбщая информация:\n\n\nЭтаж:\n\n                3\xa0/\xa010\n                \n            \n\n\nТип дома:\n\n            вторичка, \n            кирпичный\n        \n\n\nТип продажи:\nсвободная\n\n\nОбщая площадь:\n\n34\xa0м2\n\n\n\nПлощадь комнат:\n\n19\xa0м2\n\n\n\nЖилая площадь:\n\n19\xa0м2\n\n\n\nПлощадь кухни:\n\n6\xa0м2\n\n\n\nСовмещенных санузлов:\n1\n            \n\n\nБалкон:\n1 балк.\n        \n\n\nЛифт:\n1 пасс.\n\n\nПарковка:\nназемная\n\n\nТелефон:\nда\n\n\nВид из окна:\nдвор\n\n\nРемонт:\nкосметический\n\n'

Информация по Floor и Nfloors тоже содержится между обязательными полями "Этаж" и "Тип продажи".


In [699]:
building_block = re.split('Этаж|Тип продажи', table)[1]
building_block


Out[699]:
':\n\n                3\xa0/\xa010\n                \n            \n\n\nТип дома:\n\n            вторичка, \n            кирпичный\n        \n\n\n'

In [700]:
floor_block = re.split('\xa0/\xa0|\n|\xa0', building_block)
floor_block


Out[700]:
[':',
 '',
 '                3',
 '10',
 '                ',
 '            ',
 '',
 '',
 'Тип дома:',
 '',
 '            вторичка, ',
 '            кирпичный',
 '        ',
 '',
 '',
 '']

Floor указан обязательно, и он лежит во второй ячейке, Nfloors может не быть, но если есть - то он в третьей.

Floor


In [701]:
def getFloor(flat_page):
    table = flat_page.find('table', attrs = {'class':'object_descr_props'})
    table = html_stripper(table)
    
    floor_is = 0
    
    building_block = re.split('Этаж|Тип продажи', table)[1]
    floor_block = re.split('\xa0/\xa0|\n|\xa0', building_block)
    
    for i in range(1, len(floor_block[2]) + 1):
        if(floor_block[2][-i].isdigit()):
            floor_is += int(floor_block[2][-i]) * 10**(i - 1)
            
    return floor_is

In [702]:
def getAllFloors(l, r):
    
    floors = []
    
    for i in range(l, r):
        flat_url = 'http://www.cian.ru/sale/flat/' + str(links[i]) + '/'
        flat_page = requests.get(flat_url)
        flat_page = flat_page.content
        flat_page = BeautifulSoup(flat_page, 'lxml')
        floors.append(getFloor(flat_page))
        
    return floors

In [704]:
floors = getAllFloors(0, len(links))

Nfloors


In [705]:
def getNFloor(flat_page):
    table = flat_page.find('table', attrs = {'class':'object_descr_props'})
    table = html_stripper(table)
    
    floors_count = np.nan
    
    building_block = re.split('Этаж|Тип продажи', table)[1]
    floor_block = re.split('\xa0/\xa0|\n|\xa0', building_block)
    
    if floor_block[3].isdigit():
        floors_count = int(floor_block[3])
            
    return floors_count

In [706]:
def getAllNFloors(l, r):
    
    nfloors = []
    
    for i in range(l, r):
        flat_url = 'http://www.cian.ru/sale/flat/' + str(links[i]) + '/'
        flat_page = requests.get(flat_url)
        flat_page = flat_page.content
        flat_page = BeautifulSoup(flat_page, 'lxml')
        nfloors.append(getNFloor(flat_page))
        
    return nfloors

In [708]:
nfloors = getAllNFloors(0, 20)

Перейдем дальше

Totsp, Livesp


In [709]:
#flat_url = 'http://www.cian.ru/sale/flat/' + str(links[2]) + '/'
flat_url = 'http://www.cian.ru/sale/flat/150387502/' 
flat_page = requests.get(flat_url)
flat_page = flat_page.content
flat_page = BeautifulSoup(flat_page, 'lxml')
table = flat_page.find('table', attrs = {'class':'object_descr_props'})
table = html_stripper(table)
table


Out[709]:
'\n\nОбщая информация:\n\n\nЭтаж:\n\n                3\xa0/\xa010\n                \n            \n\n\nТип дома:\n\n            вторичка, \n            кирпичный\n        \n\n\nТип продажи:\nсвободная\n\n\nОбщая площадь:\n\n34\xa0м2\n\n\n\nПлощадь комнат:\n\n19\xa0м2\n\n\n\nЖилая площадь:\n\n19\xa0м2\n\n\n\nПлощадь кухни:\n\n6\xa0м2\n\n\n\nСовмещенных санузлов:\n1\n            \n\n\nБалкон:\n1 балк.\n        \n\n\nЛифт:\n1 пасс.\n\n\nПарковка:\nназемная\n\n\nТелефон:\nда\n\n\nВид из окна:\nдвор\n\n\nРемонт:\nкосметический\n\n'

Всех полей, что находятся справа от "Общая площадь", может не быть. Так что ограничим нашу область поиска только слева.


In [710]:
space_block = re.split('Общая площадь', table)[1]
space_block


Out[710]:
':\n\n34\xa0м2\n\n\n\nПлощадь комнат:\n\n19\xa0м2\n\n\n\nЖилая площадь:\n\n19\xa0м2\n\n\n\nПлощадь кухни:\n\n6\xa0м2\n\n\n\nСовмещенных санузлов:\n1\n            \n\n\nБалкон:\n1 балк.\n        \n\n\nЛифт:\n1 пасс.\n\n\nПарковка:\nназемная\n\n\nТелефон:\nда\n\n\nВид из окна:\nдвор\n\n\nРемонт:\nкосметический\n\n'

Totsp и Livesp будут обязательно. Тут мы столкнемся с проблемой: Циан записывает float через запятую, Python - через точку, поэтому дефолтная float(str) не сработает. Набросаем свой конвертор.


In [711]:
def myStrToFloat(string):
    delimiter = 0
    value = 0
    for i in range(0, len(string)):
        if string[i] == ',':
            delimiter = i
    for i in range(0, delimiter):
        value += int(string[delimiter - i - 1]) * 10 ** i

    for i in range(1, len(string) - delimiter):
        value += (int(string[delimiter + i]) * (10 ** (i - 2)))
    return value

Totsp


In [715]:
def getTotsp(flat_page):
    table = flat_page.find('table', attrs = {'class':'object_descr_props'})
    table = html_stripper(table)
    
    space_block = re.split('Общая площадь', table)[1]
   
    total = re.split('Площадь комнат', space_block)[0]
    total_space = re.split('\n|\xa0', total)[2]
    if total_space.isdigit():
        total_space = int(total_space)
    else:
        total_space = myStrToFloat(total_space)
            
    return total_space

In [724]:
def getAllTotsp(l, r):
    
    totsp = []
    
    for i in range(l, r):
        flat_url = 'http://www.cian.ru/sale/flat/' + str(links[i]) + '/'
        flat_page = requests.get(flat_url)
        flat_page = flat_page.content
        flat_page = BeautifulSoup(flat_page, 'lxml')
        totsp.append(getTotsp(flat_page))
        
    return totsp

In [718]:
totsp = getAllTotsp(0, len(links))

Livesp


In [719]:
def getLivesp(flat_page):
    table = flat_page.find('table', attrs = {'class':'object_descr_props'})
    table = html_stripper(table)
    
    space_block = re.split('Общая площадь', table)[1]
   
    living = re.split('Жилая площадь', space_block)[1]
    living_space = re.split('\n|\xa0', living)[2]
    if living_space.isdigit():
        living_space = int(living_space)
    else:
        living_space = myStrToFloat(living_space)
            
    return living_space

In [722]:
def getAllLivesp(l, r):
    
    livesp = []
    
    for i in range(l, r):
        flat_url = 'http://www.cian.ru/sale/flat/' + str(links[i]) + '/'
        flat_page = requests.get(flat_url)
        flat_page = flat_page.content
        flat_page = BeautifulSoup(flat_page, 'lxml')
        livesp.append(getLivesp(flat_page))
        
    return livesp

In [725]:
livesp = getAllLivesp(0, len(links))

Переходим к опциональным полям

Kitsp, Bal, Tel


In [726]:
#flat_url = 'http://www.cian.ru/sale/flat/' + str(links[2]) + '/'
flat_url = 'http://www.cian.ru/sale/flat/150387502/' 
flat_page = requests.get(flat_url)
flat_page = flat_page.content
flat_page = BeautifulSoup(flat_page, 'lxml')
table = flat_page.find('table', attrs = {'class':'object_descr_props'})
table = html_stripper(table)
table


Out[726]:
'\n\nОбщая информация:\n\n\nЭтаж:\n\n                3\xa0/\xa010\n                \n            \n\n\nТип дома:\n\n            вторичка, \n            кирпичный\n        \n\n\nТип продажи:\nсвободная\n\n\nОбщая площадь:\n\n34\xa0м2\n\n\n\nПлощадь комнат:\n\n19\xa0м2\n\n\n\nЖилая площадь:\n\n19\xa0м2\n\n\n\nПлощадь кухни:\n\n6\xa0м2\n\n\n\nСовмещенных санузлов:\n1\n            \n\n\nБалкон:\n1 балк.\n        \n\n\nЛифт:\n1 пасс.\n\n\nПарковка:\nназемная\n\n\nТелефон:\nда\n\n\nВид из окна:\nдвор\n\n\nРемонт:\nкосметический\n\n'

Нужный нам подблок информации мы снова можем ограничить только слева названием поля "Общая площадь"


In [727]:
space_block = re.split('Общая площадь', table)[1]
space_block


Out[727]:
':\n\n34\xa0м2\n\n\n\nПлощадь комнат:\n\n19\xa0м2\n\n\n\nЖилая площадь:\n\n19\xa0м2\n\n\n\nПлощадь кухни:\n\n6\xa0м2\n\n\n\nСовмещенных санузлов:\n1\n            \n\n\nБалкон:\n1 балк.\n        \n\n\nЛифт:\n1 пасс.\n\n\nПарковка:\nназемная\n\n\nТелефон:\nда\n\n\nВид из окна:\nдвор\n\n\nРемонт:\nкосметический\n\n'

In [728]:
optional_block = re.split('Жилая площадь', space_block)[1]
optional_block


Out[728]:
':\n\n19\xa0м2\n\n\n\nПлощадь кухни:\n\n6\xa0м2\n\n\n\nСовмещенных санузлов:\n1\n            \n\n\nБалкон:\n1 балк.\n        \n\n\nЛифт:\n1 пасс.\n\n\nПарковка:\nназемная\n\n\nТелефон:\nда\n\n\nВид из окна:\nдвор\n\n\nРемонт:\nкосметический\n\n'

Kitsp

Если площадь кухни не указана, то она фигурирует в таблице в виде прочерка. При этом она может быть float с запятой в качестве разделителя, как и предыдущие площади. Учтем это


In [729]:
def getKitsp(flat_page):
    table = flat_page.find('table', attrs = {'class':'object_descr_props'})
    table = html_stripper(table)
    
    space_block = re.split('Общая площадь', table)[1]
    optional_block = re.split('Жилая площадь', space_block)[1]
    
    kitchen_space = np.nan
    
    if 'Площадь кухни' in optional_block:
        kitchen_block = re.split('Площадь кухни', optional_block)[1]
        if re.split('\n|\xa0', kitchen_block)[2] != '–':
            if re.split('\n|\xa0', kitchen_block)[2].isdigit():
                kitchen_space = int(re.split('\n|\xa0', kitchen_block)[2])
            else:
                kitchen_space = myStrToFloat(re.split('\n|\xa0', kitchen_block)[2])
            
    return kitchen_space

In [730]:
def getAllKitsp(l, r):
    
    kitsp = []
    
    for i in range(l, r):
        flat_url = 'http://www.cian.ru/sale/flat/' + str(links[i]) + '/'
        flat_page = requests.get(flat_url)
        flat_page = flat_page.content
        flat_page = BeautifulSoup(flat_page, 'lxml')
        kitsp.append(getKitsp(flat_page))
        
    return kitsp

In [732]:
kitsp = getAllKitsp(0, len(links))

Bal

Балкона может не быть, балкон может быть, а еще балконов может быть больше одного. Информации о балконе может не быть.

Так что будем готовы к значениям > 1. Прочерк будет говорить о том, что информации нет, слово "нет" - о том, что есть информация о том, что нет балкона.


In [737]:
def getBal(flat_page):
    table = flat_page.find('table', attrs = {'class':'object_descr_props'})
    table = html_stripper(table)
    
    space_block = re.split('Общая площадь', table)[1]
    optional_block = re.split('Жилая площадь', space_block)[1]
    
    balcony = np.nan
    if 'Балкон' in optional_block:
        balcony_block = re.split('Балкон', optional_block)[1]
        if re.split('\n', balcony_block)[1] != 'нет':
            if re.split('\n', balcony_block)[1] != '–':
                balcony = int(re.split('\n', balcony_block)[1][0])
        else:
            balcony = 0
            
    return balcony

In [738]:
def getAllBal(l, r):
    
    bal = []
    
    for i in range(l, r):
        flat_url = 'http://www.cian.ru/sale/flat/' + str(links[i]) + '/'
        flat_page = requests.get(flat_url)
        flat_page = flat_page.content
        flat_page = BeautifulSoup(flat_page, 'lxml')
        bal.append(getBal(flat_page))
        
    return bal

In [740]:
bal = getAllBal(0, len(links))

Tel

Телефон может быть, может не быть, и о нем может не быть информации. О том, что он есть, нам сообщает слово "да", о том, что его нет - слово "нет".


In [741]:
def getTel(flat_page):
    table = flat_page.find('table', attrs = {'class':'object_descr_props'})
    table = html_stripper(table)
    
    space_block = re.split('Общая площадь', table)[1]
    optional_block = re.split('Жилая площадь', space_block)[1]
    
    telephone = np.nan
    if 'Телефон' in optional_block:
        telephone_block = re.split('Телефон', optional_block)[1]
        if re.split('\n', telephone_block)[1] == 'да':
            telephone = 1
        elif re.split('\n', telephone_block)[1] == 'нет':
            telephone = 0
            
    return telephone

In [742]:
def getAllTel(l, r):
    
    tel = []
    
    for i in range(l, r):
        flat_url = 'http://www.cian.ru/sale/flat/' + str(links[i]) + '/'
        flat_page = requests.get(flat_url)
        flat_page = flat_page.content
        flat_page = BeautifulSoup(flat_page, 'lxml')
        tel.append(getTel(flat_page))
        
    return tel

In [744]:
tel = getAllTel(0, len(links))

Последние штрихи перед сборкой DataFrame

N


In [745]:
N = []
for i in range(0, len(links)):
    N.append(i)

В этом ноутбуке квартиры только из ЦАО, укажу это

District


In [746]:
district = []
for i in range(0, len(links)):
    district.append('CAD')

Собираем все воедино

DataFrame


In [808]:
data = dict([('New', new), ('Bal', bal), ('Tel', tel), ('Walk', walk), ('Metrdist', metrdist), ('Nfloors', nfloors), ('Floor', floor), ('Totsp', totsp), ('Livesp', livesp), ('Kitsp', kitsp), ('N', N), ('Price', prices), ('Rooms', rooms), ('Distance', distance), ('Brick', bricks), ('District', district)])

In [809]:
df = pd.DataFrame(data)

In [810]:
df.T


Out[810]:
0 1 2 3 4 5 6 7 8 9 ... 802 803 804 805 806 807 808 809 810 811
Bal 0 NaN 1 NaN NaN 0 0 0 NaN NaN ... 1 NaN 1 1 NaN 1 1 1 0 NaN
Brick NaN 1 1 1 1 NaN NaN 1 1 NaN ... 1 0 1 1 NaN 1 NaN 0 0 0
Distance 1.45216 1.73696 1.75092 2.71596 4.19487 4.05345 4.05345 4.09831 4.05345 4.50236 ... 4.49908 3.05 4.06076 4.12913 3.27558 2.04415 5.22719 2.09632 2.19363 1.54887
District CAD CAD CAD CAD CAD CAD CAD CAD CAD CAD ... CAD CAD CAD CAD CAD CAD CAD CAD CAD CAD
Floor 2 2 3 2 9 3 2 2 2 1 ... 5 10 3 3 2 3 7 5 6 1
Kitsp 15 12 7 40 15 NaN NaN NaN NaN 7 ... 6.1 12 NaN 12 NaN 20 10 8 9 6
Livesp 89 94 30 162 35 22.7 17.3 17.3 10 20 ... 42.8 37 60.9 54 0 30 51 35 47 37
Metrdist 11 10 3 7 5 6 6 5 6 3 ... 8 5 7 5 13 12 15 4 2 4
N 0 1 2 3 4 5 6 7 8 9 ... 802 803 804 805 806 807 808 809 810 811
New 0 0 0 0 0 1 1 0 1 0 ... 0 0 0 0 1 1 0 0 0 0
Nfloors 4 7 8 14 11 NaN NaN 6 5 9 ... 8 10 5 5 7 8 14 9 14 6
Price 55000000 55000000 15400000 2500000 3750000 4403057 4585315 4677021 4814581 5100000 ... 19500000 19500000 19561000 19610000 19723264 19800000 19800000 19900000 19900000 19900000
Rooms 4 5 2 5 2 1 1 1 1 1 ... 3 2 2 3 2 1 3 2 3 2
Tel 1 1 NaN 0 NaN NaN NaN 0 NaN 1 ... 1 1 0 NaN NaN NaN NaN 1 1 1
Totsp 117 145 46 232 70 22.7 17.3 17.3 17.3 32 ... 65.4 70 60.9 85 54.4 50.8 82 56 68 58
Walk 1 1 1 1 1 1 1 1 1 1 ... 1 1 1 1 1 1 1 1 0 1

16 rows × 812 columns


In [811]:
df.to_csv('cian.csv', index=False)

Остальные округи буду считать этим же ноутбуком, так что по содержательной части всё.