Парсинг фотографий с сайта cian.ru с помощью Selenium

  • Tutorial

Здравствуйте дорогие хабровчане, в этом небольшом примере я хочу показать как можно распарсить страницу, данные на которую подгружаются с помощью javascript виджетов. Более того, даже если страницу в этом примере просто сохранить, то всё равно не получится спарсить из неё все нужные фотографии из-за этих виджетов. В данном случае я использую для примера сайт cian.ru, у которого есть свой api, который я использовать не буду, вместо этого я буду использовать Selenium. Я не работаю в cian.ru, просто использую этот сайт для примера. Код в парсере простой и расчитан на начинающих.


Небольшое вступление — когда на досуге я рассматривал примеры ремонтов в cian.ru, я подумал, что не плохо было бы сохранить понравившиеся мне фотографии, но вручную сохранять их было бы долго, к тому же это не наш метод, так я и решил написать этот парсер.


Парсер написан на языке python3 из дистрибутива Anaconda, Selenium и chromedriver binary я установил отдельно именно из этих ссылок. (Ну и конечно же в системе должен быть установлен барузер Google Chrome)


Ниже представлен полный код парсера, далее я разберу основные моменты отдельно.


from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import NoSuchElementException
import chromedriver_binary 
import urllib
import time

print('start...')

site = "https://www.cian.ru/sale/flat/222059642/"

chrome_options = Options()
chrome_options.add_argument("--headless")

driver = webdriver.Chrome(options=chrome_options)
#driver = webdriver.Chrome()
driver.get(site)

i = 0

while True:   
    try:
        url = driver.find_element_by_xpath("//div[contains(@class, 'fotorama__active')]/img").get_attribute('src')
    except NoSuchElementException:
        break        

    i += 1
    print(i, url)

    driver.find_element_by_xpath("//div[@class='fotorama__arr fotorama__arr--next']").click()

    name = url.split('/')[-1]
    urllib.request.urlretrieve(url, name)

    time.sleep(2)

print('done.')

Первым делом я загрузил страницу https://www.cian.ru/sale/flat/222059642/ с понравившимися мне фотографиями. Для этого я создал объект driver браузера и передал ему ссылку через метод get. Обратите внимание, что я использую Headless Chrome, т.е. передаю в webdriver.Chrome() параметры опций браузера с аргументом --headless, благодаря этому браузер не будет реально отрисовывать содержимое страницы, если вы захотите посмотреть на отрисовку, то не передавайте аргументы chrome_options и тогода вы сможете увидеть, что происходит на самом деле.


site = "https://www.cian.ru/sale/flat/222059642/"

chrome_options = Options()
chrome_options.add_argument("--headless")

driver = webdriver.Chrome(options=chrome_options)
#driver = webdriver.Chrome()
driver.get(site)

Далее в цикле я начал парсить фотографии, логика парсера работет также, как если бы я сам скачивал их вручную, т.е. сохраняю текущую фотографию и нажимаю на стрелку "next".



Код ниже сохраняет в переменную url ссылку на фотографию, блок try/except отслеживает ошибку NoSuchElementException, эта ошибка возникает, когда все фотографии скачаны и Selenium больше не находит ссылку.


    try:
        url = driver.find_element_by_xpath("//div[contains(@class, 'fotorama__active')]/img").get_attribute('src')
    except NoSuchElementException:
        break       

Слудующий блок кода буквально кликает по стрелке для перехода к следующей фотографии.


    driver.find_element_by_xpath("//div[@class='fotorama__arr fotorama__arr--next']").click()

Далее сохраняем фотографию по ссылке на диск через библиотеку urllib.


    name = url.split('/')[-1]
    urllib.request.urlretrieve(url, name)

И в конце простой но важный код, задержка позволяет полностью подгрузиться странтице после клика по стрелке. (здесь можно сделать код почище организовав задержку средствами Selenium)


    time.sleep(2)

Вот такой пример парсера фоторграфий на Selenium, не утверждаю, что это лучший подход, если кто-то знает как сделать лучше напишите свои идеи в комментах.


Исходный код здесь.

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 14

    0

    А почему именно фотографии, а не цены например?

    0

    cian написан на react + ssr. Там обычно валяется json стейт всей страницы. Проще просто выполнить


    window._cianConfig['frontend-offer-card'].find(i => i.key === 'defaultState').value.offerData.offer.photos

    И там есть все, что нужно. Вероятность, что поменяется структура данный на порядок ниже, чем вероятность, что поменяется разметка.

      0

      Спасибо за коммент, подскажите, где такому можно научиться?

        0

        Это опыт разработки сайтов + в их докладах видел стек. Не думаю что изучать такое отдельно имеет смысл.

          0

          Можете хотя бы в двух словах сказать, как вы поняли, что нужно смотреть в javascript? Или это в их докладе сказали?

            0

            Прочитайте пару статей по SSR, если интересно почему и как (я не знаю насколько вам близка фронтенд разработка, сложно дать точную инфу). Из гугл топа
            https://tproger.ru/translations/rendering-on-the-web/
            Смотрите часть с заголовком "Проблема регидратации: одно приложение по цене двух", там этот самый случай.


            UPD
            Вообще есть примерно 4-5 популярных способов реализации такой галереи (ссылки в html, ссылки из запроса апи, ссылки в js/json где-то). Можно начать просто с поиска url картинки в исходном коде картинки. Тут она лежала в json, который можно прочитать из window._cianConfig, дальше нюансы. Методом тыка можно находить, но с опытом в разработке таких сайтов это происходит быстрее.

    0

    Ничего не сказано про то что такое selenium, почему именно selenium, etc

    0
    Как мне кажется, чтобы использовать модуль urllib.request, его необходимо импортировать в самом начале:
    import urllib.request
      0
      А зачем тут вообще selenium?
      Как написали выше, в коде страницы есть данные, да еще и в удобном формате.
      Тут можно все намного проще сделать:
      import requests
      import json
      
      start_json_template = "window._cianConfig['frontend-offer-card'] = "
      
      url = "https://www.cian.ru/sale/flat/222059642/"
      
      photos = []
      
      response = requests.get(url)
      html = response.text
      
      if start_json_template in html:
          start = html.index(start_json_template) + len(start_json_template)
          end = html.index('</script>', start)
          json_raw = html[start:end].strip()[:-1]
          json = json.loads(json_raw)
          for item in json:
              if item['key'] == 'defaultState':
                  for photo in item['value']['offerData']['offer']['photos']:
                      photos.append(photo['fullUrl'])
                  break
      
      print(photos)
      
        +1

        Может где туториал есть, где подробно такой финт разбирается, интересно было-бы посмотреть.

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

        У них (Циан) можно воспользоваться АПИ, которое не требует регистрации/авторизации
        curl -v -H "Content-Type: application/json" -d '{"jsonQuery":{"region":{"type":"terms","value":[4743]},"_type":"flatrent","engine_version":{"type":"term","value":2},"for_day":{"type":"term","value":"!1"},"page":{"type":"term","value":1}}}' https://api.cian.ru/search-offers/v2/search-offers-desktop/


        Это пример запроса, который делает браузер при нажатии на след. страницу, соответсвенно значение страницы ставим свое и вуа ля, получаем объекты недвижимости с ссылками на фото.

        Only users with full accounts can post comments. Log in, please.