Python for Medium

Как я использовал Python, чтобы найти интересных людей на Medium

На Medium очень много пользователей и бесконечное количество постов. Найти интересные аккаунты для подписки более чем трудно.

Интересный для меня пользователь активен и часто пишет ценные комментарии.

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

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

Любая утомительная задача может и должна быть автоматизирована.

Я хотел, автоматизировать следующие вещи:

  1. Собрать всех пользователей из моих подписок.
  2. Собрать последние посты каждого.
  3. Собрать все комментарии к каждому посту.
  4. Отсеять ответы, которым больше 30 дней.
  5. Отсеять ответы с минимальным количеством хлопков.
  6. Сохранить имена оставшихся авторов.

Будем пробовать

Сначала я посмотрел API Medium, но решил, что он только ограничивает возможности. Мне было не с чем работать. Я мог получить информацию только о своей учетной записи, а не о других пользователях.

Кроме того, последнее изменение API Medium было более года назад.

Я понял, что мне придется полагаться на HTTP-запросы для получения данных, поэтому я начал ковыряться с помощью Chrome DevTools.

Первой целью было получить перечень моих подписок.

Я открыл DevTools и перешел на вкладку «Network». Я отфильтровал все, кроме XHR, чтобы увидеть, откуда Medium получает список моих подписок. Я нажал кнопку обновления страницы в своем профиле, но не получил ничего интересного.

Но что если я нажму на «Подписки»? Бинго.

Перейдя по ссылке, я нашел большой ответ. Это был хорошо отформатированный JSON, за исключением строки символов в начале ответа:

])}while(1);</x>

Я написал функцию, чтобы превратить очищенный от лишних символов JSON в словарь Python:

import json
def clean_json_response(response):
    return json.loads(response.text.split('])}while(1);</x>')[1])

Я нашел точку входа. Да начнется код.

Читайте в блоге: 5 простых способов визуализации данных на Python. Пишем код

Перечень моих подписок

Чтобы запросить эту информацию, мне нужен мой ID.

Ища способ получить ID пользователя, я узнал, что вы можете добавить ?format=json для большинства URL-адресов Medium, чтобы получить JSON с этой страницы. Я пробовал это на своей странице профиля.

