Рубрики
Без рубрики

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

Medium имеет большой объем контента, большое количество пользователей и почти подавляющее количество сообщений. Когда вы пытаетесь найти интересных пользователей для взаимодействия, вас захлестывает визуальный шум.

Автор оригинала: Radu Raicea.

Кредит на изображение: Старый средний логотип

Medium имеет большой объем контента, большое количество пользователей и почти подавляющее количество сообщений. Когда вы пытаетесь найти интересных пользователей для взаимодействия, вас захлестывает визуальный шум.

Я определяю интересного пользователя как человека из вашей сети, который активен и пишет ответы, которые, как правило, ценятся средним сообществом.

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

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

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

Я хотел, чтобы моя автоматизация выполняла следующие действия:

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

Давайте начнем тыкать

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

Кроме того, последнее изменение API Medium произошло более года назад. Не было никаких признаков недавнего развития событий.

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

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

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

Что делать, если я нажму кнопку “Ниже” в своем профиле? Бинго.

Поиск ссылки со списком подписчиков пользователя

Внутри ссылки я нашел очень большой ответ JSON . Это был хорошо отформатированный JSON, за исключением строки символов в начале ответа: ])}while(1);

Я написал функцию, чтобы очистить это и превратить JSON в словарь Python.

import json

def clean_json_response(response):
    return json.loads(response.text.split('])}while(1);')[1])

Я нашел точку входа. Пусть начнется кодирование.

Получение всех пользователей из моего списка подписчиков

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

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

О, смотрите, вот идентификатор пользователя.

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

Я написал функцию для извлечения идентификатора пользователя из данного имени пользователя. Опять же, мне пришлось использовать 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']

С помощью идентификатора пользователя я запросил конечную точку /_/api/users//following и получил список имен пользователей из моего списка подписчиков.

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

После того, как я нажал на кнопку “Показать больше людей”, я увидел, чего не хватает. Средний использует разбиение на страницы для списка следующих.

Среда использует разбиение на страницы для следующего списка

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

В конце ответа JSON от /_/api/users//, следующего за , виден интересный ключ.

..."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/@<имя пользователя>/последнее?формат=json

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

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//ответы

Эта функция принимает список идентификаторов сообщений и возвращает список ответов.

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

Получение имени пользователя автора каждого ответа

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

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

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

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 .

Если вы не знаете Python, перейдите к чтению TK ‘s Изучение Python: от нуля до героя .

Если у вас есть предложения по другим критериям, которые делают пользователей интересными, пожалуйста, напишите их ниже!

Вкратце…

  • Я сделал скрипт Python для Medium .
  • Скрипт возвращает список интересных пользователей, которые активны и публикуют интересные ответы на последние сообщения людей, за которыми вы следите.
  • Вы можете выбрать пользователей из списка и запустить скрипт с их именем пользователя вместо вашего.

Ознакомьтесь с моим |/праймером о лицензиях с открытым исходным кодом и о том, как добавлять их в свои проекты!

Оригинальный пост на |/Medium |/.

Для получения дополнительных обновлений следуйте за мной в Twitter .