Содержание


Очаровательный Python

Собираем данные в Web с помощью mechanize и Beautiful Soup

Инструменты Python, которые облегчают извлечение и структурирование данных с Web-сайтов

Comments

Серия контента:

Этот контент является частью # из серии # статей: Очаровательный Python

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Очаровательный Python

Следите за выходом новых статей этой серии.

Для написания сценариев для взаимодействия с Web-сайтами достаточно стандартных модулей Python. Модули urllib и urllib2 в Python 2.x, а также подпакеты urllib.* в Python 3.0 позволяют довольно сносно извлекать данные с URL-адресов. Однако если вы хотите реализовать умеренно сложное взаимодействие с Web-сайтом, вам стоит воспользоваться библиотекой mechanize (которую можно загрузить по ссылке в разделе Ресурсы).

Одной из самых больших сложностей при автоматизации сбора данных в Web или какой-либо другой имитации взаимодействия пользователя с Web-сайтом является использование Web-сервером cookies для отслеживания пользовательских сеансов. Конечно, поскольку cookies являются частью заголовка HTTP пакета, они доступны при обращении к ресурсам с помощью urllib. Более того, стандартные модули Cookie (http.cookie в Python 3) и cookielib (http.cookiejar в Python 3) помогают работать с этими заголовками на более высоком уровне, чем просто обработка текста. Библиотека mechanize поднимает эту обработку на еще более высокий уровень абстракции и позволяет вашему сценарию или интерактивной оболочке Python действовать почти как настоящий Web-браузер.

На создание библиотеки mechanize для Python повлиял пакет WWW:Mechanize из Perl, который обладает похожими возможностями. Конечно же, как старый любитель Python, я считаю mechanize более надежной библиотекой, что можно отнести и к двум этим языкам в целом.

Близким другом mechanize является не менее полезная библиотека Beautiful Soup (которую можно загрузить по ссылке в разделе Ресурсы). Она отлично подходит для разбора «примерно корректной» HTML разметки, которую часто можно встретить на Web-страницах в реальной жизни. Не обязательно использовать Beautiful Soup в связке с mechanize, как и наоборот, но все же в большинстве случаев при работе с «настоящим Web» вы, скорее всего, будете использовать обе библиотеки.

Пример из реальной жизни

Я использовал mechanize в нескольких своих проектах. В последнем проекте мне нужно было собрать на одном популярном сайте список имен, удовлетворяющих некоторым критериям. Этот сайт предоставляет поисковые возможности, но не предлагает никакого официального API для выполнения поиска. Так как читатели могут догадаться, что именно я делал, я изменю код примеров, чтобы не раскрывать слишком много информации ни о сайте, ни о моем клиенте. Вообще говоря, код, который я представлю ниже, в значительной части можно использовать и для других подобных задач.

С какими инструментами начинать работу

В процессе разработки кода для сбора и анализа информации из Web мне было необычайно важно иметь возможность «потрогать» содержимое Web-страниц в интерактивном режиме, чтобы выяснить, что именно происходит на интересных мне Web-страницах. Обычно ими являлись несколько страниц сайта, которые либо динамически генерировались из запроса (а значит, имели структуру, подчиняющуюся некоторым шаблонам), либо были сгенерированы заранее в соответствие с каким-либо довольно жестким шаблоном.

Такие интерактивные эксперименты можно проводить, используя mechanize либо из оболочки Python, либо, еще лучше, из какой-нибудь расширенной оболочки, например IPython (см. ссылку в разделе Ресурсы). Работая в оболочке, можно запрашивать различные взаимосвязанные ресурсы, заполнять формы, хранить и изменять cookies сайта и т.д., и лишь потом приступать к написанию окончательного сценария, который будет выполнять нужную вам работу.

Однако, по моему мнению, большинство экспериментов по взаимодействию с Web-сайтами лучше производить с помощью настоящего современного Web-браузера. Имея перед глазами удобно отображенную страницу, можно гораздо быстрее понять, что происходит на данной странице или в форме. Однако отображенная в браузере страница дает не более чем половину информации. Наличие кода страницы может дать немного большее представление. Чтобы действительно понять, что стоит за определенной Web-страницей или что происходит при некоторой последовательности взаимодействий с Web-сервером, нужно нечто большее.

Чтобы добраться до нужных мне внутренностей сайта, я обычно использую Firebug (см. ссылку в разделе Ресурсы) или модули расширения Web Developer для Firefox (также можно использовать доступное в качестве опции меню Develop в последних версиях Safari, но это немного для другой аудитории). С помощью этих инструментов можно увидеть все поля формы, посмотреть пароли, изучить DOM-структуру страницы, взглянуть на код Javascript или выполнить его, отследить трафик Ajax и т.д. Рассказ о плюсах и минусах этих инструментов может занять отдельную статью, однако если вы занимаетесь программированием для Web, вам определенно стоит познакомиться с ними.

