[译] 我是如何使用 Python 在 Medium 上找到并关注有趣的人

封面图来源:Old Medium logo前端

Medium 上有大量的内容、用户和不可胜数的帖子。当你试图寻找有趣的用户来关注时,你会发现本身不知所措。python

我对于有趣的用户的定义是来自你的社交网络,保持活跃状态并常常在 Medium 社区发表高质量评论的用户。android

我查看了我关注的用户的最新的帖子来看看是谁在回复他们。我认为若是他们回复了我关注的用户,这就说明他们可能和我志趣相投。ios

这个过程很繁琐,这就让我想起了我上次实习期间学到的最有价值的一课:git

任何繁琐的任务都可以而且应该是自动化完成的。github

我想要个人自动化程序可以作下面的事情:web

  1. 从个人关注中获取全部的用户
  2. 从每个用户中获取最新的帖子
  3. 获取每个帖子的全部评论
  4. 筛选出30天之前的回复
  5. 筛选出少于最小推荐数的回复
  6. 获取每一个回复的做者的用户名

让咱们开始吧

我首先看了看 Medium's API,却发现它颇有限。它给我提供的功能太少了。经过它,我只能获取关于我本身的帐号信息,而不能获取其余用户的信息。chrome

最重要的是,Medium's API 的最后一次更新是一年多前,最近也没有要开发的迹象。json

我意识到我只能依靠 HTTP 请求来获取个人数据,因此我开始使用个人 Chrome 开发者工具后端

第一个目标是获取个人关注列表。

我打开个人开发者工具并进入 Network 选项卡。我过滤了除了 XHR 以外的全部内容以查看 Medium 是从什么地方来获取个人关注的。我刷新了个人我的资料页面,可是什么有趣的事情都没发生。

若是我点击个人我的资料上的关注按钮怎么样?成功啦!

我找到用户关注列表的连接。

在这个连接中,我发现了一个很是大的 JSON 响应。它是一个格式很好的 JSON,除了在响应开头的一串字符:])}while(1);</x>

我写了一个函数整理了格式并把 JSON 转换成一个 Python 字典。

import json

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

我已经找到了一个入口点,让咱们开始编写代码吧。

从个人关注列表中获取全部用户

为了查询端点,我须要个人用户 ID(尽管我早就知道啦,这样作是出于教育目的)。

我在寻找获取用户 ID 的方法时发现能够经过添加 ?format=json 给 Medium 的 URL 地址来获取这个网页的 JSON 响应。我在个人我的主页上试了试。

看看,这就是个人用户 ID。

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

我写了一函数从给出的用户名中提取用户 ID 。一样,我使用了 clean_json_response 函数来去除响应开头的不想要的字符串。

我还定义了一个叫 MEDIUM 的常量,它用来存储全部 Medium 的 URL 地址都包含的字符串。

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 端点,从个人关注列表里获取了用户名列表。

当我在开发者工具中作这时,我注意到 JSON 响应只有八个用户名。很奇怪!

当我点击 “Show more people”,我找到了缺乏的用户名。原来 Medium 使用分页的方式来展现关注列表。

Medium 使用分页的方式来展现关注列表。

分页经过指定 limit(每页元素)和 to(下一页的第一个元素)来工做,我必须找到一种方式来获取下一页的 ID。

在从 /_/api/users/<user_id>/following 获取的 JSON 响应的尾部,我看到了一个有趣的 JSON 键值对。

...
"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:
            # 若是这不是关注列表的第一页
            url = MEDIUM + '/_/api/users/' + user_id
                  + '/following?limit=8&to=' + next_id
        else:
            # 若是这是关注列表的第一页
            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:
            # 若是找不到 "to" 键,咱们就到达了列表末尾,
            # 而且异常将会抛出。
            next_id = payload['paging']['next']['to']
        except:
            break

    return followings
复制代码

获取每一个用户最新的帖子

我获得了我关注的用户列表以后,我就想获取他们最新的帖子。我能够经过发送这个请求 [https://medium.com/@<username>/latest?format=json](https://medium.com/@username/latest?format=json) 来实现这个功能。

因而我写了一个函数,这个函数的参数是用户名列表,而后返回一个包含输入进来的全部用户最新发表的帖子 ID 的 Python 列表。

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 Python 列表而后返回评论的Python列表。

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)
复制代码

图片来源: Know Your Meme

最后,我添加了一个选项,能够把结果和时间戳存储在一个 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)

复制代码

关于这个项目的源文件能够在这里找到

若是你还不会 Python,阅读 TK 的 Python 教程:Learning Python: From Zero to Hero

若是你对其余让用户感兴趣的标准有建议,请在下面留言!

总结···

  • 我编写一个适用于 Medium 的 Python 脚本
  • 这个脚本返回一个用户列表,里面的用户都是活跃的且在你的关注的用户的最新帖子下面发表过有趣的评论
  • 你能够从列表里取出用户,用他的用户名而不是你的来运行这个脚本。

点击个人关于开源许可的初级教程以及如何把它们添加到你的项目中!

更多更新,请关注个人 Twitter

掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。

相关文章
相关标签/搜索