Парсер на python

Парсер сайтов может использоваться для различных целей: получать самые свежие новости сайтов, которые не поддерживают RSS; получить данные с сайта конкурента; анализировать тексты в интернете; автоматизировать рутинные действия для сайтов, которые не предоставляют API.

Исходя из целей парсера, логично предположить, что он должен уметь скачивать из интернета html страницы и извлекать необходимые данные. Так для скачивания html страницы, есть несколько способов: можно работать напрямую с сокетами и выполнять соответствующие HTTP запросы самим или воспользоваться существующими Python пакетами. Например: httplib, pycurl, pycurl2, urllib, urllib2, requsts.

Извлечение данных из html так же можно написать полностью самостоятельно, используя регулярные выражения. Но есть более удобный способ при котором Python возьмёт на себя всю сложную работу. Это использование соответствующих пактов, вот некоторые из них: BeautifulSoup, lxml, leaf (обёртка над lxml).

Для удобного разбора и поиска интересующих данных в XML, существует специальный язык запросов XPath (XML Path Language) (http://www.w3.org/TR/xpath/). Но так как html является подмножеством XML, то XPath подходит и для работы с html. XML/HTML имеет древовидную структуру, в которой таги -- узлы этого дерева. А XPath предоставляет простой способ для поиска тагов по их взаимному расположению в таком дереве и/или по значению атрибутов тагов. XPath - не единственный способ разбора html, есть вариант с использованием CSS selection http://www.w3.org/TR/selectors/.

Для начала, попробуем реализовать простой парсер для сайта "http://aftershock.su/", с которого будем получать заголовки новостей с первой страницы.

Откройте данный сайт в браузере и посмотрите исходный код отображаемой страницы. Что бы получить заголовок новости, нам нужно знать какой таг содержит её и какова общая  отличительная особенность для таких тагов. Это необходимо, что бы XPath выражение, которое мы будем использовать, подходило для всех заголовков новостей на данной странице.  Если такой таг не имеет отличительной особенности, посмотрите на родительский таг и т.д. Для нашего случая, заголовки новостей хранятся в виде '<span class="aft-postheadericon"><a ...> Заголовок новости</a></span>' и подходящим XPath выражением будет './/span[@class = "aft-postheadericon"]/a/text()'.

Совет: проверить правильность XPath выражения можно прямо в браузере, для этого установите  FirePath плагин для браузера FireFox.

Разберём данное XPath выражение:

'.//' - указывает на то, что для оставшейся части выражения поиск должен производится по всему дереву,

'span[@class = "aft-postheadericon"]' — указывает, что необходимо найти таг 'span' у которого атрибут  class имеет значение "aft-postheadericon" (это та самая отличительная особенность всех тагов которые содержат в дочерних элементах заголовок новости), символ '@' - обязательно ставится перед именем атрибута.

'/a' -  указывает на то что нас интересует не сам таг 'span', а его дочерний таг 'a',

'/text()' – это XPath функция, которая возвращает текст обрамлённый тагом указанным ранее.

 

Теперь, когда у нас есть XPath выражение, мы можем перейти непосредственно к написанию парсера. Для разбора html данных воспользуемся пакетом lxml. Этот же пакет обеспечит и скачивание страницы для парсинга:

 

from lxml import html

 

def get_urls_from_page(url):

result = []

path = './/span[@class = "aft-postheadericon"]/a/text()'

 

doc = html.parse(url)

values = doc.xpath(path)

 

for i in values:

result.append(i)

return result

 

headers = get_urls_from_page('http://aftershock.su/')

for h in headers:

print h

 

Вот так выглядит простейший парсер, который способен скачивать html страницы методом GET и искать в результате данные используя XPath выражение.

 

Совет: pip - это удобный инструмент для установки Python пакетов в систему

pip install lxml

 

Теперь, объясним некоторые моменты:

from lxml import html – так как пакет lxml содержит в себе несколько модулей, среди которых нас интересует только модуль для работы с html то импортируем только его;

doc = html.parse(url) – выполняет HTTP GET запрос на скачивание страницы по адресу url, её последующий разбор и построил соответствующей древовидной иерархии объектов;

values = doc.xpath(path) –- в данной строке выполняется поиск всех данных удовлетворяющих XPath  выражению.

 

В случае, если нам понадобится авторизация, поддержка HTTPS протокола или передача параметров через метод PUT, мы будем вынуждены использовать другой способ для получения страницы, не возлагая этой непосильной обязанности на lxml. Для этого будем использовать пакет requests.

 

Код для получения html страницы методом GET будет выглядеть следующим образом:


import requests

 

def get_page(url):

page = None

r = requests.get(url)

if r.status_code == 200:

page = r.text

return page

 

r = requests.get(url) – выполняет HTTP GET запрос страницы по адресу url;

r.status_code – содержит код ответа web сервера на запрос, код возврата 200 указывает что запрос был обработан без ошибок;

r.text – содержит данные возвражаемые сервером в виде строки.

 

Теперь код обработки страницы будет выглядеть следующим образом:

 



from lxml import html

 

def parse_page(page):

result = []

if not page:

return result

 

path = './/span[@class = "aft-postheadericon"]/a/text()'

 

doc = html.document_fromstring(page)

value = doc.xpath(path)

 

for i in value:

result.append(i)

return result

 

page = get_page()

headers = parse_page(page)

 

for h in headers:

print h

 

Как видите, изменения коснулись в основном кода разбора html перед поиском данных:

doc = html.document_fromstring(page)  – аналогично html.parse(url), за исключением того, что нет необходимости скачивать html страницу

 

В результате, мы получили простой однопотопный парсер использующий XPath для разбора html.

 

 

Автор: Kapitan.Stan


2 комментария

  1. Мой текст для многостраничного парсинга:

    from lxml import html

    def get_urls_from_page(url):
    result = []
    path = './/*[@class = "span10"]/div[1]/text()'
    for pag in range(1, 10):
    doc = html.parse(url + 'pag')
    values = doc.xpath(path)
    for i in values:
    result.append(i)
    return result

    headers = get_urls_from_page('http://msl.ua/ru/megalot/archive/page/')
    for h in headers:
    print(h)

    Парсится в цикле только первая страница. Подскажите, пожалуйста, что не правильно?

    • Ну вы же только одну страницу и парсите. Результат правильный.