
У меня много статей в закладках. Многие из них я добавил, чтобы прочитать позже. Эта статья не про то, почему так произошло и как с этим бороться, а про то, как выбрать статью для удаления чтения. Давайте найдем самые лучшие статьи. Критериями могут быть, например, рейтинг, просмотры и т.д. И красиво оформим в виде HTML-файла.
Пример закладок @alizar: 800+ статей.
Кратко
$ pip install habra-favorites$ habra_favorites <username> # имя пользователя (не обязательно своё)$ firefox favorites.html # google-chrome favorites.html# Hint: на названия столбцов можно нажимать, чтобы переупорядочить.
Подробнее
Исходный код проекта доступен GitHub и залит на PyPI.
Для парсинга используется библиотека Scrapy. У нее очень хорошая документация, но позволю себе немного повториться и дополнить некоторые моменты.
Scrapy
В Scrapy мне нравится структурированность. Скелет проекта чем-то похож на структуру проекта Django, поэтому при описании будут возникать некоторые сопоставления.
Item
Первым делом объявим класс Item. Item — это описание элементов, которые мы будет сохранять. Если говорить в терминах Django, то это Model, не связанная с БД (хотя это можно добавить).
Вернемся к критериям сортировки. Это как раз и будут поля нашего Item: числовые данные, которые мы можем получить у статьи. Не забудем название с адресом и автора:
class FavoriteItem(Item): id_ = Field() ref = Field() title = Field() datetime = Field() author = Field() # Статистика rating = Field() # рейтинг rating_all = Field() # кол-во всех голосов rating_up = Field() # кол-во голосов "за" rating_down = Field() # кол-во голосов "против" views = Field() # кол-во просмотров count = Field() # кол-во добавлений comments = Field() # кол-во комментариев
Loader
Для упрощения обработки элементов существуют Loader'ы. Loader применяет функции для обработки данных и создает Item. Напоминает обработку форм в Django. Где-то просто приводим к числу, где-то пишем что-то своё:
class FavoriteItemLoader(ItemLoader): default_item_class = FavoriteItem default_output_processor = TakeFirst() id__in = MapCompose(int) ref_in = MapCompose(urljoin) datetime_in = MapCompose(process_datetime) rating_in = MapCompose(process_rating) rating_all_in = MapCompose(process_rating_all(1)) rating_up_in = MapCompose(process_rating_all(2)) rating_down_in = MapCompose(process_rating_all(3)) views_in = MapCompose(process_views) count_in = MapCompose(int) comments_in = MapCompose(int)
Spider
Переходим к самому пауку. Нужно сохранить статьи с текущей странички и перейти на следующую, если она есть. Немного упрощенный код:
def parse(self, response, **kwargs): if response.status == 404: msg = 'There is no such user.' self.logger.error(msg) raise CloseSpider(msg) yield from response.follow_all(response.css('a#pagination-next-page'), self.parse) posts = response.css('article.tm-articles-list__item') for post in posts: l = FavoriteItemLoader(selector=post, response=response) l.add_xpath('id_', '@id') l.add_css('ref', 'a.tm-title__link::attr(href)') l.add_css('title', 'a.tm-title__link span::text') l.add_css('author', 'a.tm-user-info__username::text') l.add_xpath('datetime', '//time/@datetime') l.add_css('rating', '.tm-votes-meter__value_rating::text') l.add_css('rating_all', '.tm-votes-meter__value_rating::attr(title)') l.add_css('rating_up', '.tm-votes-meter__value_rating::attr(title)') l.add_css('rating_down', '.tm-votes-meter__value_rating::attr(title)') l.add_css('views', '.tm-icon-counter__value::text') l.add_css('count', '.bookmarks-button__counter::text') l.add_css('comments', '.tm-article-comments-counter-link__value::text') yield l.load_item()
Pipeline
Есть еще уровень обработки: Pipeline'ы. Сюда поступают уже созданные Item'ы. В принципе, делать можно что угодно. Именно здесь обычно происходит сохранение в базу и проверка полей. Последним вариантом и воспользуемся: если у статьи нет рейтинга — выставим None.
class FavoriteItemPipeline(object): def __init__(self): self.fields = ['rating', 'rating_all', 'rating_up', 'rating_down'] def process_item(self, item, _spider): for field in self.fields: if field not in item: item[field] = None return item
Exporter
Теперь нужно все данные собрать в файл. В Scrapy по умолчанию есть возможность сохранения данных в JSON, XML и СSV, которая реализуется с помощью классов Exporter. Но я решил, что HTML-страничка, где видно все статьи со ссылками и можно всё упорядочить, лишь кликнув мышкой, будет более удобным вариантом. Для этого был написан новый Exporter.
P.S.
Проект был создан 11 лет назад, когда еще был Хабрахабр (и Geektimes), а закладки были избранным. Отсюда и название.
