Основы программирования в Python

Алла Тамбовцева, НИУ ВШЭ

Посмотрим на другие примеры использования selenium.

Пример. Зайдем на сайт книжного магазина и найдем все книги В.Пелевина. Загрузим библиотеку, веб-драйвер и откроем страницу в браузере через Python.

In [1]:
from selenium import webdriver as wb
br = wb.Chrome("/Users/allat/Downloads/chromedriver")

# открываем страницу в Chrome в автоматическом режиме
br.get("http://www.biblio-globus.ru/")

Найдем с помощью CSS Selector'а (SelectorGadget) поле для ввода названия книги или автора.

In [2]:
field = br.find_element_by_css_selector("input")

Сохраним фамилию автора:

In [3]:
author = "Пелевин"

Введем фамилию автора в поле для поиска (.send_keys) и подождем чуть-чуть:

In [4]:
field.send_keys("Пелевин")
br.implicitly_wait(2)  # подождем пару секунд

Теперь найдем кнопку для поиска (значок лупа рядом со строкой поиска) через CSS Selector:

In [5]:
submit = br.find_element_by_css_selector("#search_submit")

Кликнем на нее:

In [6]:
submit.click()

Сохраним первую страницу с результатами в переменную page1.

In [7]:
page1 = br.page_source
In [9]:
page1

Теперь обработаем эту страницу через BeautifulSoup:

In [10]:
from bs4 import BeautifulSoup
In [11]:
soup1 = BeautifulSoup(page1, 'lxml')

Найдем все названия книг на этой странице. По исходному коду можно увидеть, что они имеют тэг a с атрибутом class, равным name:

In [12]:
soup1.find_all('a', {'class':'name'})
Out[12]:
[<a class="name" href="/search/catalog/details/10535935">Четверо</a>,
 <a class="name" href="/search/catalog/details/10525596">iPhuck 10</a>,
 <a class="name" href="/search/catalog/details/9465869">Чапаев и Пустота</a>,
 <a class="name" href="/search/catalog/details/10231526">Священная книга оборотня</a>,
 <a class="name" href="/search/catalog/details/10236177">S.N.U.F.F.</a>,
 <a class="name" href="/search/catalog/details/10311151">Лампа Мафусаила, или Крайняя битва чекистов с масонами</a>,
 <a class="name" href="/search/catalog/details/10222930">Generation П</a>,
 <a class="name" href="/search/catalog/details/10437108">Числа</a>,
 <a class="name" href="/search/catalog/details/10452010">Лампа Мафусаила, или Крайняя битва чекистов с масонами</a>,
 <a class="name" href="/search/catalog/details/10480299">Бэтман Аполло</a>]

С помощью списковых включений выберем из ссылок с тэгом <a> текст (так мы уже делали, и не раз).

In [13]:
books1 = [b.text for b in soup1.find_all('a', {'class':'name'})]
In [14]:
books1
Out[14]:
['Четверо',
 'iPhuck 10',
 'Чапаев и Пустота',
 'Священная книга оборотня',
 'S.N.U.F.F.',
 'Лампа Мафусаила, или Крайняя битва чекистов с масонами',
 'Generation П',
 'Числа',
 'Лампа Мафусаила, или Крайняя битва чекистов с масонами',
 'Бэтман Аполло']

Теперь аналогичным образом сгрузим информацию о наличии книг:

In [15]:
instore1 = [s.text for s in soup1.find_all('div', {'class':'title_data green'})]
In [16]:
instore1
Out[16]:
['\n                        В наличии\n                    ',
 '\n                        В наличии\n                    ',
 '\n                        В наличии\n                    ',
 '\n                        В наличии\n                    ',
 '\n                        В наличии\n                    ',
 '\n                        В наличии\n                    ',
 '\n                        В наличии\n                    ',
 '\n                        В наличии\n                    ',
 '\n                        В наличии\n                    ',
 '\n                        В наличии\n                    ']

Уберем лишнее:

In [17]:
instore1 = [i.strip() for i in instore1]  # убираем лишние пробелы
In [18]:
instore1
Out[18]:
['В наличии',
 'В наличии',
 'В наличии',
 'В наличии',
 'В наличии',
 'В наличии',
 'В наличии',
 'В наличии',
 'В наличии',
 'В наличии']

Сгрузим расположение:

In [19]:
place1 = [p.text for p in soup1.find_all('div', {'class':'placement'})]
In [20]:
place1
Out[20]:
['Расположение в торговом зале: Уровень 2, зал № 12, секция 02, шкаф 19, полка 36',
 'Расположение в торговом зале: Уровень 2, зал № 12, секция 07, шкаф 70, полка 05',
 'Расположение в торговом зале: Уровень 2, зал № 12, секция 07, шкаф 70, полка 05',
 'Расположение в торговом зале: Уровень 2, зал № 12, секция 07, шкаф 70, полка 05',
 'Расположение в торговом зале: Уровень 2, зал № 12, секция 07, шкаф 70, полка 05',
 'Расположение в торговом зале: Уровень 2, зал № 12, секция 07, шкаф 70, полка 04',
 'Расположение в торговом зале: Уровень 2, зал № 12, секция 07, шкаф 70, полка 05',
 'Расположение в торговом зале: Уровень 2, зал № 12, секция 07, шкаф 70, полка 05',
 'Расположение в торговом зале: Уровень 2, зал № 12, секция 07, шкаф 70, полка 05',
 'Расположение в торговом зале: Уровень 2, зал № 12, секция 07, шкаф 70, полка 05']

