Рубрики
Без рубрики

Простой и практичный парсинг веб-страниц на Python

Автор оригинала: Arun Ravindran.

Этот пост вдохновлен отличным постом под названием Веб-парсинг 101 с помощью Python . Это отличное введение в парсинг веб-страниц на Python, но я заметил две проблемы с ним:

  1. Подбирать элементы было немного громоздко
  2. Можно было бы сделать проще

Если вы спросите меня, я бы написал такие скрипты парсинга с использованием интерактивного интерпретатора, такого как IPython , и с использованием более простого синтаксиса селектора CSS.

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

Проблема

Мы собираемся решить ту же проблему, о которой говорилось в первой ссылке. Нам интересно узнать победителей конкурса Chicago Reader’s Best of 2011 . К сожалению, на странице Chicago Reader показаны только пять разделов. Каждый из этих разделов содержит категории наград, например «Лучший винтажный магазин» в категории «Товары и услуги». На каждой из страниц категорий наград вы найдете победителя и занявшего второе место. Наша миссия – собрать имена победителей и призеров каждой награды и представить их в виде одного простого списка.

Установка

Запустите python, IPython , bpython или любой другой интерактивный интерпретатор python по вашему выбору. В оставшейся части статьи я буду использовать IPython.

Обычной отправной точкой для большинства потребностей веб-синтаксического анализа является получение проанализированной веб-страницы из URL-адреса. Итак, давайте определим нашу функцию get_page следующим образом:

from urllib2 import urlopen
from lxml.html import fromstring

def get_page(url):
    html  urlopen(url).read()
    dom  fromstring(html)
    dom.make_links_absolute(url)
    return dom

В функции get_page первая строка загружает страницу с помощью функции urlopen и возвращает ее содержимое в виде строки. Вторая строка использует lxml для анализа строки и возвращает объектное представление страницы.

Поскольку большинство ссылок на странице html будут относительными, мы преобразуем их в абсолютные ссылки. Например, ссылка типа /about будет преобразована в http://www.chicagoreader.com/about . Это упрощает вызов функции get_page для таких URL позже.

Выбор элементов страницы

Затем нам нужно вызвать эту функцию и выбрать части документа. Но перед этим нам нужно знать, какие детали нам нужны.

Я предпочитаю использовать синтаксис селектора CSS по сравнению с XPath для выбора узлов. Например, путь к одному и тому же элементу в этих двух разных синтаксисах показан ниже:

Путь CSS : html body # BestOf.BestOfGuide div # gridClamp div # gridMain div # gridFrame div # gridMainColumn div # StoryLayout.MainColumn div # storyBody.page1 strong p a

XPath :/html/body/div [3]/div [2]/div/div [2]/div [5]/div/strong/p [2]/a.

Пути CSS могут быть длиннее, но их легче понять. Что еще более важно, их легче построить.

В Firefox вы можете использовать Firebug, чтобы щелкнуть правой кнопкой мыши любой элемент страницы, чтобы получить его путь CSS.

Поиск путей CSS в Firefox с помощью Firebug

В Chrome вы не сможете скопировать путь CSS, но вы можете увидеть его в строке состояния внизу.

Поиск путей CSS в Chrome

Селектор Гаджет

Эти пути CSS очень длинные, и я бы не рекомендовал их использовать. Они слишком специфичны и привязаны к общей структуре документа, которая может измениться. Более того, вы можете сократить путь к селектору CSS, не влияя на его специфичность.

Я рекомендую использовать букмарклет под названием Selector Gadget , который элегантно решает обе эти проблемы. Он также работает во всех браузерах.

Сначала перетащите букмарклет на панель инструментов закладок. Откройте любую страницу и щелкните гаджет выбора, чтобы активировать его. Теперь щелкните элемент, для которого вы хотите использовать селектор CSS. После того, как вы щелкните элемент, он станет желтым, и в гаджете появится селектор CSS. Многие другие элементы, соответствующие этому селектору, также будут показаны желтым цветом.

