Продолжаем изучать парсинг с Python. В прошлый раз мы использовали библиотеки Requests, Beautifulsoup и Selenium. Сегодня поговорим о библиотеке Scrapy. Она позволяет быстро собирать информацию с сайтов, работать с динамическими сайтами, когда информация подгружается автоматически, работать с api. На примере книжного магазина мы попробуем разобраться, как работает эта библиотека.

Съемка и монтаж: Глеб Лиманский

Работать со Scrapy удобнее в редакторе Visual Studio Code. Если вы еще не знаете, как работать в VStudio и устанавливать виртуальное окружение, посмотрите уроки, где мы показывал, как написать робота для анализа госконтрактов. В них мы подробно рассказывали, как установить редактор, разбирали базовые команды в терминале, учились устанавливать и запускать виртуальное окружение. 

Этот урок мы начнем с момента, когда VStudio и виртуальное окружение установлено. 

Создаем проект

В редакторе мы открываем терминал. Через терминал создадим папку, где будет лежать наш проект, назовем его our_spider. Команда: mk dir our_spider. 

Перейдем в эту папку: cd our_spider.

И запустим виртуальное окружение: pipenv shell

Теперь установим библиотеку: pip install scrapy

Чтобы было легче смотреть на команды, которые мы вводим, можно иногда очищать терминал. Для этого на Mac есть горячие клавиши: control+L.

Создадим наш первый проект, назовем его bestsellers. Для этого вводим команду: scrapy startproject bestsellers. 

Перейдем в папку: cd bestsellers. 

Откроем эту папку и в редакторе: open folder → our_spider.

Мы видим, что в папке с проектом появилось несколько папок и файлов. Это файлы конфигурации, настройки нашего скрейпера. Подробнее о том, что содержится в каждом файле, написано в официальной документации Scrapy. Но сейчас нас эти файлы не интересуют. Мы заходим в папку spiders. В ней будут лежать пауки это файлы, в которых мы прописываем, что именно нужно собирать на сайте. Создадим первого паука. Для этого нужно нажать на значок папки рядом с названием основной папки — bestsellers. Назовем нашего паука book_spider.py. Не забываем указывать расширение .py для питона. 

Изучаем сайт

Посмотрим на сайт, с которым мы будем работать. Это онлайн-магазин Book24. Мы будем собирать информацию о книгах из раздела «бестселлеры». Для этого нужно зайти на страницу каждой книги, собрать нужную информацию, выйти и зайти на страницу уже следующей книги. И так пройти по всем книгам раздела. 

Прежде чем начинать писать код, посмотрим, какие команды будут доставать нужные нам элементы. Для этого перейдем в scrapy shell. Scrapy shell — это оболочка, что-то вроде отдельной комнаты, в которой можно пробовать обращаться к сайту с помощью отдельных команд и смотреть, какие ответы возвращаются. При этом не нужно писать целый код.

Чтобы перейти в scrapy shell, набираем в терминале команду scrapy shell и нажимаем enter.

Теперь обратимся к сайту, с которым будем работать. Для этого воспользуемся командой fetch. В скобках и кавычках передадим ссылку на страницу книги. В ответе мы увидели число 200. Это значит, сайт работает и готов отдавать нам информацию. 

Чтобы обратиться к сайту, воспользуемся командой response. Обращаться к элементам мы можем через css-элементу или xpath. Об этом мы уже рассказывали в прошлых уроках. Попробуем обратиться по css-элементу. 

Название книги

Посмотрим, где лежит название нашей книги. Для этого нажмем правой кнопкой мыши на название и выберем «посмотреть код». Откроются элементы разработчика. Заголовок книги лежит в теге h1 c классом product-detail-page__title. Копируем его. 

В терминале набираем response.css('h1.product-detail-page__title'). Нажимаем enter. Мы получили весь css-селектор.

В этой записи сложно что-то разобрать. Чтобы было легче понять, что мы достали, после названия класса напишем два двоеточия и слово text: response.css('h1.product-detail-page__title::text'). Чтобы каждый раз не переписывать команду в терминале с нуля, можно вызывать последнюю команду с помощью горячих клавиш: на Mac это стрелка вверх. 

Мы получили строку, в которой есть нужный нам заголовок. Чтобы достать его, допишем в конец команды get, он выводит первый элемент из списка css-селекторов: response.css('h1.product-detail-page__title::text').get(). Мы получили нужный нам заголовок. 

Чтобы избавиться от пробелов по бокам, воспользуемся уже известным нам методом strip: response.css('h1.product-detail-page__title::text').get().strip()

Эту команду можно скопировать в какой-нибудь текстовый документ. Она нам еще понадобится. 

Количество покупок

Теперь попробуем собрать информацию о количестве покупок. Она находится в теге p с классом product-detail-page__purchased-text. 

