Закладки и их статистика
Закладки и их статистика

У меня много статей в закладках. Многие из них я добавил, чтобы прочитать позже. Эта статья не про то, почему так произошло и как с этим бороться, а про то, как выбрать статью для удаления чтения. Давайте найдем самые лучшие статьи. Критериями могут быть, например, рейтинг, просмотры и т.д. И красиво оформим в виде HTML-файла.

Пример закладок @alizar: 800+ статей.

Кратко

  1. $ pip install habra-favorites

  2. $ habra_favorites <username> # имя пользователя (не обязательно своё)

  3. $ firefox favorites.html # google-chrome favorites.html

  4. # 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), а закладки были избранным. Отсюда и название.

Комментарии (7)


  1. Artemka1806
    15.03.2024 19:20
    +11

    Добавлю это в закладки...


  1. SemyonVyatskov
    15.03.2024 19:20
    +6

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


    1. Vladislav_Dudnikov
      15.03.2024 19:20

      У меня обратная ситуация возникает часто, как только удаляю закладку, и вот она нужна (хорошо, если закладка была добавлена недавно и её можно найти по истории).


    1. fenom82
      15.03.2024 19:20

      А как решайте с рабочим закладками? Используйте для них вики или как то иначе?


      1. SemyonVyatskov
        15.03.2024 19:20
        +1

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


  1. gun_dose
    15.03.2024 19:20
    +1

    Я, чтобы упорядочить закладки, раз в месяц захожу и удаляю штук 10 ненужных, полагаю, что надо удалять больше, но лень. Идеальным решением была бы возможность выставить время жизни для закладок, чтобы всё, что добавлено больше месяца назад, удалялось автоматически, если не поставлена галочка "хранить постоянно", т.к. в реальности есть 3-5 закладок, которыми пользуюсь постоянно, плюс иногда нужно что-то из числа трёх последних добавленных.


  1. woxe
    15.03.2024 19:20
    +2

    Как начать жить и перестать сохранять закладки?