Иногда также подбираются элементы, которые вам не нужны. Чтобы устранить это, щелкните элемент, которому НЕ нужно соответствовать. Продолжайте этот процесс выбора и отклонения, пока не получите именно тот селектор CSS, который вам нужен. Для получения инструкций нажмите кнопку «Справка».

Использование iPython

Запустите интерпретатор iPython и вставьте строки кода, которые мы видели ранее:

$ ipython
Python 2.7.3 (default, Sep 26 2012, 21:51:14) 
Type "copyright", "credits" or "license" for more information.

IPython 0.13.1.rc2 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: from urllib2 import urlopen

In [2]: from lxml.html import fromstring

In [3]: def get_page(url):
   ...:         html  urlopen(url).read()
   ...:         dom  fromstring(html)
   ...:         dom.make_links_absolute(url)
   ...:         return dom
   ...: 

In [4]: dom  get_page(

В последней строке вы извлекаете начальную страницу, которую хотите очистить, и назначаете ее проанализированный объект DOM в dom .

В следующих трех командах функция cssselect вызывается с помощью селектора CSS “#storyBody p a” для получения всех ссылок на разделы. Результат – список. Поскольку нам нужны только URL-адреса, мы запускаем анализ списка по списку ссылок.

In [5]: dom.cssselect("#storyBody p a")
Out[5]: 
[,
 ,
 ,
 ,
 ,
 ]

In [6]: [link.attrib['href'] for link in _]
Out[6]: 
[

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

Поиск всех категорий

Затем нам нужно получить и проанализировать каждую страницу раздела. Это легко сделать с пониманием следующего списка. Вторая команда – это вложенный список с двумя циклами. Как и раньше, нам нужны только URL-адреса. Всего их 389, каждый представляет свою награду.

In [13]: doms  [get_page(secn) for secn in secns]

In [14]: [link.attrib['href'] for dom in doms for link in dom.cssselect("#storyBody a")]
Out[14]: 

In [15]: categs_

In [16]: len(categs)
Out[16]: 389

Поиск титула, победителя и занявшего второе место

Затем откройте любой URL-адрес из списка category и найдите CSS-селекторы для интересующих нас элементов. Этими тремя пунктами являются: звание награды, победитель и занявший второе место. Поскольку функция cssselect возвращает список (даже если найдено только одно совпадение), нам нужно извлечь 0-й элемент. Другая функция, называемая text_content , применяется для получения именно той информации, которую мы ищем.

In [17]: categ  categs[0]

In [18]: domget_page(categ)

In [19]: dom.cssselect("h1.headline")[0].text_content()
Out[19]: u'Best longtime cause worth fighting for\xa0'

In [20]: dom.cssselect(".boc1")[0].text_content()
Out[20]: 'Public school reform'

In [21]: dom.cssselect(".boc2")[0].text_content()
Out[21]: 'Recycling in Chicago'

Именованные кортежи – идеальные структуры данных для очищенного ввода

Ранее кортежи использовались для хранения отброшенных результатов. Они используют меньше памяти по сравнению со словарями. В последнее время в Python появилась поддержка именованных кортежей, которые намного проще в использовании и столь же эффективны с точки зрения памяти.

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


In [22]: from collections import namedtuple

In [23]: Award  namedtuple("Award", "title, winner, runnerup")

In [24]: awards  []

In [25]: for categ in categs[:2]:
             domget_page(categ)
             title  dom.cssselect("h1.headline")[0].text_content()
             winner  dom.cssselect(".boc1")[0].text_content()
             runnerup  dom.cssselect(".boc2")[0].text_content()
             a  Award(titletitle, winnerwinner, runneruprunnerup)
             awards.append(a)

In [36]: awards
Out[36]: 
[Award(titleu'Best longtime cause worth fighting for\xa0', winner'Public school reform', runnerup'Recycling in Chicago'),
 Award(titleu'Best historic building\xa0', winner'Chicago Cultural Center', runnerup'The Rookery')]

Сила интерактивности

Для одноразовых скриптов очистки часто лучше использовать только интерпретатор Python. Я попытался объяснить вам, как решить проблему очистки набора веб-страниц. Надеюсь, вы нашли это полезным!