Python является высокоуровневым языком. Он не имеет конкретного назначения, так что подходит для решения любых задач, в том числе для написания парсеров. Синтаксис Питона минимальный, при этом язык поддерживает ООП, функциональные, императивные и остальные типы программирования. Благодаря этому парсинг на Python гораздо удобней.
Python VS PHP
Когда речь заходит о разработке парсеров, все вспоминают PHP и Python – именно данные языки работают на стороне сервера. Несмотря на то, что множество программ всё ещё работает на PHP, в настоящий момент гораздо выгоднее пользоваться Питоном. Сейчас Python применяется такими известными порталами, как YouTube, Instagram, Facebook, Google, Netflix.
Парсинг сайтов на Python имеет массу преимуществ:
- Язык Python имеет более ясную архитектуру. Он заставляет программиста писать более чистый и продуманный код. Здесь не требуется глубокого знания нюансов и особенностей, чтобы создавать элегантную кодировку.
- Язык Python проще в освоении. Он имеет подробную документацию и большое сообщество программистов, которые готовы помочь новичку. Но помимо прочего синтаксис языка интуитивно понятный и от этого простой.
- Библиотеки Python более универсальные. Их сравнительно немного, обычно в парсинге используются Scrapy, Beautiful-Soup, Selenium. Библиотеки решают все распространённые задачи. Фреймворки безопасны, быстры, стабильны и надёжны в работе.
- Простые, интуитивно понятные инструменты отладки. Наиболее широко распространён Python Debugger (PDB). Также плюсом является то, что для отладки требуется меньше времени.
- Управление пакетами в Python сделано гораздо удобнее. Их проще создавать, обмениваться ими. Это крайне важно, когда парсер должен обмениваться информацией с другими программами.
- В Python имеются лямбда-функции, которые могут создаваться и вызываться в любом месте. Другими словами, для этого не требуется привязка к идентификатору.
- Кроме того, Python очень универсален. Он годится для построения нейросетей, обработки фото и видео, а также для любых других задач парсинга.
Установка среды разработки Python
В первую очередь необходимо установить среду разработки. Для этого нужно зайти на официальный сайт Питона: https://www.python.org/, где перейти по вкладке «Download» и скачать последнюю версию языка. Установка стандартная и не требует особых действий, исключение составляет процедура для Windows, где надо поставить галочку у строки «Add Python 3,5 to PATH».
После установки для проверки работоспособности Питона нужно на Windows открыть командную строку («Пуск» — «командная строка»), а на остальных ОС – терминал. И ввести простейшую фразу: print (‘Hello’), после чего отобразится слово «Hello».
Разумеется, через терминал или командную строку программировать неудобно. Для нормальной работы надо всё делать через файлы. Для чего нужно создать пустой документ через любой текстовый редактор или даже блокнот с обязательным форматом .py (например, index.py). Здесь можно писать любой код, в том числе тот, что указан в примере выше.
Для проверки кода надо пользоваться терминалом или командной строкой. Далее надо выйти из среды разработки Питона, для чего написать строку: quit(). Затем нужно открыть созданный файл, для чего вбить команду: Air-Georgij:~ GeorgiyDudar$ cd "C:/Python/new"
.
Впрочем, если нет желания устанавливать программы, то можно воспользоваться онлайн-компилятором: https://www.tutorialspoint.com/execute_python_online.php
Библиотеки для парсинга
Для парсинга в Python имеется несколько библиотек, у которых есть свои преимущества и недостатки. Каждая из них обладает уникальным ядром и механизмом работы.
SCRAPY
Scrapy – библиотека с открытым кодом, созданная для парсинга различных сайтов. Она отличается крайне большой производительностью. Важно отметить, что Scrapy сформирована на базе Twisted – асинхронной сетевой системы, что резко повышает стабильность и скорость отправки запросов.
Главные уникальные характеристики Scrapy:
- Scrapy обладает встроенными функциями для получения информации из тегов HTML и CSS стилей с применением языка запросов XPath.
- Данная библиотека является кроссплатформенной. Другими словами, она одинаково работает на Linux, Mac, Windows, IOS, Android и других операционных системах.
- Scrapy просто расширяется и дополняется.
- Он намного быстрее других фреймворков. Его скорость при извлечении информации с сайтов в 20 раз выше, нежели у прочих инструментов.
- Он использует гораздо меньше ресурсов процессора, постоянной и оперативной памяти.
- С его помощью можно писать стабильные, многофункциональные и гибкие программы.
- Имеется большое сообщество разработчиков, которые перевели всю документацию и могут ответить на вопросы новичков.
Beautiful-Soup
Надо отметить, что Beautiful-Soup, является действительно отличным инструментом для написания парсеров из-за широкого функционала. С его помощью вебмастер может моментально скачать информацию с любой страницы. Фреймворк поможет получить данные из документов формата XML, HTML. Впрочем, есть минус – Beautiful-Soup не способен закончить процедуру сам. Он нуждается в некоторых модулях для функционирования.
В частности нужна установка фреймворка для отсылки запроса на веб-портал, ведь Beautiful-Soup не способен отправить его. Для выхода из этой ситуации необходимо воспользоваться одним из распространённых фреймворков: Requests, Lxml или Urlib2. Данные библиотеки сделают запрос к сайту. Наиболее часто применяется Requests, Lxml. После отправки информации HTML и XML на персональный компьютер желательно использовать программу для анализа скаченных данных.
Достоинства Beautiful-Soup:
- Он прост в изучении. В частности, для получения ссылок со страницы потребуется всего несколько строчек кода:
from bs4 import BeautifulSoup
# подключаем библиотеку Beautiful-Soup
import requests
# подключаем фреймворк Requests
page = requests.get("http://required-site.ru/page")
# Передаём GET-запрос, результат пишем в page
variable = BeautifulSoup(page.text, "html.parser")
# Ключевая строка, скачивающая весь html-код
for result in variable.find_all("a"):
# перебираем циклом все теги <а>
print(result.get("href"))
# извлекаем и распечатываем атрибуты href=»»
В указанном только что примере в функции Beautiful-Soup()
первым параметром передаётся страница, а затем указывается команда html.parser, которая будет разбирать содержимое.
- Он имеет отличную подробную документацию, с помощью которой можно легко изучить библиотеку.
- Он обладает внушительной технической поддержкой сообщества. Так что для решения проблем не придётся долго искать ответы в интернете.
Selenium
Нужно понимать, что Selenium создан для автоматизации тестирования онлайн-приложений. Он даёт возможность программисту писать на нескольких распространённых языках: PYTHON, C#, JavaScript, JAVA, RUBY и прочих (также с использованием JSON).
Ниже приведён пример автоматизации и имитации работы браузера:
from selenium import webdriver
# подключение библиотеки Selenium
from selenium.webdriver.common.keys import Keys
# подключение класса Keys, чтобы было взаимодействие с кнопками клавиатуры (Shift, Space, F1, ALT)
driver = webdriver.Chrome()
# строка формирует элемент класса Chrome
driver.get("http://www.python.org")
# метод загружает страницу
assert "Python" in driver.title
# метод проверяет наличие слова «Python» в заголовке. Если его нет, то программа прекращает свою работу (это своего рода проверка)
element = driver.find_element_by_name("x")
# метод находит тег по атрибуту name=»»
element.send_keys("pycon")
# передаём нажатия кнопок
element.send_keys(Keys.RETURN)
# получение результата
assert "Google" in driver.title
# проверка результата на содержание слова «Google»
driver.close()
# закрыть окно браузера
Из указанного кода можно подвести итог, что использование Selenium крайне удобно для новичков веб-программирования, так как можно просто написать парсер. Именно поэтому он так известен в среде разработчиков. Главным образом Selenium применяется для ускорения тестирования сайтов. Библиотека требуется для создания парсеров-пауков.
Какую библиотеку выбрать
Теперь можно подвести итоги и определиться с выбором библиотеки.
Гибкость
Архитектура Scrapy запрограммирована так, чтобы было удобно корректировать промежуточное программное обеспечение и добавлять свои функции. С помощью такого нюанса можно сделать приложение более гибким. Также с помощью Scrapy одна программа легко внедряется в другой проект. Для крупных проектов – идеальный выбор для программирования. Он предоставит прокси, конвейер информации.
Если приложение небольшое, не является высокоуровневым проектом, то можно выбрать Beautiful-Soup для решения задачи. С его помощью можно сделать код гибким. Если человек только изучает программирование и разрабатывает парсер, то Beautiful-Soup – идеальный вариант.
Selenium тоже имеет хорошую гибкость, особенно если речь идёт о работе с JavaScript на сайте. Но объём информации должен быть небольшим.
Производительность
Scrapy – наиболее скоростная из всех библиотек.
Beautiful-Soup сравнительно медленно решает отдельную задачу, но за счёт многопоточности легко обходит данную проблему. Но вебмастеру надо владеть этой технологией.
Selenium тоже быстро обрабатывает данные, но не так молниеносно, как Scrapy.
Экосистема
Scrapy обладает отличной экосистемой, библиотека позволяет применять прокси для автоматизации процессов. Поэтому фреймворк желательно использовать для крупных проектов. Приложение может выполнять запросы с различных прокси-адресов.
Beautiful-Soup – у библиотеки масса зависимостей. Фактически фреймворк не может работать самостоятельно. Это серьёзный минус.
Selenium – имеет достойную экосистему, но недостаток в том, что в базовом функционале нельзя применять прокси.
Создание парсера на Python – задача и инструменты
Воспользуемся для примера сайтом КиноПоиск. Например, для анализа поведения какого-то пользователя, его оценок, периодов активности, изменений вкусов ручная работа не подходит, так как нередко разбирать приходится до 1000 рецензий и более. КиноПоиск, как и большинство других сайтов, не обладает публичным API, так что прекрасно годится для обучающего парсинга.
Для анализа необходимо выгрузить информацию:
- наименование фильма (русское или иностранное);
- время и дата оценки;
- количество баллов.
Решение задачи состоит из двух этапов:
- Выгрузить и сохранить HTML.
- Перевести данные в удобный формат.
Дальнейший этап – обработка полученной информации. Но в рамках данной статьи эта стадия рассматриваться не будет.
Для создания HTTP-запросов имеется много Python библиотек. Здесь будет рассмотрен пример парсера на Beatiful-Soup, Lxml и Requests. Будут применяться и регулярные выражения, но в ограниченном количестве, чтобы не усложнять код для новичков. Beautiful-Soup применяет Lxml в роли внутренней подпрограммы для ускорения, что сильно упрощает работу с библиотеками.
Загрузка данных
Сначала следует выгрузить страницу целиком по URL и записать в документ на компьютере:
import requests
# подключаем библиотеку
myId = 47589
# указываем id пользователя
address = "http://www.kinopoisk.ru/user/%s/votes/list/ord/date/page/2/#list" % (myId)
# после процента указывается кортеж, значение которого вставляется вместо символов «%s»
page = requests.get(address)
# передаём GET-запрос на страницу, результат пишем в page
with open("test.html", "w") as doc:
# инструкция with поможет обработать исключения (и закроет файл по завершении работы)
doc.write(page.text.encode("cp1251"))
# метод write изменит кодировку файла
Однако если посмотреть на результат, то можно сразу обнаружить, что появилась проблема: портал принял парсер за робота и отказал в доступе к информации. Стандартное сообщение КиноПоиска: «От IP-адреса было много запросов. Система безопасности решила, что действует робот и ограничила доступ».
Для разрешения ситуации надо выяснить, как функционирует браузер.
Для этого (у Google) нужно кликнуть правой клавишей мыши и в появившемся списке нажать пункт «Посмотреть код» (аналоги есть и у других браузеров). Откроется окно, где справа будет блок с полезной информацией. В нём нужно найти вверху ссылку «Network». При клике откроется окошко, где можно увидеть, какие запросы выполняет браузер. Обычно требуемый запрос является самым длительным (как вариант – первый или последний).
Accept | text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 |
Accept-Encoding | gzip, deflate |
Accept-Language | ru-RU,ru;q=0.8,en=US;q=0.5,en;q=0.3 |
Connection | keep-alive |
Cookie | … |
Host | www.kinopoisk.ru |
User-Agent | Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:45.0) Gecko/20100101 Firefox/45.0 |
Оказывается, проблема в том, что браузер отправляет в заголовках различные данные. В первую очередь нужно отправлять User-Agent.
Для разрешения задачи надо переслать заголовки:
head = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:45.0) Gecko/20100101 Firefox/45.0"
}
page = requests.get(address, head = head)
В этом случае сервер передаст запрашиваемые данные.
Далее нужно выяснить, сколько страниц парсить. Выход из ситуации простой – необходимо с помощью цикла обрабатывать страницы, каждый раз увеличивая её номер на 1, пока на ней располагается блок с нужным классом (<div class = "profileFilmsList">)
.
Теперь код будет выглядеть так:
import requests
# подключаем библиотеку
s = requests.Session()
# устанавливаем сессию
s.headers.update({
# отправляем заголовки
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:45.0) Gecko/20100101 Firefox/45.0"
})
def load_user_data(myId, webpage, session):
address = "http://www.kinopoisk.ru/user/%s/votes/list/ord/date/page/%s/#list" % (myId, webpage)
result = session.get(address)
return result.text
def contain_movies_data(text):
variable = BeautifulSoup(text)
filmList = variable.find("div", {"class": "profileFilmsList"})
return filmList is not None
# загрузка файлов
webpage = 1
while True:
information = load_user_data(myId, webpage, s)
if contain_movies_data(information):
with open("./page_%s.html" % (webpage), "w") as doc:
doc.write(information.encode("cp1251"))
webpage += 1
else:
break
Парсинг данных
После получения информации её можно разобрать и проанализировать. Для примера будет использован язык запросов XPath (отлично подходит для xml и xhtml).
Вся таблица с баллами содержится в теге <div class = "profileFilmsList">
. Для получения этого блока потребуется такой код:
from bs4 import BeautifulSoup
# подключение библиотеки Beautiful-Soup
from lxml import html
# подключение фреймворка lxml# Beatiful-Soup
variable = BeautifulSoup(text)
filmList = variable.find("div", {"class": "profileFilmsList"})
# поиск тега с классом profileFilmsList# Lxml
wood = html.fromstring(text)
# получение кода html в виде текста
filmListLxml = wood.xpath('//div[@class = "profileFilmsList"]')[0]
# поиск тега с тем же классом
Каждая запись с кино представлена следующим html:
<div class="item even">
<div class="num">9</div>
<div class="info">
<div class="nameRus">
<a href="/film/775276" data-propup-info="enabled">Зверополис (2016)</a>
<!— Тут нужны цифры в атрибуте href и название фильма —>
</div>
<div class="nameEng">Zootopia</div>
<div class="rating">
<b>8.646</b>
<!— Собственно сама оценка —>
<span class="text-grey">(46 488)</span>
<span class="text-grey">108 мин.</span>
</div>
</div>
<div class="date">04.03.2016, 11:54</div>
<!— Дата —>… <!— Далее есть ещё код, но он уже не нужен —>
</div>
Получить русское название кино и ссылку на него можно с помощью кода:
# Beatiful-Soup
movieLink = item.find("div', {"class": "nameRus"}).find("a").get("href")
# получаем ссылки
movieDesc = item.find("div", {"class": "nameRus"}).find("a").text
# извлекаем содержимое тега a# Lxml
movieLink = item_lxml.xpath('.//div[@class = "nameRus"]/a/@href')[0]
# выбираем ссылки
movieDesc = item_lxml.xpath('.//div[@class = "nameRus"]/a/text()')[0]
# ищем содержимое тега a
Чтобы узнать, что было получено в результате парсинга, в Beautiful-Soup можно использовать стандартную функцию распечатки данных:
print item
В Lxml можно применить функцию tostring() для модуля etree:
from lxml import etree
# подключаем модуль etree
print etree.tostring(item_lxml)
# печать результата
Целиком парсинг КиноПоиска выглядит следующим образом:
def readFile(fileName):
with open(fileName) as inputFile:
text = inputFile.read()
return text
def parse_bs(fileName):
results = []
text = readFile(fileName)
variableBS = BeautifulSoup(text)
filmList = filmList = variableBS.find("div", {"class": "profileFilmsList"})
itemBS = filmList.find_all("div", {"class": ["item", "item even"]})
for item in itemBS:
# получение идентификаторов фильмов
movieLink = item.find("div", {"class": "nameRus"}).find("a").get("href")
movieDesc = item.find("div", {"class": "nameRus"}).find("a").text
movieId = re.findall("\d+", movieLink)[0]
# получение английских названий
nameEng = item.find("div", {"class": "nameEng"}).text
#получение даты и времени
watchDatetime = item.find("div", {"class": "date"}).text
dateWatched, timeWatched = re.match("(\d{2}\.\d{2}\.\d{4}), (\d{2}:\d{2})", watchDatetime).groups()
# получение рейтинга от пользователя
userRating = item.find("div", {"class": "vote"}).text
if userRating:
userRating = int(userRating)
results.append({
"movieId": movieId,
"nameEng": nameEng,
"dateWatched": dateWatched,
"timeWatched": timeWatched,
"userRating": userRating,
"movieDesc": movieDesc
})
return results
def parse_lxml(fileName):
results = []
text = readFile(fileName)
wood = html.fromstring(text)
filmListLxml = wood.xpath('//div[@class = "profileFilmsList"]')[0]
itemsLxml = filmListLxml.xpath('//div[@class = "item even" or @class = "item"]')
for item_lxml in itemsLxml:
# получение идентификаторов фильмов
movieLink = item_lxml.xpath('.//div[@class = "nameRus"]/a/@href')[0]
movieDesc = item_lxml.xpath('.//div[@class = "nameRus"]/a/text()')[0]
movieId = re.findall("\d+", movieLink)[0]
# получение английских названий
nameEng = item_lxml.xpath('.//div[@class = "nameEng"]/text()')[0]
#получение даты и времени
watchDatetime = item_lxml.xpath('.//div[@class = "date"]/text()')[0]
dateWatched, timeWatched = re.match("(\d{2}\.\d{2}\.\d{4}), (\d{2}:\d{2})", watchDatetime).groups()
# получение рейтинга от пользователя
userRating = item_lxml.xpath('.//div[@class = "vote"]/text()')
if userRating:
userRating = int(userRating [0])
results.append({
"movieId": movieId,
"nameEng": nameEng,
"dateWatched": dateWatched,
"timeWatched": timeWatched,
"userRating": userRating,
"movieDesc": movieDesc
})
return results