Парсинг на Pуthon. Как собрать архив Голубятен

Статья описывает разработку скрипта на языке Python. Скрипт выполняет парсинг HTML-кода, составление списка материалов сайта, скачивания статей и предварительную очистку текста статьи от «посторонних» элементов. Используется библиотеки urllib (получение HTML-страниц), lxml (парсинг HTML-кода, удаление элементов и сохранение «очищенной» статьи), re (работа с регулярными выражениями), configobj (чтение файлов конфигурации).

Для написания скрипта достаточно базовых знаний языка Python, навыков программирования и отладки кода.

В статье даются пояснения по применению библиотек на примере составления списка публикаций С.М. Голубицкого, приведена ссылка на работающий скрипт.

Предисловие или немного лирики


Едва ли ошибусь, сказав, что многие хабражители знакомы с неуемным творчеством Сергея Голубицкого. За без малого 15 лет околокомпьютерной публицистики имярек выдал на гора 433 статьи в безвременно почившей в бозе бумажной Компьтерре и более 300 Голубятен на портале Компьютерра-онлайн. И это не считая аналитических изысканий о героях забугорных гешефтов в Бизнес-журнале, приоткрытии завесы над тайнами творчества в “Домашнем Компьютере”, статей в “Русском журнале”, “D`” и проч. и проч. Претендующие на полноту обзоры жизнетворчества интересующиеся найдут по приведенным выше ссылкам.

В прошлом году начал работу авторский проект “Старый голубятник и его друзья”, который задумывался (и стал) в частности постоянно пополняемым архивом публикаций самого автора и площадкой для ведения культурповидлианских дискуссий. Как человек, неравнодушный к виртуозно вскрываемым автором темам сетевой жизни, социальной мифологии и саморазвития, а так же охочий до качественного досужего чтения, однажды стал завсегдатаем посиделок на Голубятне и я. По мере сил стараюсь не только держать проект в поле зрения, но и как-то участвовать в его развитии.

Подвизаясь на ниве корректорских правок статей, переносимых в архив с портала Компьютерра-онлайн, первым делом я решил составить опись всех Голубятен.

Постановка задачи


Итак, задача, на примере которой мы будем рассматривать парсинг сайтов на Python, состояла в следующем:
  • Составить список всех Голубятен, размещенных на Компьютерре-онлайн. Список должен включать название статьи, дату публикации, информацию о содержимом статьи (только текст, наличие картинок, видео), Синопсис, ссылку на источник.
  • Дополнить список материалами, опубликованными в бумажной Компьютерре, найти дубликаты.
  • Дополнить список материалами из архива сайта Internettrading.net
  • Загрузит ь список статей, уже опубликованных на портале “Старого голубятника”
  • Скачать статьи на локальный диск для дальнейшей обработки, по возможности автоматически очистив текст от ненужных элементов.

Подбор инструментария


В части языка программирования мой выбор сразу же и однозначно пал на Python. Не только потому, что несколько лет назад изучал его на досуге (а потом какое-то время использовал шелл Active Python как продвинутый калькулятор), но и за обилие библиотек, примеров исходного кода и простоту написания и отладки скриптов. Не в последнюю очередь интересовали и перспективы дальнейшего использования полученных навыков для решения очередных задач: интеграции с API Google Docs, автоматизации обработки текстов и т.д.

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

Итак, выбор инструментов начался определения подходящей версии Python. Первоначально пробовал использовать Python 3.2, но в процессе экспериментов остановился на Python 2.7, т.к. некоторые примеры на “тройке” не пошли.

Для упрощения установки дополнительных библиотек и пакетов использовал setuptools — средство для загрузки, сборки и инсталляции пакетов.

Дополнительно были установлены библиотеки:
  • urllib — получение HTML-страниц сайтов;
  • lxml — библиотека для парсинга XML и HTML кода;
  • configobj — библиотека для чтения файлов конфигурации.

В качестве подручных средств использовались:
  • Notepad++ — текстовый редактор с подсветкой синтаксиса:
  • FireBug — плагин браузера FireFox, позволяющий просматривать исходный код HTML-страниц
  • FirePath — плагин браузера FireFox для анализа и тестирования XPath:
  • Встроенный Python GUI для отладки кода.

Неоценимую помощь оказали статьи и обсуждения на Хабре:

А так же мануалы, примеры и документации:

И, конечно же, книга Язык программирования Python

Обзор решения


Задача включает в себя четыре однотипных процедуры загрузки материалов с четырех разных сайтов. На каждом из них есть одна или несколько страниц со списком статей и ссылками на материал. Чтобы не тратить много времени на формализацию и унификацию процедуры, был написан базовый скрипт, на основе которого под каждый сайт дорабатывался собственный скрипт с учетом особенностей структуры списка материалов и состава HTML страниц. Так, парсинг материалов на Internettrading.net, где HTML видимо формировался вручную, потребовал множество дополнительных проверок и сценариев разбора страницы, в то время как формируемые CMS Drupal (“Старый голубятник и его друзья”) и Bitrix (“Компьютерра-онлайн”, архивы бумажной Компьютерры) страницы содержали минимум особенностей.

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

Список статей выводится в разделе “Протограф”. Здесь есть название, ссылка на статью и синопсис. Список разбит на несколько страниц. Перейти к следующей странице можно изменяя в цикле параметр в строке адреса (?page=n), но мне показалось изящнее доставать ссылку на следующую страницу из текста HTML.

На странице статьи есть дата публикации в формате DD Месяц YYYY, собственное ее текст и указание на источник в подписи.

Для работы с различными типами данных было создано два объекта: MaterialList(object) — список статей (содержит метод парсинга отдельной страницы списка _ParseList и метод получения URL следующей страницы _GetNextPage, хранит список материалов и их идентификаторов) и Material(object) — собственно статья(содержит метод формирования идентификатора на основе даты _InitID, метод парсинга страницы _ParsePage, метод определения источника публикации _GetSection и атрибуты статьи, такие как дата публикации, тип материала и проч.)

Дополнительно определены функции работы с элементами дерева документа:
  • get_text(item, path) — получение текста элемента по пути path в документе item
  • get_value(item) — получение текста ноды в документе item
  • get_value_path(item, path) — получение текста ноды в документе item по пути path
  • get_attr_path(item, path, attr) — получение аттрибута элемента по пути path в документе item

И функция get_month_by_name(month), возвращающая номер месяца по его названию для разбора даты.

Основной код (процедура main()) содержит загрузку конфигурации из файла, проход по страницам списка материалов с загрузкой содержимого в память и дальнейшее сохранение в файлы как самого списка (в формате CSV), так и текстов статей (в HTML, имя файла формируется на основе идентификатора материала).

В файле конфигурации хранятся URL начальной страницы списка материалов, все пути XPath для страниц материалов и списка статей, имена файлов и путь к каталогу для сохранения статей.

Детали реализации


В этой части я рассмотрю основные моменты кода, так или иначе вызвавшие затруднений или курение мануалов.

Для упрощения отладки путей внутри документов и облегчения чтения кода все XPath вынесены в отдельный конфигурационный файл. Для работы с файлом конфигурации вполне подошла библиотека configobj. Файл конфигурации имеет следующую структуру:
# Comment
[ Section_1 ]
   # Comment
   variable_1 = value_1
   # Comment
   variable_2 = value_2
   [[Subsection_1]]
      variable_3 = value_3
   [[Subsection_2]]
[ Section_2 ]


Вложенность подсекций может быть произвольной, допускаются комментарии к секциям и конкретным переменным. Пример работы с файлом конфигурации:

from configobj import ConfigObj

# Загрузить файл конфигурации
cfg = ConfigObj('sgolub-list.ini')
# Получить значение параметра url из секции sgolub
url = cfg['sgolub']['url']


Загрузка html-страницы реализована с помощью библиотеки urllib. С помощью lxml преобразуем документ в дерево и фиксим относительные ссылки:

import urllib
from lxml.html import fromstring

# Загрузка html-документа в строку
html = urllib.urlopen(url).read();

# Преобразование документа к типу lxml.html.HtmlElement
page = fromstring(html)

# Преобразование относительных ссылок внутри документа в абсолютные
page.make_links_absolute(url)


При разборе списка публикаций нам потребуется перебрать в цикле все элементы списка. Для этого подойдет метод lxml.html.HtmlElement.findall(path). Например:

for item in page.findall(path):
   url = get_attr_path(item,cfg['sgolub']['list']['xpath_link'],'href')


Сейчас самое время сделать замечание по поводу плагина FirePath и его использования для построения XPath. Действительно, как уже писали на Хабре, FirePath дает пути, которые отличаются от путей в lxml. Незначительно, но разница есть. Довольно скоро эти отличия удалось выявить и в дальнейшем использовать FirePath с поправками, например, тег tbody заменять на * (самая частая проблема). В то же время, скорректированные таким образом пути можно проверять в FirePath, что существенно ускоряет дело.

В то время как page.findall(path) возвращает список элементов, для получения отдельного элемента существует метод find(path). Например:

content = page.find(cfg['sgolub']['doc']['xpath_content'])

Методы find и findall работают только с простыми путями, не содержащими логических выражений в условиях, например:

xpath_blocks = './/*[@id='main-region']/div/div/div/table/*/tr/td'
xpath_nextpage = './/*[@id='main-region']/div/div/div/ul/li[@class="pager-next"]/a[@href]'


Для того, чтобы использовать более сложные условия, например, вида
xpath_purifytext = './/*[@id="fin" or @class="info"]'
потребуется уже метод xpath(path), который возвращает список элементов. Вот пример кода, вычищающий из дерева выбранные элементы (как работает эта магия я не понимаю до сих пор, но элементы действительно удаляются из дерева):

from lxml.html import tostring

for item in page.xpath(cfg['computerra']['doc']['xpath_purifytext']):
item.drop_tree()
text=tostring(page,encoding='cp1251')

В этом фрагменте также используется метод lxml.html.tostring, сохраняющий дерево (уже без лишних элементов!) в строку в заданной кодировке.

В заключение приведу два примера работы с библиотекой регулярных выражений re. Первый пример реализует разбор даты в формате «DD Месяц YYYY»:

import re
import datetime

# content имеет тип lxml.html.HtmlElement
# и является частью страницы, содержащей непосредственно статью
datestr=get_text(content,cfg['sgolub']['doc']['xpath_date'])
if len(datestr)>0:
   datesplit=re.split('\s+',datestr,0,re.U)
   self.id = self._InitID(list,datesplit[2].zfill(4)+str(get_month_by_name(datesplit[1])).zfill(2)+datesplit[0].zfill(2))
   self.date = datetime.date(int(datesplit[2]),get_month_by_name(datesplit[1]),int(datesplit[0]))
else:
   self.id = self._InitID(list,list.lastid[0:8])
   self.date = datetime.date(1970,1,1)


Используется функция re.split(regexp,string,start,options), которая формирует список из элементов строки, разделенных по определенной маске (в данном случае, по пробелу). Опция re.U позволяет работать со строками, содержащими русские символы в юникоде. Функция zfill(n) добивает строку нулями слева до указанного количества символов.

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

def _GetSection(item, path):
   # рекомендуется компилировать регулярные выражения
   reinfo = re.compile(r'.*«(?P<gsource>.*)».*',re.LOCALE)
   for info in item.xpath(path):
      src=get_value_path(info,'.').strip('\n').strip().encode('cp1251')
      if src.startswith('Впервые опубликовано'):
         parser = self.reinfo.search(src)
         if parser is not None:
            if parser.group('gsource')=='Бизнес-журнале':
               return 'Бизнес-журнал'
            else:
               return parser.group('gsource')
         break
      return ''


В приведенном примере показан код функции _GetSection(item, path), которой передается поддерево, содержащее указание на источник публикации, например «Впервые опубликовано в Бизнес-журнале». Обратите внимание на фрагмент регулярного выражения ?P<gsource>. Помещенный в скобки он позволяет определять именованные группы в строке и обращаться к ним с помощью parser.group('gsource'). Опция re.LOCALE аналогична re.U.

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

Заключение


Результатом применения технологии стал архив статей с четырех сайтов на жестком диске и списки всех публикаций Голубятен. Списки были вручную загружены в таблицу Google Docs, статьи из архива также переносятся вручную для правки в документы Google.

В планах решение задач:
  • Написание службы, автоматически отслеживающей новые публикации
  • Интеграция с API Google Docs для автоматического внесения новых публикаций в список
  • Преобразование архивных статей из HTML к XML-формату с автоматической коррекцией части ошибок и загрузкой в Google Docs


P.S. Большое спасибо всем за комментарии, поддержку и конструктивную критику. Надеюсь, что большинство замечаний станут мне полезны в будущем после внимательного изучения.
Метки:
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 41
  • +11
    Код совершенно не в стиле питона. Рекомендую заглянуть в PEP 8. Также небесполезно будет взять какой-нибудь букварь и почитать про возможности языка (словари, условный оператор, elif, format и так далее).
    • 0
      Спасибо за совет, особенно, за elif.
    • +1
      _GetSection не статичен, а self в аргументах заменяется на item, но при этом используется self в тексте.
      if parser is not None: = if parser:
      • –1
        Чем вызван выбор lxml?
        • 0
          А вы бы что посоветовали?

          Как по мне — одна из наиболее функциональных и удобных библиотек для работы с XML. Пожалуй даже самая удобная из всех, с которыми я работал.
          • 0
            Html5lib как более универсальную обертку. Хождение по ссылкам с помощью Scrapy. Более pythonic.
            • 0
              Не знаю чем оно более универсальное, судя по коду (быстро пролистал) функционал там только базовый. Тем не менее, как я смотрю, html5lib в lxml уже интегрирована lxml.de/html5parser.html

              Насчет Scrapy поддерживаю.
              • 0
                Там кроме этого синтаксиса поддерживается синтаксис BeautifulSup и другие вкусности. Не понимаю логику минусующих.
                • 0
                  За 10 минут сложилось такое впечатление: Документации нет. Что умеет не понятно. Написана целиком на Pyhton — скорее всего медленнее и памяти больше надо. Умеет ли XPath — не понятно. Умеет ли CSS селекторы — не ясно. Чем универсальнее — тоже не совсем понимаю. Может пример небольшой дадите?

                  Например спарсить топик и его метаданные с хабра:
                  import urllib # urllib для краткости примера. Обычно Scrapy или celery.
                  from lxml import etree
                  topic={}
                  tree=etree.HTML(urllib.urlopen('http://habrahabr.ru/blogs/python/121815/').read())
                  _topic_block=tree.cssselect("div.hentry")[0]
                  topic['title']=_topic_block.xpath("h2/span[@class='topic']/text()")[0]
                  topic['body']=etree.tounicode(_topic_block.xpath("div[@class='content']")[0])
                  topic['tags']=_topic_block.xpath("ul[@class='tags']/li/a/text()")
                  topic['author']=_topic_block.cssselect("a.nickname")[0].xpath("span/text()")[0]
                  
                  При этом я обычно использую пару своих функций — расширений для XPath чтобы проще @class обрабатывать и не использую CSS селекторы. Тут уж совсем для краткости сделал.

                  По поводу минуса — это я сгоряча. А карму плюсанул для равновесия.
            • 0
              И html5lib который на гуглокоде.
              • 0
                хм… а до этого мы о каком html5lib говорили?
                • 0
                  То что вы приводили — часть lxml, тот который на гуглокоде отдельный проект на сколько я понимаю.
                  • 0
                    Ну, я написал про lxml.de/html5parser.html просто чтоб показать что lxml и html5 умеет парсить.
                    А так да, я про гуглокодовскую говорил.
                    • 0
                      Познав разницу вы остались с lxml? Он на мой взгляд сильно низкоуровневый.
                      • 0
                        Зато lxml почти не глючит и очень быстро работает. Когда нужно парсить в сутки от сотен тысяч страниц и более это выливается в банальную экономию денег.
            • 0
              Прежде всего, решением задачи. lxml дал самый быстрый отклик в плане работающего примера. Тут наверное нужно пояснить. До этого я писал полноценный скрипт на python только один раз (не считая всяких «hello world»), сам язык знаю плохо и потратил на решение задачи минимум времени (написание и отладку кода, копание в документации, сравнение различных инструментов). То есть то, что не заработало сразу или требовало длительного изучения, я отбрасывал сразу.

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

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

              Что касается lxml, то он оказался простым, эффективным в плане гибкости применения, хорошо и с разных сторон документированным инструментом.
            • 0
              Используйте pastebin-ы
            • +1
              Статья очень похожа на курсовую работу.
              • +4
                Да ладно, я такое девушке постоянно пишу, когда какой-нибудь цитатник Конфуция нужно выкачать. Курсовая работа это все же посерьезнее, хотя бы на несколько недель интенсивной реализации. Не принижайте вы уж так технические вызу.
                • 0
                  Курсовая работа — это все таки образцово-показательное выступление. Я же только брался показать, что всего за один день, со скромными познаниями в пайтоне, еще меньшим опытом и без углубления в документации и мучительный выбор, что ставить — пробелы или табуляцию (я, кстати, сразу выбрал табуляцию, просто потому что быстрее им ошибок меньше в отступах) — вполне реально решить конкретную задачу, буде такая появится.
              • 0
                «Чем вызван выбор lxml?» — тут вопросов нет, говорю Вам как Профессиональный Парсер. все правипльно. аналигов по скорости и адеквате нет!, но чтение статьи закончилось на первой строчке — urllib — получение HTML-страниц сайтов;, на этом Все. урл либ хорошо но при ПАРСИНГЕ соверщенно не подходит. я не говорю про проблемы с кодировкой и получением контенста, выы даже ИП не сможете нормлаьно поменять.

                pycurl намного адекватный выбор.
                • +1
                  э. а где urllib имеет дело с кодировкой? O_o
                  urllib — нативно на пейтоне, не требуется собирать pycurl (что может представлять проблему для win машин). Единственное, смена IP чуть геморойна.
                  • НЛО прилетело и опубликовало эту надпись здесь
                  • +4
                    Scrapy удобно соединяет все, что нужно для web-краулинга, последующего парсинга html и сохранения результатов. Советую взглянуть в его сторону, и не изобретать велосипедов.
                    • 0
                      Поддерживаю. Хотя у меня впечатление что автор не осилит (не в обиду сказано).
                      • 0
                        Пытаюсь осилить. Однако, тут вот какая штука. Scrapy — мощный фреймворк. Он облегчает решение стандартных задач, но и устанавливает рамки. И он не подходит для первого знакомства с парсингом для таких новичков как я. Причины просты:
                        1. Требуется время на изучение, чтобы понять, чем именно он упрощает решение задач и как.
                        2. Заранее неизвестны неочевидные ограничения, которые начнут всплывать на практике и уже после того, как затрачены усилия на освоение базы.
                        3. Количество различных примеров и разноплановой документации для простого инструмента в несопоставимое количество раз больше, чем для фреймворка.
                        4. Дополнительные библиотеки, необходимые для установки. Во-первых это масса возможностей сделать ошибку. Так, например, я пытался twisted поставить на Win с помощь easy-install и все вроде бы хорошо ставилось, вот только библиотека не нашлась. Может надо было просто пути прописать к каталогу scripts, а может действительно нужно ставить через msi, но процесс на время затормозился.
                        4. Дополнительные библиотеки. Неминуемы отсылки документации к возможностям библиотек и время на изучение особенностей их использования во фреймворке.

                        В частности, сейчас я знаю (благодаря доброму совету и чтению документации по Srapy), что смог бы переписать решение задачи с использованием фреймворка. Но в очереди следующие, для решения которых нужно выбирать инструменты с оглядкой на фреймворк. Как подвесить краулер сервисом windows? Возникнут ли какие-нибудь особенности, если нужно записать данные не в файл, а в документ через API Google Docs? Можно ли вызывать Srapy из кода, а не из командной строки?
                        Есть и другие смутные вопросы, ответы на которые есть смысл искать, погрузившись в Srapy по самые гланды.

                        В любом случае, Srapy попробую. Спасибо за наводку!
                        • +1
                          У Scrapy есть преимущество в том, что он обладает высокой поизводительностью (может качать десятки документов одновременно) и кучей готовых вспомогательных модулей, например для защиты от повторной загрузки уже загруженных страниц и зацикливания, поддержка Cookies и пр.
                          Есть модули для создания к нему веб-интерфейса. Есть встроенная telnet консоль (т.е. можно подключиться к запущенному и работающему пауку по telnet и выполнять внутри него любые команды Python прям во время работы). Но при этом нужно иметь в виду, что он работает асинхронно и для сохранения собранных данных в БД уже нужно изворачиваться.
                          Чтобы сохранить данные не в файл а в GoogleDocs тоже придется дополнительно потрудиться…
                          Вызвать Scrapy из кода можно, но эта возможность по-моему не документирована. Я как-то так его стартовал:
                          """ run as `python this_file.py <spider_name> <start_url>` """
                          import os
                          os.environ.setdefault('SCRAPY_SETTINGS_MODULE', 'forums.settings')
                          
                          import sys
                          from scrapy.core.queue import ExecutionQueue
                          from scrapy.core.manager import scrapymanager
                          from scrapy.spider import spiders
                          import scrapy_syslog_log as syslog_log
                          
                          def main():
                              syslog_log.patch()
                              scrapymanager.configure(control_reactor=True)
                              q=ExecutionQueue()
                              spider_name, url=get_spider_and_url()
                              spider=spiders.create(spider_name)
                              q.append_url(spider.getFetchUrl(url), spider)
                              scrapymanager.queue=q
                              scrapymanager.start()#this is blocking call
                              sys.stdout.write('OK')
                              sys.exit(0)
                          
                          
                          def get_spider_and_url():
                              if len(sys.argv)<3:
                                  sys.stdout.write('ERR')
                                  sys.exit('NOT ALL ARGS')
                          
                              return sys.argv[1:]
                          
                          if __name__=='__main__':
                              main()
                          Чтоб этот кусок кода написать, пришлось «погрузиться в Srapy по самые гланды».

                          Для GoogleDocs попробуйте официальную библиотеку от Google code.google.com/p/gdata-python-client/.
                          Вот примеры: code.google.com/p/gdata-python-client/source/browse/samples/docs/docs_example.py code.google.com/p/gdata-python-client/source/browse/samples/docs/resumable_upload_sample.py
                          Но со Scrapy ее будет не очень легко интегрировать. Разве что пул потоков организовать т.к. Scrapy неблокирующий а библиотека блокирующая.
                          • 0
                            Спасибо за пример! Утащил к себе в избранное.

                            Жаль, у меня пока нет задач, для которых действительно потребовалась бы вся мощь Scrapy (а было бы круто для приготовления холостяцкого ужина запустить промышленную установку по формированию пельменей — джастфофан).

                            Впрочем, думаю рано или поздно такие задачи появятся, и тогда советы придутся очень кстати.
                    • +1
                      Архив Голубятен в студию!
                      • +4
                        Ужас ужас.
                        Если используете lxml — используйте xpath
                        Почитайте про PEP8

                        Учитесь кодить на пайтоне, зачем здесь else?

                        if parser.group('gsource')=='Бизнес-журнале':
                        return 'Бизнес-журнал'
                        else:
                        return parser.group('gsource')


                        ну и т.д.
                        • +1
                          Попробуйте посмотреть еще scrapy
                          • НЛО прилетело и опубликовало эту надпись здесь
                          • +8
                            Хоть и не хочется обидеть, но… Похвально конечно, что боле менее разобрались и задачу решили, но статью такую лучше не показывать публично. И код тоже.
                            Вообще, как уже сказали, по стилю написания очень похоже на курсовую или какую-то именно студенческую работу. Типа 2 дня перед зачетом, быстренько прочитал первые 5 абзацев из каждого мануала и начал строчить статью с как можно большим наборов умных слов и растянутыми предложениями… Это уж очень в глаза бросается.

                            По коду:
                            * все-таки не понимаю зачем XPath вынесли в конфиг… SQL запросы в своих программах тоже в конфиг будете выносить?
                            * лесенки из if — это как-то очень подозрительно…
                            * вместо if len(src)==0 можно писать if not src: и т.п.
                            * писать if condition: do smth (if и тело подвыражения на одной строке) хоть и можно, но очень не рекомендуется
                            * строки кода длиннее 80 символов не рекомендуются. Если пишите цепочку функций, лучше переносите на след. строку, обернув в скобки:
                            m.synopsis = get_text(item,cfg['sgolub']['list']['xpath_synopsis']).strip('\n').replace('\n',u' ').replace(';',u'|').strip().encode('utf-8')
                            заменяем на
                            m.synopsis = (
                                get_text(item,cfg['sgolub']['list']['xpath_synopsis'])
                                .strip('\n')
                                .replace('\n',u' ')
                                .replace(';',u'|')
                                .strip()
                                .encode('utf-8'))

                            * генерировать CSV вручную — последнее дело. Есть специальный модуль, встроенный в Python docs.python.org/library/csv.html
                            * вместо
                            fdoc = open(fname, 'w')
                            fdoc.write(m.text)
                            fdoc.close()

                            пишем
                            with open(fname, 'w') as fdoc:
                                fdoc.write(m.text)
                            — после выхода из with или если случится Exception файл закроется автоматически. Файловые дескрипторы гарантированно не утекают
                            * self.mlist+=[m] заменяем на self.mlist.append(m)
                            * Хранить ВСЕ скачанные материалы в памяти (self.mlist) не рационально. Лучше записывать в файл сразу после распарсивания очередной статьи. yield вам в помощь. Хотя тут много уже переписывать придется.
                            * Везде по коду натыканы .encode('utf-8') .encode('cp1251'). Может названия кодировок в конфиг вынести? Да и внутри программы лучше юникодом пользоваться (в Python юникод != utf8).
                            * Склеивать строки сложением (+) считается дурным тоном. Но тут есть варианты.

                            Можно и дальше продолжать в принципе…
                            • 0
                              Можно и дальше продолжать в принципе…

                              Так вы продолжайте не стесняйтесь, между прочим очень интересно и автору подозреваю тоже, как вы говорите надо склеивать строки?
                              • 0
                                С помощью join (он работает быстрее, чем последовательность конкатенаций).
                                Но данном случае это неактуально, потому что склеивание строк активно используется только при создании CSV вручную, а для этого, как уже написали выше, есть специальный модуль.
                              • +1
                                Спасибо за развернутые и полезные комментарии. Для того, чтобы узнать многое из описанного, потребовалось бы кипу всего перечитать. Например, сам никогда бы не задался вопросом, есть ли специальный модуль для записи CSV-файлов!

                                С кодировками вышла самая большая маета, осмыслить проблему логически не получилось и пришлось подбирать методом тыка. Наверное как раз из-за того, что в Python юникод != utf8.

                                С выносом XPath в конфиг все просто (кстати, да SQL я бы тоже вынес). Говорят, что человек, начинающий изучать два иностранных языка, путает слова и конструкции и только с опытом происходит надежное отделение двух языковых полей. Так и здесь: ХPath запросы, помещенные прямо в текст, отвлекали от кода. Кроме того, оказалось удобнее отделить написание python-кода, и отладку XPath. Охотно признаю, что опыт в том и другом сделает такое разделение избыточным.

                                По хранению в памяти согласен. Но в данной задаче памяти требовалось немного, а отлаживать отдельно парсинг и запись в файл показалось удобнее. На других объемах, разумеется, пришлось бы память экономить.
                                • 0
                                  С кодировками проблемы у всех начинающих и многих продвинутых программистов. А юникод != utf8 практически везде, где есть юникод на уровне языка/фреймворка, просто далеко не все об этом задумываются и/или знают разницу.
                              • 0
                                >> Notepad++ — текстовый редактор с подсветкой синтаксиса:

                                SciTE(http://code.google.com/p/scite-ru/) лучше)
                              • 0
                                Еще раз спасибо всем за обсуждение, советы и замечания!

                                Узнал много нового и полезного, как из комментариев, так и благодаря им. Жаль, что не могу проголосовать, не ожидал, что время голосования истекает.

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