Andrey Prokopenko's Blog

Yesod: Tips & Tricks

Общие слова

Решил посмотреть, чем занимаются нынче фрилансеры. И наткнулся на типовую задачу, на которую ранее как-то не обращал внимания: парсеры. Требовалось реализовать парсер via HTTP, встроенный в веб-сервер со страницей отображения прогресса парсинга. Т.е. тут и трёхзвенка, и парсер. Объёмы данные большие, интересно. Думаю, почему бы и нет…

Выбор технологий

  • Поскольку ранее уже проходил туториал по Yesod, то решил чуть углубиться в него и посмотреть поближе, что же это такое, и с чем это едят. Тем более, придётся всё равно иметь дело с базой. Сервер выбран.
  • А с парсингом не работал. Посмотрел в Hackage: тут и http-client, wreq, и hxt, и parsec, на котором можно написать свой парсер чего угодно. Но я остановился на tagsoup.

Итак, технологии выбраны, поехали.

Tagsoup

Вкратце, всё просто.

  • Импортируем модуль и упрощаем себе жизнь для получения страницы.
  • Заводим требуемый нам тип данных: объект с нужными нам атрибутами.
  • Задаём шаблон строки, по которой мы будем дробить страницу на юниты с объектами.
  • Открываем URL и парсим страницу в список Tag.
  • Группируем теги по шаблону forParsing.
  • Используя repl, определяем местоположение атрибутов, устанавливаем тип атрибута: статический или динамический. Если статический, то находим индекс элемента в списке. Если динамический, то находим шаблоны начала и конца тегов, содержащих искомый атрибут. По итогам исследования пишем преобразование тегов в объект типа Company:

И теперь, чтобы не плодить лишних параметров, можно переписать код без создания групп:

С парсером разобрались. Идём дальше.

Взаимодействие с PostgreSQL

  • Чтобы не вводить искусственные типы данных руками, как выше это было сделано с Company, можно использовать config/models, тогда компания свернётся в такой код. При компиляции этот код раскрывается в нечто куда большее, подробнее можно узнать здесь:
Company
    name Text
    url Text
    response_url Text Maybe
    city Text
    phone Text Maybe
    good_responses Text Maybe
    options Text Maybe
    UniqueCompany url
    deriving Show
  • Выборка данных из базы по собственному запросу осуществляется так:
count' = do
  let sql = "select count(1) cntr from category" :: Text
  [(Single cntr)] :: [(Single Int)] <- runDB $ rawSql sql []
  return cntr
  • Если колонок несколько, то тогда следует вернуть список “простых значений” ([Single a]) и “избавиться от простоты” (unSingle). Важно, чтобы между топовыми select и from не было лишних знаков вопроса, иначе запрос скомпилируется, но в рантайме будут досадные ошибки. Такова прелесть Raw SQL. Ну а если существует необходимость конкатенировать результат запроса со строкой, содержащей символ знака вопроса (модификация url в базе), то тогда можно обернуть результат конкатенации в подзапрос, вопрос исчезнет из топа.

  • Причина тому в заложенной возможности выборки сущностей через два вопросительных знака:
  let sql= "select ??, ?? from category ca, company co where ca.id = co.category_id"
  objs :: [(Entity Category, Entity Company)] <- runDB $ rawSql sql []

Другие хитрости Yesod

  • Работая с обработчиками, следует держать во внимании, что вместо монады IO, тут используется (YesodPersist site, YesodPersistBackend site ~ SqlBackend) => HandlerT site IO. Для того, чтобы осуществлять действия с побочными эффектами, нужно использовать liftIO. Она смещает акцент из IO в нужную монаду. Парсинг в обработчике станет выглядеть так:
  • Для создания форков в Yesod нужно пойти обратным путём:
  • Для добавления статического контента в область видимости сервера нужно выполнить очистку пакета и пересобрать его.
cabal clean
yesod devel # cabal install

Posted on 2014-12-14 by agr . Powered by Hakyll. Inspired by Yann Esposito.