Использование PRAW (оболочки Python Reddit API) для создания локальной базы данных сообщений и комментариев Reddit — Руководство для начинающих

Есть два популярных модуля Python для доступа к данным Reddit. Мы собираемся использовать PRAW, а не Pushshift по двум причинам. Во-первых, потому что PRAW позволяет вам запрашивать сам Reddit, чтобы получать самые свежие сообщения и информацию, а не ждать, пока что-то будет проиндексировано и обновлено в базе данных Pushshift (хотя следует отметить, что Pushshift — лучший способ получить исторические данные). ). Во-вторых, PRAW позволяет вам использовать все функции Reddit API — поэтому, если в какой-то момент вы захотите публиковать, комментировать или иным образом взаимодействовать с Reddit программно, помимо очистки данных, вы захотите сделать это с помощью PRAW. Используйте эту силу во благо, хотя, пожалуйста, миру больше не нужны спам-боты.

Вы можете установить PRAW с помощью conda или pip, в зависимости от ваших предпочтений. Используйте любой из:

pip install praw
conda install -c conda-forge praw

Вам также понадобится модуль pandas. Если у вас это еще не установлено, сделайте это сейчас.

Во-первых, давайте получим доступ к Reddit API.

Перейдите на https://www.reddit.com/prefs/apps.

Прокрутите страницу вниз и нажмите «Вы разработчик? кнопка создать приложение…»

Теперь у вас есть несколько полей для заполнения:

Выберите скрипт
название: как хотите
описание: не требуется
о URL-адресе:не требуется
URI перенаправления: требуется — вы можете использовать http://localhost:8080 для ссылки на ваш локальный компьютер

После того, как вы нажмете «Отправить», вы получите экран, который включает ваш client_id в левом верхнем углу и ваш секрет в середине — они вам понадобятся через мгновение:

Это все, что нам нужно, чтобы начать использовать API в режиме чтения для извлечения данных!

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

Теперь давайте начнем сбор данных

Вот хорошая вещь — код. Сначала нам нужно запустить и авторизовать наш экземпляр PRAW.

import praw
import csv
import pandas as pd
reddit = praw.Reddit(
    client_id="THE CLIENT ID YOU GOT FROM REDDIT",
    client_secret="THE SECRET YOU GOT FROM REDDIT", 
    user_agent="A UNIQUE USER AGENT YOU SET RIGHT NOW")

Вам нужно будет заполнить client_id и секрет, которые Reddit назначил вам ранее, а также создать уникальное имя для вашего пользовательского агента. Вот что PRAW рекомендует для вашего пользовательского агента, но это может быть любое уникальное имя, которое вы хотите: «<platform>:<app ID>:<version string> (by u/<Reddit username>)"

Теперь давайте проверим, успешно ли вы подключились, получив некоторые базовые данные о сабреддите r/redditdev:

test_subreddit = reddit.subreddit(“redditdev”)
print(test_subreddit.display_name)
print(book_subreddit.title)
print(book_subreddit.description)

Если этот код сработал и вернул данные о r/redditdev, вы успешно подключились к Reddit API!

Извлечение всех самых последних сообщений в сабреддите и создание локальной базы данных

Приведенный ниже код создаст файл csv и заполнит его самыми последними сообщениями ~900 в выбранном вами субреддите (самая большая партия, которую вы можете получить с помощью PRAW). Для очень активного субреддита это может быть от нескольких дней до недели. Для меньшего сабреддита посты могут длиться месяцы или годы. Чтобы изменить запрашиваемый субреддит, измените значение my_subreddit в приведенном ниже коде.

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

ЭТО ЗАНИМАЕТСЯ ДОЛГО ВРЕМЯ. Reddit API позволяет нам делать только 100 запросов в секунду. PRAW автоматически создаст для вас пакетный запрос, но это по-прежнему означает, что выполнение этого блока кода может занять до двух минут.

Каждый раз, когда вы запускаете этот блок, он будет обновлять вашу локальную базу данных и добавлять любые новые сообщения с момента последнего запуска! Этот код состоит из двух основных частей:

  1. Во-первых, мы извлекаем все последние сообщения и все полезные атрибуты этих сообщений и сохраняем результаты в локальном CSV-файле.
  2. Во-вторых, мы дедуплицируем нашу базу данных, удаляя сообщения, которые уже были в нашем локальном csv*.

