BugBug

Парсинг сайта с помощью Python и Scrapy

Привет, в этой статье я расскажу как можно собрать информацию о 10000 фильмах с сайта IMDb с помощью python фреймворка Scrapy.

Для начала давайте установим Scapy это можно сделать при помощи pip.

$ pip install scrapy

После успешной установки можно приступать к написанию скрипта.

Мы будем использовать базовые возможности Scapy, подробно о возможностях фреймворка читайте информацию на официальном сайте.

Создадим базового паука

Создадим класс IMDbSpyder являющимся подклассом scrapy.Spider. Это базовый класс предоставляемый Scapy. Наш класс имеет два обязательных атрибута:

  • name-имя паука
  • start_urls-список URL-адресов, с которых вы начинаете парсинг. Наш класс начнет с одного адреса
  

  import scrapy

  class IMDbSpyder(scrapy.Spider):
      name='imdb_spyder'
      start_urls=['http://www.imdb.com/search/title?country_of_origin=ru&page=1&ref_=adv_prv',]


Разберем код по строкам:

Мы импортируем scrapy, чтобы воспользоваться базовыми классами фреймворка. Затем, мы делаем из класса Spider подкласс IMDbSpyder. Подскласс имеет все методы и модели поведения базового класса, более подробно о которых можно узнать в документации. Далее мы называем нашего паука imdb_spyder. В последней строчке мы даем нашему парсеру URL-адрес http://www.imdb.com/search/title?country_of_origin=ru&page=1&ref_=adv_prv. По этому адресу находиться список фильмов популярных в России

Проверим наш python скрипт.Scapy имеет собственный интерфейс командной строки. Для того что бы запустить наш парсер в терминале наберите следующую команду:

$ scrapy runspider scraper.py

Вы увидите нечто подобное:

2017-08-23 12:03:18 [scrapy.core.engine] INFO: Closing spider (finished)
2017-08-23 12:03:18 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 263,
 'downloader/request_count': 1,
 'downloader/request_method_count/GET': 1,
 'downloader/response_bytes': 36837,
 'downloader/response_count': 1,
 'downloader/response_status_count/200': 1,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2017, 8, 23, 9, 3, 18, 694534),
 'log_count/DEBUG': 2,
 'log_count/ERROR': 1,
 'log_count/INFO': 7,
 'response_received_count': 1,
 'scheduler/dequeued': 1,
 'scheduler/dequeued/memory': 1,
 'scheduler/enqueued': 1,
 'scheduler/enqueued/memory': 1,
 'spider_exceptions/NotImplementedError': 1,
 'start_time': datetime.datetime(2017, 8, 23, 9, 3, 16, 965521)}
2017-08-23 12:03:18 [scrapy.core.engine] INFO: Spider closed (finished)

При вызове этой команды Scapy выполняет кучу работы автоматически:

  • Загружаются дополнительные компоненты и расширения, необходимые для обработки и чтения данных из URL-адресов.
  • Скачивается HTML-код страницы предоставленной страницы.
  • Передается этот HTML-код parse методу, который мы еще напишем

Теперь можно приступать к извлечению данных со страницы

Извлечение данных со страницы

Извлечение данных начинается с изучения структуры страницы. Для этого нам потребуется открыть инструмент разработчика встроенный в ваш браузер и просмотреть HTML-код страницы. Выглядит он вот так: IMDb html

Scapy извлекает данные со страницы на основе селекторов. Селекторы - это шаблоны используемые для поиска одного или нескольких элементов на странице. Scapy поддерживает CSS и XPath селекторы.

Напишем метод parse нашего класса IMDbSpyder:

def parse(self, response):
        SET_SELECTOR='//div[@class="lister-item mode-advanced"]'
        for i in response.xpath(SET_SELECTOR):
          pass

В этом коде мы передаем XPath селектор методу xpath объекта response и таким образом находим все записи на странице содержащие нужную нам информацию о фильмах. При помощи нашего парсера мы получим: название фильма, его рейтинг, год выпуска, продолжительность, жанр и кассовые сборы. Вы легко сможете модифицировать код что бы получить и другие данные.

Продолжим изучение HTML-кода страницы. Название фильма содержится в ссылке заключенной в теге h3. IMDb html Мы легко можем его извлечь немного дописав наш скрипт:


class Scrap(scrapy.Spider):
    name='scrap_my'
    start_urls=['http://www.imdb.com/search/title?country_of_origin=ru&page=1&ref_=adv_prv']
    def parse(self, response):
        SET_SELECTOR='//div[@class="lister-item mode-advanced"]'
        for i in response.xpath(SET_SELECTOR):
            yield {
                'title':i.xpath('.//h3/a/text()').extract_first(),

            } 