Какой бы инструмент для изучения нужного вам Web-сайта вы ни выбрали, весьма вероятно, что вы проведете намного больше времени за выяснением того, что именно делает сайт, чем за написанием необычайно компактного кода mechanize, который будет выполнять вашу задачу.

Сборщик результатов поиска

Для работы над проектом, о котором я упоминал ранее, я разделил свой сценарий, состоящий примерно из ста строк, на две функции:

  • Функцию извлечения всех страниц с нужными мне результатами
  • Функцию получения интересной мне информации из извлеченных страниц

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

Я мог сначала загрузить кучу страниц и сохранить их на диск, а потом извлечь нужную мне информацию из сохраненных файлов. Конечно, если в вашей задаче извлеченная информация используется для определения новых взаимодействий с Web в рамках того же сеанса, вам придется немного изменить последовательность этапов разработки.

Итак, давайте сначала взглянем на функцию fetch():

Листинг 1. Извлекаем содержимое web-страниц
import sys, time, os
from mechanize import Browser

LOGIN_URL = 'http://www.example.com/login'
USERNAME = 'DavidMertz'
PASSWORD = 'TheSpanishInquisition'
SEARCH_URL = 'http://www.example.com/search?'
FIXED_QUERY = 'food=spam&' 'utensil=spork&' 'date=the_future&'
VARIABLE_QUERY = ['actor=%s' % actor for actor in
        ('Graham Chapman',
         'John Cleese',
         'Terry Gilliam',
         'Eric Idle',
         'Terry Jones',
         'Michael Palin')]