*Обратите внимание, что нам не нужно беспокоиться о том, что люди удалят свои сообщения при работе с дубликатами в базе данных. Если сообщение удалено, оно больше не будет отображаться в вызове API, поэтому мы всегда хотим, чтобы самая последняя информация о сообщении получала самую актуальную оценку и количество комментариев.

my_subreddit = "redditdev"
submission_headers = ['author', 'created_utc', 'id', 
                      'is_original_content', 'is_self', 
                      'link_flair_text', 'locked', 'name', 
                      'num_comments', 'over_18', 'permalink', 
                      'score', 'selftext', 'spoiler', 'stickied', 
                      'subreddit', 'title', 'upvote_ratio', 'url']
#Note: 'a' opens the file in append mode to avoid overwriting data
with open("reddit_test_submission_db.csv", 'a', 
          encoding="utf-8", newline='') as f_object:
    newposts = reddit.subreddit(my_subreddit).new(limit=None)
    for post in newposts:
    #Below are all the fields we'll request from PRAW for each post
        data = {'author': post.author, 'created_utc': 
                post.created_utc, 'id': post.id, 
                'is_original_content': post.is_original_content, 
                'is_self': post.is_self, 'link_flair_text': 
                post.link_flair_text, 'locked': post.locked, 
                'name': post.name, 'num_comments': 
                post.num_comments, 'over_18': post.over_18, 
                'permalink': post.permalink, 'score': post.score, 
                'selftext': post.selftext, 'spoiler': post.spoiler, 
                'stickied': post.stickied, 'subreddit': 
                post.subreddit, 'title': post.title, 
                'upvote_ratio': post.upvote_ratio, 'url': post.url}
        dictwriter_object = csv.DictWriter(
            f_object, fieldnames=submission_headers)
        dictwriter_object.writerow(data)
    f_object.close()
#Code below will delete duplicates on successive pulls
post_db = pd.read_csv("reddit_test_submission_db.csv", 
                      names=submission_headers, header=0)
post_db.drop_duplicates(subset="permalink", 
                        keep="last", inplace=True)
post_db.to_csv("reddit_test_submission_db.csv", 
               index=False, chunksize=1000)

Если вы хотите настроить атрибуты, которые вы сохраняете для каждого сообщения, ознакомьтесь с документацией PRAW здесь и соответствующим образом обновите заголовки отправки и строки данных кода. Эта ссылка также включает описание всех полей, которые мы сохраняем, и вы можете использовать их в качестве словаря данных.

Вот как должны выглядеть первые 5 строк ваших данных после запуска кода:

Извлеките все комментарии к этим сообщениям и сохраните их в базе данных комментариев

Как и выше, этот блок создаст CSV-файл в том же каталоге, где работает ваш код — измените ссылки на файлы, чтобы изменить это.

ЭТОТ БЛОК КОДА ЗАНИМАЕТ ОЧЕНЬ, ОЧЕНЬ ДОЛГО ВРЕМЯ. Из-за ограничения в 100 запросов в секунду этот блок потенциально может выполняться до 3–4 часов или более при первом запуске, в зависимости от того, сколько комментариев обычно получает каждое сообщение в выбранном вами сабреддите.

Этот блок кода состоит из трех основных разделов:

  1. Создайте список материалов, для которых мы хотим получить комментарии. На постоянной основе важно не запрашивать комментарии, которые мы уже получили. Если мы продолжим запрашивать все комментарии для всех материалов в нашей локальной базе данных, время обработки будет увеличиваться и увеличиваться с каждым запуском программы. Вместо этого мы проверяем наш список представлений и создаем список только новых представлений. В приведенном ниже коде мы находим самое последнее представление, для которого есть какие-либо комментарии, смотрим на дату его публикации, вычитаем три дня, чтобы убедиться, что мы не пропустили комментарии к сообщениям, которые все еще были активны и комментировались в время, когда мы вытягивали посты последними, а затем запрашивали комментарии для всех представлений в нашей базе данных, сделанных после этой даты.
  2. Используйте PRAW, чтобы запросить у Reddit API все комментарии для этого списка материалов. Приведенный ниже код запрашивает все полезные атрибуты для этих комментариев, а затем добавляет их в локальный файл csv.
  3. Удаляйте повторяющиеся комментарии из нашей базы данных комментариев. С нашей трехдневной погрешностью мы извлечем некоторые комментарии, которые уже есть в нашей базе данных. Однако иногда люди удаляют комментарии. Если они это сделали, мы хотим сохранить исходный комментарий до того, как они его удалили, и сохранить самый старый экземпляр комментария в наших данных, а для ситуаций, когда комментарий не был удален, мы будем собирать самые последние данные, которые показывают окончательная оценка.
