вторник, 9 ноября 2010 г.

Ценителям поэзии

Есть в рунете замечательный сайт - www.stihi.ru, на котором помимо графоманов довольно много и интересных, хороших авторов. Если какой-то автор вам особенно понравился, то все его стихи выкачать и аккуратно сложить по папочкам (в соответствии с классификацией автора) можно с помощью такого кода на Common Lisp:
(defun load-poem (url dir &key verbose)
(html:with-parse-html (page url :encoding "cp1251")
(let* ((index (xpath:find-single-node page "/html/body/index"))
(title (xpath:find-string index "h1")))
(when verbose
(format t "Load ~A (~A)~%" url title))
(with-open-file (out (make-pathname :directory (pathname-directory dir)
:name title
:type "txt")
:direction :output :if-exists :supersede)
(iter (for br in (xpath:find-list index "div[@class='text']/br"))
(write-line (string-trim #(#\Newline)
(xtree:text-content (xtree:prev-sibling br)))
out))))))

(defun load-all-poems (url target &key verbose (recursive t))
(ensure-directories-exist target)
(html:with-parse-html (page url :encoding "cp1251")
(iter (for node in (xpath:find-list page "//ul/li/a"))
(load-poem (puri:merge-uris (puri:parse-uri (xtree:attribute-value node "href"))
url)
target
:verbose verbose))
(when recursive
(iter (for node in (xpath:find-list page "//div[@id='bookheader']/a"))
(let ((title (xtree:text-content node)))
(when verbose
(format t "~%Load book ~A~%" title))
(load-from-stihiru (puri:merge-uris (puri:parse-uri (xtree:attribute-value node "href"))
url)
(merge-pathnames (make-pathname :directory (list :relative title))
target)
:recursive nil
:verbose verbose))))))
Выделение содержательной части из html-страниц весьма тривиально и реализуется за счёт использования языка запросов XPath. Теперь скачать, например, все стихи моей жены можно таким вызовом:
(load-all-poems #U"http://www.stihi.ru/avtor/mari_mishon"
#P"/var/kubart/poems/"
При написании данного кода я обнаружил, что libxml2 не может правильно определить кодировку страниц стихиры, так что пришлось несколько доработать cl-libxml2, добавив возможность явно указывать кодировку html-страниц.

11 комментариев:

  1. Я так понимаю, ты автор cl-libxml2? А что побудило к написанию собственного биндинга вместо использования pure-lisp cxml/chtml? Я так понимаю, какие-то их недостатки?

    Я лично использовал chtml для массового парсинга кучи html-ок (задача стояла выдрать из них тексты для последующей обработки), и в целом с моей задачей он вполне справлялся. При работе, правда, обнаружилась пара мелких багов, проявляющихся довольно редко (один - при парсинге документов, содержащих очень много (тысячи) entity references, второй - если документ кончался внутри тега - типа <br в конце документа). Баги были успешно пофикшены и патчи отправлены (и приняты).

    ОтветитьУдалить
  2. > Я так понимаю, ты автор cl-libxml2?

    Да.

    > А что побудило к написанию собственного биндинга

    Во-первых, мне была нужна поддержка XSLT, которой на момент создания биндинга ещё не было. Впрочем, она и сейчас не в самом лучшем состоянии с точки зрения производительности.

    Во-вторых, для меня работа с XML была очень даже критической и мне нужна была библиотека, которая гарантированно покроет все мои нужды. Я не мог полагаться на cxml, но знал что libxml2 сможет обеспечить меня любым необходимым сервисом.

    В-третьих, CXML размазана по целому ряду библиотек, ей определённо не хватает консолидации. И многим компонентам не хватает практической обкатки, интерфейс той же Plexippus XPath не совсем удобен.

    ОтветитьУдалить
  3. archimag, а что с cl-libxml2 на гуглокоде?

    asdf пишет-
    sbcl gzip: stdin: not in gzip format

    ОтветитьУдалить
  4. @pscat

    Всё нормально. asdf такого писать не может, только asdf-install. Но asdf-install никогда нормально не работало. А там не gzip, а bz2. Используй quicklisp.

    ОтветитьУдалить
  5. Где можно найти изменения, которые вы внесли в cl-libxml2?

    ОтветитьУдалить
  6. @orivej

    Ну, дык, https://github.com/archimag/cl-libxml2

    ОтветитьУдалить
  7. Спасибо, не заметил почему-то, что 0.3.4 вышла раньше.

    Я новичок в Common Lisp, я правильно пониманию, что для включения имён из iterate в пространство имён этого кода необходимо сделать:

    (require 'cl-libxml2)
    (defpackage stihiru
    (:use cl)
    (:use iterate)
    (:export load-all-poems))
    (in-package :stihiru)

    и затем вызывать #'stihiru:load-all-poems, или есть менее изолирующий, но более короткий способ? (Но не (in-package :iterate), потому что так можно включить только один пакет.)

    Да, функция load-from-stihiru оказывается не определена.

    ОтветитьУдалить
  8. Нашёл ответ, это use-package. Я замечаю, что раньше имена пакетов писали, начиная с ', затем с :, теперь у вас и в других местах встречаю '#:. С чем связан выбор последнего?

    ОтветитьУдалить
  9. @orivej

    Лучше такие вопросы на lisper.ru задавать :) Здесь я не всегда имею возможности ответить, тем более, если вопрос не по теме поста )

    'symbol - просто символ
    :symbol - keyworld, символ из пакета keyword
    '#:symbol - внепакетный символ, не замусоривает пакеты ненужными символами

    ОтветитьУдалить
  10. А как вам такая разработка?

    ОтветитьУдалить
  11. http://www.alex-y28.narod.ru/pilesos.html
    забыл линк

    ОтветитьУдалить