И, конечно, цену:

In [21]:
price1 = [p.text for p in soup1.find_all('div', {'class':'title_data price'})]
In [22]:
price1
Out[22]:
['Цена: 409,00 руб.',
 'Цена: 279,00 руб.',
 'Цена: 269,00 руб.',
 'Цена: 189,00 руб.',
 'Цена: 269,00 руб.',
 'Цена: 739,00 руб.',
 'Цена: 269,00 руб.',
 'Цена: 149,00 руб.',
 'Цена: 269,00 руб.',
 'Цена: 209,00 руб.']

Осталось пройтись по всем страницам, которые были выданы в результате поиска. Для примера перейдем на страницу 2 и на этом остановимся.

In [23]:
next_p = br.find_element_by_css_selector('.next_page')
In [24]:
next_p.click()

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

In [25]:
page2 = br.page_source
soup2 = BeautifulSoup(page2, 'lxml')
books2 = [b.text for b in soup2.find_all('a', {'class':'name'})]
instore2 = [s.text for s in soup2.find_all('div', {'class':'title_data green'})]
instore2 = [i.strip() for i in instore2] 
place2 = [p.text for p in soup2.find_all('div', {'class':'placement'})]
price2 = [p.text for p in soup2.find_all('div', {'class':'title_data price'})]

Расширим списки результатов с первой страницы данными, полученными со второй страницы, используя метод .extend().

In [26]:
books1.extend(books2)
instore1.extend(instore2)
place1.extend(place2)
price1.extend(price2)

Осталось импортировать библиотеку pandas и создать датафрейм.

In [27]:
import pandas as pd

Для разнообразия создадим датафрейм не из списка списков, а из словаря. Ключами словаря будут названия столбцов в таблице, а значениями – списки с сохраненной информацией (названия книг, цены и проч.).

In [28]:
df = pd.DataFrame({'books': books1, 'in_store': instore1, 
                   'placement': place1, 'price': price1})
In [29]:
df.head()
Out[29]:
books in_store placement price
0 Четверо В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 409,00 руб.
1 iPhuck 10 В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 279,00 руб.
2 Чапаев и Пустота В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 269,00 руб.
3 Священная книга оборотня В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 189,00 руб.
4 S.N.U.F.F. В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 269,00 руб.

Давайте приведем столбец с ценой к числовому типу. Уберем слова Цена и руб, а потом сконвертируем строки в числа с плавающей точкой. Напишем функцию get_price(),

In [30]:
def get_price(price):
    book_price = price.split(' ')[1]  # разобьем строку по пробелу и возьмем второй элемент
    book_price = book_price.replace(',', '.')  # заменим запятую на точку
    price_num = float(book_price)  # сконвертируем в float
    return price_num
In [31]:
# проверка
get_price(df.price[0])
Out[31]:
409.0

Всё отлично работает! Применим функцию к столбцу price и создадим новый столбец nprice.

In [32]:
df['nprice'] = df.price.apply(get_price)
In [33]:
df.head()
Out[33]:
books in_store placement price nprice
0 Четверо В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 409,00 руб. 409.0
1 iPhuck 10 В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 279,00 руб. 279.0
2 Чапаев и Пустота В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 269,00 руб. 269.0
3 Священная книга оборотня В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 189,00 руб. 189.0
4 S.N.U.F.F. В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 269,00 руб. 269.0

Теперь можем расположить книги по цене в порядке возрастания:

In [34]:
df.sort_values('nprice')
Out[34]:
books in_store placement price nprice
7 Числа В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 149,00 руб. 149.0
14 П 5 В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 159,00 руб. 159.0
10 Жизнь насекомых В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 159,00 руб. 159.0
13 Generation "П" В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 179,00 руб. 179.0
3 Священная книга оборотня В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 189,00 руб. 189.0
9 Бэтман Аполло В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 209,00 руб. 209.0
17 Бэтман Аполло В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 219,00 руб. 219.0
18 T В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 259,00 руб. 259.0
19 Любовь к трем цукербринам В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 259,00 руб. 259.0
6 Generation П В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 269,00 руб. 269.0
11 Жизнь насекомых В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 269,00 руб. 269.0
12 Омон Ра В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 269,00 руб. 269.0
4 S.N.U.F.F. В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 269,00 руб. 269.0
2 Чапаев и Пустота В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 269,00 руб. 269.0
8 Лампа Мафусаила, или Крайняя битва чекистов с ... В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 269,00 руб. 269.0
1 iPhuck 10 В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 279,00 руб. 279.0
16 Generation "П" В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 389,00 руб. 389.0
0 Четверо В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 409,00 руб. 409.0
15 Чапаев и Пустота В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 519,00 руб. 519.0
5 Лампа Мафусаила, или Крайняя битва чекистов с ... В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 739,00 руб. 739.0

Выберем какую-нибудь книгу:

In [36]:
df[df.books == "Чапаев и Пустота"]
Out[36]:
books in_store placement price nprice
2 Чапаев и Пустота В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 269,00 руб. 269.0
15 Чапаев и Пустота В наличии Расположение в торговом зале: Уровень 2, зал №... Цена: 519,00 руб. 519.0

И сохраним всю таблицу в csv-файл:

In [37]:
df.to_csv("books.csv")