#This block creates list of submissions for which we want comments
comment_headers = ['author', 'body', 'created_utc', 
                   'distinguished', 'edited', 'id', 
                   'is_submitter', 'link_id', 'parent_id', 
                   'permalink', 'saved', 'score', 'stickied', 
                   'submission', 'subreddit', 'subreddit_id']
with open('reddit_test_comment_db.csv', 'a') as comment_file:
    comments_db = pd.read_csv('reddit_test_comment_db.csv', 
                              usecols=["submission"], 
                              names=comment_headers)
    comment_file.close()
submission_db = pd.read_csv("reddit_test_submission_db.csv", 
                            usecols=["created_utc", "id"])
#Filter down to submissions for which we don't yet have comments
comments_set = set(comments_db["submission"])
#259,200 is three days worth of seconds
try:
    time_cutoff = max([created_utc for post_id, created_utc in 
         zip(submission_db.id, submission_db.created_utc) 
         if post_id in comments_set]) - 259200
except:
    time_cutoff = submission_db["created_utc"].min()
submissions_to_pull = submission_db.loc[submission_db
                                        ["created_utc"] >= 
                                        time_cutoff, "id"]
#This block pulls all comments for the list of submissions we have identified
with open("reddit_test_comment_DB.csv", 'a', 
          encoding="utf-8", newline='') as f_object:
    for row in submissions_to_pull:
        submission = reddit.submission(id=row)
        submission.comments.replace_more(limit=None)
        for comment in submission.comments.list():
            data = {'author': comment.author, 'body': 
                    comment.body, 'created_utc': 
                    comment.created_utc, 'distinguished': 
                    comment.distinguished, 'edited': 
                    comment.edited, 'id': comment.id, 
                    'is_submitter': comment.is_submitter, 
                    'link_id': comment.link_id, 'parent_id': 
                    comment.parent_id, 'permalink': 
                    comment.permalink, 'saved': comment.saved, 
                    'score': comment.score, 'stickied': 
                    comment.stickied, 'submission': 
                    comment.submission, 'subreddit': 
                    comment.subreddit, 
                    'subreddit_id': comment.subreddit_id}
            dictwriter_object = csv.DictWriter(f_object, 
                                               fieldnames=
                                               comment_headers)
            dictwriter_object.writerow(data)
    f_object.close()
    
#Now drop duplicate rows
comment_db = pd.read_csv("reddit_test_comment_DB.csv", 
                         names=comment_headers, header=0)
#First drop duplicates that are not edited, keep the last pull
comment_db.drop_duplicates(subset=["permalink", "body", "id"],
                           keep = "last", inplace=True)
#Then drop duplicates that have been edited, keep the first pull
comment_db.drop_duplicates(subset="permalink", 
                           keep = "first", inplace=True)
comment_db.to_csv("reddit_test_comment_DB.csv", 
                  index=False, chunksize=1000)

Если вы хотите настроить атрибуты, которые вы сохраняете для каждого комментария, ознакомьтесь с документацией PRAW здесь и соответствующим образом обновите заголовки комментариев и строку кода данные. Эта ссылка также включает описание всех полей, которые мы сохраняем, которые вы можете использовать в качестве словаря данных.

Ваши первые пять строк должны выглядеть так после запуска кода:

Вот и все, вы создали локальную базу данных сообщений и комментариев на Reddit, которую вы можете обновлять!

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

Это отличный первый шаг к работе с API Reddit и извлечению данных для использования во всех видах анализа. Поздравляю!

Если вы хотите поддерживать базу данных в актуальном состоянии в течение более длительного периода времени, одним из улучшений будет настройка локальной базы данных sql вместо использования CSV-файлов — это будет намного надежнее и доступнее в долгосрочной перспективе.

Это также было бы отличной программой для добавления в качестве задания cron или запланированного задания. Таким образом, вы можете настроить его на периодический запуск в фоновом режиме вашего компьютера и поддерживать актуальность баз данных без необходимости запуска кода вручную.

И, наконец, вы также можете уменьшить код выше. Ряд строк необходим только при первом запуске программы для создания CSV-файла или может быть полностью удален, если вы сначала создадите свой CSV-файл. Вы можете настроить свою программу, удалив эти строки в будущем, но не думайте, что вам это нужно. Если вы продолжите запускать код без каких-либо настроек, ваша база данных будет со временем расти по мере создания новых сообщений.

Всем удачного кодирования!