Возвращаемся в терминал. Опять пишем команду response.css, только теперь вставляем тег p и нужный класс, сразу допишем text и get: response.css('p.product-detail-page__purchased-text::text').get()

Мы получили строку: « Купили 939 раз ». Но мы хотим извлечь только число, для этого будем использовать уже известный нам метод split. Он разбивает строку по пробелам и возвращает список слов. 

Нам нужен первый элемент в списке. Не забываем, что в программировании отсчет начинается с нуля: response.css('p.product-detail-page__purchased-text::text').get().split()[1]

Мы получили нужное нам число. Скопируйте или запомните эту команду, она нам понадобится. 

Название раздела

Третий элемент, который мы будем собирать на странице книги — информация о разделе. Опять нажимаем на название раздела правой кнопкой мыши и выбираем «просмотреть код». 

Мы видим, что название раздела находится в атрибуте title тега a, который вложен в тег div. Скопируем класс тега div и вернемся в терминал. 

Набираем команду response. Так как заголовок был вложен в атрибут тега а, после названия класса напишем тег a и через два двоеточия слово attr и в скобках название атрибута: response.css('div.product-characteristic__value a::attr(title)'). 

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

Нужная нам характеристика лежит в третьем теге. Так как в программировании отсчет начинается с нуля, обратимся ко второму элементу списка: response.css('div.product-characteristic__value a::attr(title)')[2].get().

 

Мы получили название раздела, которое нам нужно. Скопируйте эту команду, она нам еще понадобится. 

Пишем Spider

Теперь, когда мы понимаем, как доставать нужные элементы, можем написать нашего первого паука. Для начала импортируем scrapy: import scrapy. 

В библиотеке scrapy каждый паук, который собирает информацию, — это класс. Нам нужно создать его.

Дадим пауку имя — book24. И начальную ссылку, с которой нужно начать поиск. В нашем случае это раздел бестселлеров.

Правила скрейпинга мы задаем, используя метод parse. Cтандартная запись для всех методов Scrapy: def parse(self, response):

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

Посмотрим, где лежит ссылка на страницу книжек. Ссылка это атрибут href в теге а, который вложен в тег div.

Мы можем вызвать ее так же, как вызывали название раздела, только укажем правильное название класса, а вместо атрибута title напишем href: response.css('div.product-card__image-holder a::attr(href)').get(). 

Пишем цикл для перехода по книгам

Нам нужно, чтобы паук проходился по каждой ссылке и заходил в нее. Для этого напишем цикл. Скажем, что нужно проходиться по каждой ссылке из нашего списка. Здесь мы уже не дописываем get к команде response.css, потому что get выводит первый элемент из списка css-селекторов, а нам нужны ссылки на все книги.

Для перехода по ссылке воспользуемся этого методом follow. Пишем yield, это ключевое слово в Scrapy, которое позволяет выполнить разные действия. Затем пишем response.follow и в качестве атрибута передадим нашу ссылку. А еще напишем callback — правило, по которому паук поймет, что именно нужно сделать на странице, на которую он перейдет. Если не напишем callback, паук просто перейдет по ссылке и ничего не сделает. В callback передадим новый метод parse_book.

Собираем информацию о каждой книге

Метод parse.book мы пишем так же, как до этого писали метод parse. В нем мы зададим, что именно нужно собирать на странице каждой книги. Для этого опять напишем ключевое слово yield и создадим словарь. Ключами в нашем словаре будут слова: name, buy и type (название книги, количество покупок, название раздела). А в качестве значений передадим команды, которые мы вызывали в scrapy shell (помните, мы советовали их скопировать).

Переход по страницам

Нам осталось сделать добавить переход по страницам. Так как переходить на новую страницу нужно, когда собрана вся информация на первой странице, код с переходом добавим внизу метода parse. Переходить мы будем с помощью уже известного слова yield и команды response.follow. Только на этот раз переходить будем не по ссылке внутрь книги, а по сслыке на новую страницу. 

Как получить ссылку на следующую страницу с помощью f-строк мы рассказывали в первом уроке по скрейпингу. 

В callback прописываем, что нужно сделать после перехода: зайти внутрь каждой книги. Эту задачу мы прописывали в методе parse, поэтому пишем callback=self.parse

Запускаем паука и записываем результат парсинга в файл

Теперь наш паук полностью готов, осталось его запустить. Для начала нужно выйти из scrapy shell. На Mac для этого есть горячие клавиши control+d. Если мы уверены, что хотим выйти — нажимаем y на клавиатуре. Не забываем сохранить нашего паука.

Набираем в терминале последнюю команду scrapy crawl, передаем название нашего парсера — book24.

Чтобы записать данные в файл, допишем: -O book.csv. Паук соберет все данные в файл с названием book и расширением csv. 

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

 

Если у вас что-то не получилось — пишите в наш Telegram-чат, постараемся подсказать.