def fetch():
    result_no = 0                 # количество файлов с результатами
    br = Browser()                # создаем браузер
    br.open(LOGIN_URL)            # открываем страницу входа на сайт
    br.select_form(name="login")  # находим форму для ввода имени и пароля
    br['username'] = USERNAME     # заполняем форму
    br['password'] = PASSWORD
    resp = br.submit()            # и нажимаем кнопку

    # Автоматическое перенаправление иногда не срабатывает, поэтому 
    # при необходимости переходим вручную
    if 'Redirecting' in br.title():
        resp = br.follow_link(text_regex='click here')

    # Обходим результаты поиска, сохраняя значения постоянных параметров
    for actor in in VARIABLE_QUERY:
        # Мне нравится видеть в консоли, что делает скрипт
        print >> sys.stderr, '***', actor
        # Выполняем запрос
        br.open(SEARCH_URL + FIXED_QUERY + actor)
        # Запрос выдает нам ссылки на страницы с нужными нам данными
        # но на странице имеются и ненужные ссылки, которые мы будем игнорировать
        nice_links = [l for l in br.links()
                        if 'good_path' in l.url
                        and 'credential' in l.url]
        if not nice_links:        # Возможно, что не окажется никаких результатов 
            break
        for link in nice_links:
            try:
                response = br.follow_link(link)
                # Пишем в консоль заголовок страницы, на которую мы переходим
                print >> sys.stderr, br.title()
                # инкрементируем номер в имени файла, открываем файл и записываем 
                # в него результат
                result_no += 1
                out = open(result_%04d' % result_no, 'w')
                print >> out, response.read()
                out.close()
            # Конечно, иногда могут случаться ошибки, мы будем их игнорировать
            except mechanize._response.httperror_seek_wrapper:
                print >> sys.stderr, "Response error (probably 404)"
            # Не будем напрягать сайт слишком частыми запросами
            time.sleep(1)

Проведя интерактивное исследование интересного мне сайта, я обнаружил, что запросы, которые я бы хотел сделать, имеют некоторые постоянные и переменные составляющие. Я просто соединил их в одном большом GET-запросе и получил страницу с результатами. Этот список результатов в свою очередь содержал ссылки на ресурсы, которые мне были нужны. После этого, я обошел эти ссылки (обернув действия в блок try/except, на случай, если что-то пойдет не так) и сохранил содержимое этих страниц.

Довольно просто, не правда ли? С помощью mechanize можно делать и более сложные вещи, но, тем не менее, данный пример иллюстрирует, какие широкие возможности предоставляет эта библиотека.

Обрабатываем результаты

К этому моменту работа с mechanize завершена. Теперь остается извлечь нужную нам информацию из той массы HTML-файлов, которую мы сохранили в блоке fetch(). Для данной задачи мне удалось четко разделить этапы сбора Web-страниц и извлечения из них данных, но понятно, что в других программах функции fetch() и process() могут взаимодействовать более тесно. Благодаря Beautiful Soup обработка данных выполняется еще проще, чем их извлечение.

Мы будем искать на сохраненных ранее Web-страницах нужную в нам информацию и помещать ее в CSV-файл.

Листинг 2. Собираем по кусочкам нужную нам информацию с помощью Beautiful Soup
from glob import glob
from BeautifulSoup import BeautifulSoup

def process():
    print "!MOVIE,DIRECTOR,KEY_GRIP,THE_MOOSE"
    for fname in glob('result_*'):
        # Помещаем этот HTML файл в "суп"
        soup = BeautifulSoup(open(fname))

        # Пытаемся найти нужные нам поля. При неудаче по умолчанию выставляется 
        # значение  UNKNOWN
        try:
            movie = soup.findAll('span', {'class':'movie_title'})[1].contents[0]
        except IndexError:
            fname = "UNKNOWN"

        try:
            director = soup.findAll('div', {'class':'director'})[1].contents[0]
        except IndexError:
            lname = "UNKNOWN"

        try:
            # Возможно мы найдем более одного ассистента, в таком случае выбираем первого
            grips = soup.findAll('p', {'id':'grip'})[0]
            grips = " ".join(grips.split())   # Убираем лишние пробелы
        except IndexError:
            title = "UNKNOWN"

        try:
            # Ищем кое-что, спрятанное в <meta>-тегах HTML
            moose = soup.findAll('meta', {'name':'shibboleth'})[0]['content']
        except IndexError:
            moose = "UNKNOWN"

        print '"%s","%s","%s","%s"' % (movie, director, grips, moose)

Глядя на код функции process() можно получить первое впечатление о Beautiful Soup. За детальной информацией о модуле обращайтесь к его документации, но типичный пример работы с ним хорошо представлен в этом фрагменте кода. Большая часть кода состоит из вызовов .findAll() для поиска по возможно не вполне корректно сформированному HTML-документу. Местами встречаются обращения к атрибутам .parent, nextSibling и previousSibling, напоминающие навигацию по DOM-структуре документа. Все это похоже на браузер, работающий в режиме обратной совместимости. “Суп”, получаемый с помощью Beautiful Soup, является не совсем деревом разбора. Метафорично его, скорее, можно сравнить с мешком овощей, кое-что из которого можно использовать для супа.

Заключение

Старые упрямцы, подобные мне, и даже, возможно, некоторые молодые читатели помнят, какое удовольствие раньше доставляло создание сценариев с помощью TCL Expect (и его аналогjd, написанными на Python и многих других языках). Автоматизация взаимодействия с оболочками, в том числе удаленными, такими как telnet, ftp, ssh и другие, делается относительно просто, так как все, что происходит во время сеанса, отображается на экране. Взаимодействие с Web реализуется сложнее, так как здесь информация разделена между заголовками и телом сообщения, а взаимосвязанные ресурсы связаны друг с другом с помощью гиперссылок, фреймов, Ajax и т.д. Конечно, в принципе можно использовать, например, wget для получения каждого байта, который может предоставить Web-сервер, и затем обработать данные с помощью сценариев Expect, которые будут организованы так же, как при работе с другими сетевыми протоколами.

На практике немногие программисты настолько преданны таким старомодным решениям, как wget + Expect. Работать с mechanize также комфортно, как со старыми добрыми сценариями Expect, а писать с его помощью настолько же просто, если не еще проще. Объект Browser() имеет такие команды, как .select_form(), .submit() и .follow_link(), которые являются самым простым и очевидным способом сказать "посмотри сюда и сделай вот это", и в то же время он обеспечивает все удобства в обработке сеансов и состояния, которых только можно пожелать от инфраструктуры автоматизации работы с Web.


Ресурсы для скачивания


Похожие темы

  • Ознакомьтесь с оригиналом статьи: "Charming Python: Easy Web data collection with mechanize and Beautiful Soup" (EN, developerWorks, ноябрь 2009 г.).
  • Загрузите mechanize и его документацию. (EN)
  • Загрузите Beautiful Soup и его документацию. (EN)
  • IPython - это замечательная разновидность интерактивной оболочки Python, которая умеет делать очень интересные вещи. Например, она помогает выполнять параллельные вычисления. Мне же она нравится удобной работой в интерактивном режиме: подсветкой кода, улучшенной историей команд, завершением по клавише tab, возможностями создания макросов и улучшенной интерактивной системой помощи. (EN)
  • Firebug устанавливается непосредственно из меню Инструменты/Дополнения браузера Firefox 3.0+. Этот модуль предоставляет множество инструментов редактирования, отладки и мониторинга, полезных в Web-разработке. Также можно установить модуль Web Developer extension, который аналогичным образом добавляет в браузер меню и панель с различными инструментами Web-разработчика.(EN)
  • В статье "Создание "Web-пауков" в Linux" (developerWorks, ноябрь 2006 г.) обсуждаются "Web-пауки" (spiders и scrapers) и рассказывается, как можно сделать несколько простых "пауков" с помощью Ruby.(EN)
  • В статье "Debug and tune applications on the fly with Firebug" (developerWorks, май 2008 г., EN) рассказывается, как работать с Firebug, чтобы уметь намного больше, чем просто посмотреть исходный код страницы Web или Ajax-приложения.
  • В статье "Использование Net-SNMP и IPython" (developerWorks, ноябрь 2007 г.) подробно рассказывается о том, как с помощью IPython и Net-SNMP организовать активное, основанное на Python управление сетью.

Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=604895
ArticleTitle=Очаровательный Python: Собираем данные в Web с помощью mechanize и Beautiful Soup
publish-date=12232010