Смотрите, вот и ID:

])}while(1);</x>{"success":true,"payload":{"user":{"userId":"d540942266d0","name":"Radu Raicea","username":"Radu_Raicea",
...

Я написал функцию, чтобы вытащить ID из имени пользователя. Опять же, мне пришлось использовать clean_json_response для удаления нежелательных символов в начале ответа.

Я также создал константу под названием MEDIUM, которая содержит базу для всех URL-адресов Medium:

import requests
MEDIUM = 'https://medium.com'
def get_user_id(username):
    print('Retrieving user ID...')
    url = MEDIUM + '/@' + username + '?format=json'
    response = requests.get(url)
    response_dict = clean_json_response(response)
    return response_dict['payload']['user']['userId']

С ID пользователя я запросил конечную точку /_/api/users/<user_id>/following и получил имена пользователей из моих подписок.

Когда я сделал это в DevTools, то заметил, что JSON выдал только восемь имен. Странно.

После того, как я нажал «показать больше людей», то увидел и других пользователей. Medium использует разбивку на страницы для списка подписок.

Разбивка на страницы работает путем указания limit (элементов на страницу) и to (первый элемент следующей страницы). Я должен был найти способ получить ID следующего элемента.

В конце JSON-ответа от /_/api/users/<user_id>/following я увидел интересный ключ:

...
"paging":{"path":"/_/api/users/d540942266d0/followers","next":{"limit":8,"to":"49260b62a26c"}}},"v":3,"b":"31039-15ed0e5"}

Начиная отсюда, писать код стало легко.

def get_list_of_followings(user_id):
    print('Retrieving users from Followings...')
    
    next_id = False
    followings = []
    while True:
        if next_id:
            # If this is not the first page of the followings list
            url = MEDIUM + '/_/api/users/' + user_id
                  + '/following?limit=8&to=' + next_id
        else:
            # If this is the first page of the followings list
            url = MEDIUM + '/_/api/users/' + user_id + '/following'
        response = requests.get(url)
        response_dict = clean_json_response(response)
        payload = response_dict['payload']
        for user in payload['value']:
            followings.append(user['username'])
        try:
            # If the "to" key is missing, we've reached the end
            # of the list and an exception is thrown
            next_id = payload['paging']['next']['to']
        except:
            break
    return followings

Последние посты каждого пользователя

Как только я получил список пользователей, я захотел найти их последние посты. Я мог бы сделать это с запросом https://medium.com/@<username>/latest?format=json.

Я написал функцию, которая выдает ID всех последних постов пользователей во входном списке:

def get_list_of_latest_posts_ids(usernames):
    print('Retrieving the latest posts...')
    post_ids = []
    for username in usernames:
        url = MEDIUM + '/@' + username + '/latest?format=json'
        response = requests.get(url)
        response_dict = clean_json_response(response)
        try:
            posts = response_dict['payload']['references']['Post']
        except:
            posts = []
        if posts:
            for key in posts.keys():
                post_ids.append(posts[key]['id'])
    return post_ids

Комментарии к постам

Из списка последних постов я извлек все комментарии, используя https://medium.com/_/api/posts/<post_id>/responses.

Эта функция принимает список ID постов и выдает список комментариев:

def get_post_responses(posts):
    print('Retrieving the post responses...')
    responses = []
    for post in posts:
        url = MEDIUM + '/_/api/posts/' + post + '/responses'
        response = requests.get(url)
        response_dict = clean_json_response(response)
        responses += response_dict['payload']['value']
    return responses

Сортировка комментариев

Сначала я хотел получить комментарии с минимальным количеством хлопков. Но затем понял, что это не лучший показатель: пользователь может поставить несколько хлопков одной и той же статье.

Вместо этого я отфильтровал комментарии по количеству рекомендаций — этот показатель не учитывает дубликаты.

Я хотел, чтобы минимум был динамическим, поэтому я создал переменную с именем recommend_min.

Следующая функция принимает комментарий и переменную recommend_min. Затем она проверяет, соответствует ли комментарий этому минимуму:

def check_if_high_recommends(response, recommend_min):
    if response['virtuals']['recommends'] >= recommend_min:
        return True

Я все еще хотел проанализировать свежие комментарии. Поэтому я отфильтровал те, которые были старше 30 дней, используя эту функцию:

from datetime import datetime, timedelta
def check_if_recent(response):
    limit_date = datetime.now() - timedelta(days=30)
    creation_epoch_time = response['createdAt'] / 1000
    creation_date = datetime.fromtimestamp(creation_epoch_time)
    if creation_date >= limit_date:
        return True

Имена пользователей, оставивших коммментарий

Как только у меня были все отфильтрованные комментарии, я собрал ID их авторов, используя следующую функцию:

def get_user_ids_from_responses(responses, recommend_min):
    print('Retrieving user IDs from the responses...')
    user_ids = []
    for response in responses:
        recent = check_if_recent(response)
        high = check_if_high_recommends(response, recommend_min)
        if recent and high:
            user_ids.append(response['creatorId'])
    return user_ids

ID пользователей бесполезны, когда вы пытаетесь получить доступ к чьему-то профилю. Я сделал следующий запрос функции к конечной точке /_/api/users/<user_id>, чтобы получить имена пользователей:

def get_usernames(user_ids):
    print('Retrieving usernames of interesting users...')
    usernames = []
    for user_id in user_ids:
        url = MEDIUM + '/_/api/users/' + user_id
        response = requests.get(url)
        response_dict = clean_json_response(response)
        payload = response_dict['payload']
        usernames.append(payload['value']['username'])
    return usernames

Собирая все вместе

После завершения всех функций я создал конвейер, чтобы получить список рекомендуемых пользователей:

def get_interesting_users(username, recommend_min):
    print('Looking for interesting users for %s...' % username)
    user_id = get_user_id(username)
    usernames = get_list_of_followings(user_id)
    posts = get_list_of_latest_posts_ids(usernames)
    responses = get_post_responses(posts)
    users = get_user_ids_from_responses(responses, recommend_min)
    return get_usernames(users)

Скрип готов! Чтобы запустить его, вы должны вызвать конвейер:

interesting_users = get_interesting_users('Radu_Raicea', 10)
print(interesting_users)

Наконец, я добавил опцию добавления результатов в CSV с пометкой времени.

import csv
def list_to_csv(interesting_users_list):
    with open('recommended_users.csv', 'a') as file:
        writer = csv.writer(file)
        now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        interesting_users_list.insert(0, now)
        
        writer.writerow(interesting_users_list)
interesting_users = get_interesting_users('Radu_Raicea', 10)
list_to_csv(interesting_users)

Исходный код проекта доступен на GitHub.

перевод: Астафьева Наталья

оригинал статьи: How I Used Python to find interesting people to follow on Medium

Поделиться:
Опубликовано в рубрике PythonTagged