Ответ Эрика диагностирует конкретную c причину этой ошибки, но в более широком смысле я думаю, что это вызвано тем, что вы не используете PRAW в полной мере. Ваш скрипт импортирует requests
и выполняет много ручных запросов, для которых у PRAW уже есть методы. Весь смысл PRAW состоит в том, чтобы запретить вам писать эти запросы, например, разбивать список на страницы, поэтому я рекомендую вам воспользоваться этим.
Например, ваша функция get_submission_ids
(которая очищает веб-версию Reddit и обрабатывает нумерацию страниц) можно заменить просто
def get_submission_ids(self, pages=2):
return [
submission.id
for submission in self.reddit.subreddit(self.subreddit).hot(
limit=25 * pages
)
]
, потому что .hot()
функция делает все, что вы пытались сделать вручную.
Я собираюсь go сделать еще один шаг, и функция просто вернет список Submission
объектов, потому что остальная часть вашего кода в конечном итоге делает то, что было бы лучше благодаря взаимодействию с объектом PRAW Submission
. Вот этот код (я переименовал функцию, чтобы отразить ее обновленное назначение):
def get_submissions(self, pages=2):
return list(self.reddit.subreddit(self.subreddit).hot(limit=25 * pages))
(Я обновил эту функцию, чтобы просто возвращать ее результат, поскольку ваша версия возвращает значения и устанавливает его как self.submission_ids
, , если pages
не равно 0
. Это показалось довольно противоречивым, поэтому я заставил его просто вернуть значение.)
Ваша функция get_comments
выглядит хорошо.
Функция save_comments_submissions
, как и get_submission_ids
, выполняет много ручной работы, которую может выполнять PRAW. Вы создаете temp_url
с полным URL-адресом поста, а затем используете его для создания объекта PRAW Submission
, но мы можем заменить его напрямую, используя тот, который возвращается get_submissions
. У вас также есть несколько звонков на номер time.sleep()
, которые я удалил, потому что PRAW автоматически уложит для вас подходящую сумму. Наконец, я удалил возвращаемое значение этой функции, потому что суть функции заключается в том, чтобы сохранять данные на диск, а не возвращать их в другое место, а остальная часть вашего сценария не использует возвращаемое значение. Вот обновленная версия этой функции:
def save_comments_submissions(self, pages):
"""
1. Save all the ids of submissions.
2. For each submission, save information of this submission. (submission_id, #comments, score, subreddit, date, title, body_text)
3. Save comments in this submission. (comment_id, submission_id, author, date, score, body_text)
4. Separately, save them to two csv file.
Note: You can link them with submission_id.
Warning: According to the rule of Reddit API, the get action should not be too frequent. Safely, use the defalut time span in this crawler.
"""
print("Start to collect all submission ids...")
submissions = self.get_submissions(pages)
print(
"Start to collect comments...This may cost a long time depending on # of pages."
)
comments = []
pandas_submissions = []
for count, submission in enumerate(submissions):
pandas_submissions.append(
(
submission.name[3:],
submission.num_comments,
submission.score,
submission.subreddit_name_prefixed,
date.fromtimestamp(submission.created_utc).isoformat(),
submission.title,
submission.selftext,
)
)
temp_comments = self.get_comments(submission)
comments += temp_comments
print(str(count) + " submissions have got...")
comments_fieldnames = [
"comment_id",
"submission_id",
"author_name",
"post_time",
"comment_score",
"text",
]
df_comments = pd.DataFrame(comments, columns=comments_fieldnames)
df_comments.to_csv("comments.csv")
submissions_fieldnames = [
"submission_id",
"num_of_comments",
"submission_score",
"submission_subreddit",
"post_date",
"submission_title",
"text",
]
df_submission = pd.DataFrame(pandas_submissions, columns=submissions_fieldnames)
df_submission.to_csv("submissions.csv")
Вот обновленная версия всего сценария, который полностью использует PRAW:
from datetime import date
import sys
import pandas as pd
import praw
class Crawler:
"""
basic_url is the reddit site.
headers is for requests.get method
REX is to find submission ids.
"""
def __init__(self, subreddit="apple"):
"""
Initialize a Crawler object.
subreddit is the topic you want to parse. default is r"apple"
basic_url is the reddit site.
headers is for requests.get method
REX is to find submission ids.
submission_ids save all the ids of submission you will parse.
reddit is an object created using praw API. Please check it before you use.
"""
self.subreddit = subreddit
self.submission_ids = []
self.reddit = praw.Reddit(
client_id="your_id",
client_secret="your_secret",
user_agent="subreddit_comments_crawler",
)
def get_submissions(self, pages=2):
"""
Collect all submissions..
One page has 25 submissions.
page url: https://www.reddit.com/r/subreddit/?count25&after=t3_id
id(after) is the last submission from last page.
"""
return list(self.reddit.subreddit(self.subreddit).hot(limit=25 * pages))
def get_comments(self, submission):
"""
Submission is an object created using praw API.
"""
# Remove all "more comments".
submission.comments.replace_more(limit=None)
comments = []
for each in submission.comments.list():
try:
comments.append(
(
each.id,
each.link_id[3:],
each.author.name,
date.fromtimestamp(each.created_utc).isoformat(),
each.score,
each.body,
)
)
except AttributeError as e: # Some comments are deleted, we cannot access them.
# print(each.link_id, e)
continue
return comments
def save_comments_submissions(self, pages):
"""
1. Save all the ids of submissions.
2. For each submission, save information of this submission. (submission_id, #comments, score, subreddit, date, title, body_text)
3. Save comments in this submission. (comment_id, submission_id, author, date, score, body_text)
4. Separately, save them to two csv file.
Note: You can link them with submission_id.
Warning: According to the rule of Reddit API, the get action should not be too frequent. Safely, use the defalut time span in this crawler.
"""
print("Start to collect all submission ids...")
submissions = self.get_submissions(pages)
print(
"Start to collect comments...This may cost a long time depending on # of pages."
)
comments = []
pandas_submissions = []
for count, submission in enumerate(submissions):
pandas_submissions.append(
(
submission.name[3:],
submission.num_comments,
submission.score,
submission.subreddit_name_prefixed,
date.fromtimestamp(submission.created_utc).isoformat(),
submission.title,
submission.selftext,
)
)
temp_comments = self.get_comments(submission)
comments += temp_comments
print(str(count) + " submissions have got...")
comments_fieldnames = [
"comment_id",
"submission_id",
"author_name",
"post_time",
"comment_score",
"text",
]
df_comments = pd.DataFrame(comments, columns=comments_fieldnames)
df_comments.to_csv("comments.csv")
submissions_fieldnames = [
"submission_id",
"num_of_comments",
"submission_score",
"submission_subreddit",
"post_date",
"submission_title",
"text",
]
df_submission = pd.DataFrame(pandas_submissions, columns=submissions_fieldnames)
df_submission.to_csv("submissions.csv")
if __name__ == "__main__":
args = sys.argv[1:]
if len(args) != 2:
print("Wrong number of args...")
exit()
subreddit, pages = args
c = Crawler(subreddit)
c.save_comments_submissions(int(pages))
Я понимаю, что мой ответ здесь мы попадаем на территорию Code Review , но я надеюсь, что этот ответ поможет понять некоторые вещи, которые PRAW может сделать. Вашу ошибку «Список индексов вне диапазона» можно было бы избежать, используя уже существующий библиотечный код, поэтому я считаю, что это решение вашей проблемы.