В этом коде мы проходим в цикле все наши div-контейнеры и извлекаем текст из ссылок. Если вы запустите ваш скрипт, то увидите что он успешно извлек название фильмов:

  
...
2017-08-24 12:36:06 [scrapy.core.scraper] DEBUG: Scraped from <200 http://www.imdb.com/search/title?country_of_origin=ru&page=1&ref_=adv_prv>
{'title': 'Burnt by the Sun'}
2017-08-24 12:36:06 [scrapy.core.scraper] DEBUG: Scraped from <200 http://www.imdb.com/search/title?country_of_origin=ru&page=1&ref_=adv_prv>
{'title': 'Мажор'}
2017-08-24 12:36:06 [scrapy.core.scraper] DEBUG: Scraped from <200 http://www.imdb.com/search/title?country_of_origin=ru&page=1&ref_=adv_prv>
{'title': 'Экипаж'}
2017-08-24 12:36:06 [scrapy.core.scraper] DEBUG: Scraped from <200 http://www.imdb.com/search/title?country_of_origin=ru&page=1&ref_=adv_prv>
{'title': 'The Duelist'}
2017-08-24 12:36:06 [scrapy.core.scraper] DEBUG: Scraped from <200 http://www.imdb.com/search/title?country_of_origin=ru&page=1&ref_=adv_prv>
{'title': 'Snow Queen'}
2017-08-24 12:36:06 [scrapy.core.scraper] DEBUG: Scraped from <200 http://www.imdb.com/search/title?country_of_origin=ru&page=1&ref_=adv_prv>
{'title': 'Снежная королева 3. Огонь и лед'}
...
  

Аналогичным образом можно извлечь и остальные нужные нам данные со страницы:


  def parse(self, response):
        SET_SELECTOR='//div[@class="lister-item mode-advanced"]'
        for i in response.xpath(SET_SELECTOR):
            yield {
                'title':i.xpath('.//h3/a/text()').extract_first(),
                'year':i.xpath('.//h3/span[@class="lister-item-year text-muted unbold"]/text()').extract_first(),
                'rating':i.xpath('.//div[@class="inline-block ratings-imdb-rating"]/strong/text()').extract_first(),
                'length_of_film':i.css('div.lister-item-content p.text-muted span.runtime::text').extract_first(),
                'genre':i.css('div.lister-item-content p.text-muted span.genre::text').extract_first(),
                'gross':i.css('div.lister-item-content p.sort-num_votes-visible span.ghost+span.text-muted+span::text').extract_first(),
            }

В данном коде я применил и CSS-селекторы для поиска данных. В целом CSS и XPath селекторы схожи и не затруднительны в использовании. Снова запустив скрипт вы увидите что все данные необходимые нам на странице мы получили. Время переходить к следующему этапу обходу всех страниц.

Обход нескольких страниц

На данный момент скрипт извлекает информацию только с одной страницы, а их может быть очень много. С помощью Scapy мы легко сможем собрать подобные данные и с остальных страниц.

На целевой странице, вверху, располагается пагинация, которая нам и понадобиться для обхода всех страниц сайта содержащих нужную нам информацию. Снова откроем инструмент разработчика в браузере для просмотра HTML-кода и изучим этот элемент. IMDb html Нам нужно извлечь атрибут href ссылки на следующую страницу. Если ссылка присутствует на странице и скрипт ее успешно извлек он передает ее обратно методу parse для обработки:


import scrapy

class Scrap(scrapy.Spider):
    name='scrap_my'
    start_urls=['http://www.imdb.com/search/title?country_of_origin=ru&page=1&ref_=adv_prv']
    def parse(self, response):
        SET_SELECTOR='//div[@class="lister-item mode-advanced"]'
        for i in response.xpath(SET_SELECTOR):
            yield {
                'title':i.xpath('.//h3/a/text()').extract_first(),
                'year':i.xpath('.//h3/span[@class="lister-item-year text-muted unbold"]/text()').extract_first(),
                'rating':i.xpath('.//div[@class="inline-block ratings-imdb-rating"]/strong/text()').extract_first(),
                'length_of_film':i.css('div.lister-item-content p.text-muted span.runtime::text').extract_first(),
                'genre':i.css('div.lister-item-content p.text-muted span.genre::text').extract_first(),
                'gross':i.css('div.lister-item-content p.sort-num_votes-visible span.ghost+span.text-muted+span::text').extract_first(),
            }
        NEXT_PAGE='div#main div.article div.sub-list div.nav div.desc a.next-page::attr(href)'
        next_page=response.css(NEXT_PAGE).extract_first()
        if next_page:
            yield scrapy.Request(
                response.urljoin(next_page),callback=self.parse
            )

Так выглядит полный код парсера собирающего данные о фильмах с сайта IMDb. Более подробно о том как писать парсеры при помощи фреймворка Scapy читайте в официальной документации.