Guia de como raspar Reddit

Neste guia passo-a-passo, aprenderá a raspar Reddit com Python e a evitar as taxas da API de Reddit.
17 min read
How to scrape Reddit in Python

Neste guia passo-a-passo, você aprenderá como raspar Reddit usando Python.

Este tutorial abordará:

  • Nova política da API de Reddit
  • API de Reddit vs. raspagem de Reddit
  • Raspagem de Reddit com Selenium

Nova política da API de Reddit

Em abril de 2023, Reddit anunciou novas taxas para as suas APIs de dados, basicamente tornando as empresas mais pequenas incapazes de as pagar. No momento em que escrevo, a taxa da API está fixada em US$ 0,24 por 1,000 chamadas. Como pode imaginar, este valor pode aumentar rapidamente, mesmo com uma utilização modesta. Isto é especialmente verdade tendo em conta as toneladas de conteúdos gerados pelos usuários disponíveis em Reddit e as enormes quantidades de chamadas necessárias para os recuperar. Apollo, uma das aplicações de terceiros mais utilizadas, criada com base na API de Reddit, foi forçada a encerrar por esse motivo.

Será que isto significa o fim de Reddit como fonte de análise de sentimentos, retroalimentação dos usuários e dados sobre tendências? De certeza que não! Existe uma solução que é mais eficaz, menos dispendiosa e não está sujeita a decisões empresariais de um dia para o outro. Essa solução chama-se raspagem da web. Vamos descobrir porquê!

API de Reddit vs. raspagem de Reddit

A API de Reddit é o método oficial para obter dados do sítio. Tendo em conta as recentes alterações de política e as direções tomadas pela plataforma, existem boas razões para que a raspagem de Reddit seja uma melhor solução:

  • Custo-benefício: Tendo em conta o novo custo da API de Reddit, a raspagem de Reddit pode ser uma alternativa muito mais económica. A criação de um raspador de Reddit com Python permite-lhe coletar dados sem incorrer em despesas adicionais associadas à utilização da API.
  • Coleta de dados melhorada: Ao fazer raspagem de Reddit, tem a flexibilidade de personalizar o código de extração de dados para obter apenas as informações que correspondem aos seus requisitos. Esta personalização ajuda-o a ultrapassar as limitações do formato dos dados, a limitação da taxa e as restrições de utilização na API.
  • Acesso a dados não oficiais: Enquanto a API de Reddit apenas fornece acesso a uma seleção de informações selecionadas, a raspagem fornece acesso a quaisquer dados publicamente acessíveis no sítio.

Agora que já sabe porque é que a raspagem é uma opção mais eficaz do que chamar APIs, vamos ver como construir um raspador de Reddit em Python. Antes de passar para o próximo capítulo, considere explorar o nosso guia detalhado sobre raspagem da web com Python.

Raspagem de Reddit com Selenium

Neste tutorial passo a passo, verá como construir um script de raspagem da web de Reddit com Python.

Passo 1: Configuração do projeto

Primeiro, certifique-se de que cumpre os seguintes pré-requisitos:

Inicialize um projeto em Python com um ambiente virtual através dos comandos abaixo:


mkdir reddit-scraper
cd reddit-scraper
python -m venv env

A pasta reddit-scraper criada aqui é a pasta do projeto para o seu script de Python.

Abra o diretório no IDE, crie um ficheiro scraper.py e inicialize-o como abaixo:

print('Hello, World!')

Neste momento, este script imprime simplesmente “Hello, World!” (Olá, mundo!), mas em breve conterá a lógica de raspagem.

Verifique se o programa funciona, premindo o botão de execução do seu IDE ou iniciando-o:

python scraper.py

No terminal, deve ver:

Hello, World!

Maravilhoso! Agora você tem um projeto em Python para o seu raspador de Reddit.

Passo 2: Selecionar e instalar as bibliotecas de raspagem

Como já deve saber, Reddit é uma plataforma altamente interativa. O sítio carrega e apresenta novos dados de forma dinâmica com base na forma como os usuários interagem com as suas páginas através de operações de clique e deslocação. De um ponto de vista técnico, isto significa que Reddit depende fortemente de JavaScript.

Assim, a raspagem de Reddit em Python requer uma ferramenta que possa renderizar páginas web num navegador. É aqui que entra Selenium! Esta ferramenta permite raspar sítios web dinâmicos em Python, possibilitando operações automatizadas em páginas web num navegador.

Pode adicionar Selenium e o Webdriver Manager às dependências do seu projeto com:

pip install selenium webdriver-manager

O processo de instalação pode demorar algum tempo, por isso, seja paciente.

O pacote webdriver-manager não é estritamente necessário, mas é altamente recomendado. Permite-lhe evitar descarregar, instalar e configurar manualmente os controladores web no Selenium. A biblioteca lida com tudo por você.

Integre Selenium no seu ficheiro scraper.py:


from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.options import Options

# enable the headless mode
options = Options()
options.add_argument('--headless=new')

# initialize a web driver to control Chrome
driver = webdriver.Chrome(
    service=ChromeService(ChromeDriverManager().install()),
    options=options
)
# maxime the controlled browser window
driver.fullscreen_window()

# scraping logic...

# close the browser and free up the Selenium resources
driver.quit()

Este script instancia um objeto Chrome WebDriver para controlar programaticamente uma janela de Chrome.

Por predefinição, Selenium abre o navegador numa nova janela com GUI. Isto é útil para monitorizar o que o script está a fazer nas páginas para depuração. Ao mesmo tempo, carregar um navegador web com a sua interface de usuário consome muitos recursos. Por isso, recomenda-se que configure o Chrome para ser executado no modo sem cabeça. Especificamente, a opção --headless=new dará instruções ao Chrome para começar sem IU por detrás da cena.

Muito bem! É momento de visitar a página alvo de Reddit!

Passo 3: Ligar a Reddit

Aqui, verá como extrair dados do subreddit r/Technology. Não se esqueça de que qualquer outro subreddit serve.

Em pormenor, suponhamos que pretende raspar a página com as principais publicações da semana. Este é o URL da página de destino:

https://www.reddit.com/r/technology/top/?t=week

Armazene essa cadena numa variável de Python:

url = 'https://www.reddit.com/r/technology/top/?t=week'

Em seguida, utilize Selenium para visitar a página com:

driver.get(url)

A função get() dá instruções ao navegador controlado para se ligar à página identificada pelo URL passado como parâmetro.

Este é o aspeto do seu raspador web de Reddit até agora:


from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.options import Options

# enable the headless mode
options = Options()
options.add_argument('--headless=new')

# initialize a web driver to control Chrome
driver = webdriver.Chrome(
    service=ChromeService(ChromeDriverManager().install()),
    options=options
)
# maxime the controlled browser window
driver.fullscreen_window()

# the URL of the target page to scrape
url = 'https://www.reddit.com/r/technology/top/?t=week'
# connect to the target URL in Selenium
driver.get(url)

# scraping logic...

# close the browser and free up the Selenium resources
driver.quit()

Teste o seu script. Abrirá a janela do navegador abaixo durante uma fração de segundo antes de a fechar devido à instrução quit():

Raspagem de dados do Reddit

Veja a mensagem “Chrome está a ser controlado por software de teste automatizado”. Ótimo! Isto garante que Selenium está a funcionar corretamente em Chrome.

Passo 4: Inspecionar a página de destino

Antes de entrar no código, é necessário explorar a página de destino para ver que informações oferece e como as pode obter. Em particular, é necessário identificar quais os elementos HTML que contêm os dados de interesse e conceber estratégias de seleção adequadas.

Para simular as condições em que o Selenium funciona, ou seja, uma sessão de navegador “vanilla”, abra a página do Reddit incógnito. Clique com o botão direito do rato numa seção qualquer da página e clique em “Inspecionar” para abrir as ferramentas de desenvolvimento (DevTools) de Chrome:

Esta ferramenta ajuda-o a compreender a estrutura DOM da página. Como pode ver, o sítio depende de classes CSS que parecem ser geradas aleatoriamente no momento da criação. Por outras palavras, não deve basear as suas estratégias de seleção nelas.

Continuação da raspagem de dados do Reddit

Felizmente, os elementos mais importantes do sítio têm atributos HTML especiais. Por exemplo, o nó de descrição do subreddit tem o seguinte atributo:

data-testid="no-edit-description-block"

Esta é uma informação útil para criar uma lógica de seleção de elementos HTML eficaz.

Continue a analisar o sítio nas ferramentas de desenvolvimento e familiarize-se com o seu DOM até estar pronto para raspar Reddit em Python.

Passo 5: Extrair as informações principais do subreddit

Primeiro, crie um dicionário de Python onde armazenar os dados raspados:

subreddit = {}

Depois, note que pode obter o nome do subreddit a partir do elemento

no topo da página:

Recupere-o da seguinte forma:


name = driver \
    .find_element(By.TAG_NAME, 'h1') \
    .text

Como já deve ter reparado, algumas das informações gerais mais interessantes sobre o subreddit estão na barra lateral à direita:

Barra lateral do Reddit

Pode obter a descrição do texto, a data de criação e o número de membros com:


description = driver \
    .find_element(By.CSS_SELECTOR, '[data-testid="no-edit-description-block"]') \
    .get_attribute('innerText')

creation_date = driver \
    .find_element(By.CSS_SELECTOR, '.icon-cake') \
    .find_element(By.XPATH, "following-sibling::*[1]") \
    .get_attribute('innerText') \
    .replace('Created ', '')

members = driver \
    .find_element(By.CSS_SELECTOR, '[id^="IdCard--Subscribers"]') \
    .find_element(By.XPATH, "preceding-sibling::*[1]") \
    .get_attribute('innerText')

Neste caso, não é possível utilizar o atributo text porque as cadeias de texto estão contidas em nós aninhados. Se utilizasse .text, obteria uma cadeia de caracteres vazia. Em vez disso, é necessário chamar o método get_attribute() para ler o atributo innerText, que devolve o conteúdo de texto processado de um nó e dos seus descendentes.

Se olhar para o elemento data de criação, verá que não existe uma forma fácil de o selecionar. Como é o nó a seguir ao ícone do bolo, selecione primeiro o ícone com .icon-cake e, em seguida, utilize a expressão XPath following-sibling::*[1] para obter o irmão seguinte. Limpe o texto raspado para remover a cadeia “Created” (Criado) chamando o método replace() de Python.

Quando se trata do elemento do contador do membro subscritor, acontece algo semelhante. A principal diferença é que é necessário aceder ao irmão anterior, neste caso.

Não se esqueça de adicionar os dados coletados ao dicionário do subreddit:


subreddit['name'] = name
subreddit['description'] = description
subreddit['creation_date'] = creation_date
subreddit['members'] = members

Imprima o subreddit com print(subreddit), e verá:

{'name': '/r/Technology', 'description': 'Subreddit dedicated to the news and discussions about the creation and use of technology and its surrounding issues.', 'creation_date': 'Jan 25, 2008', 'members': '14.4m'}

Perfeito! Acabou de raspar a web com Python!

Passo 6: Extrair as publicações do subreddit

Uma vez que um subreddit apresenta várias mensagens, vai precisar de uma matriz para armazenar os dados coletados:

posts = []

Inspecionar um elemento HTML de publicação:

Aqui pode ver que pode selecioná-los a todos com o botão [data-testid="post-container"] Seletor CSS:


post_html_elements = driver \
    .find_elements(By.CSS_SELECTOR, '[data-testid="post-container"]')

Iterar sobre eles. Para cada elemento, crie um dicionário de publicações para manter o registo dos dados de cada publicação:


for post_html_element in post_html_elements:
    post = {}

    # scraping logic...

Inspecione o elemento upvote:

inspecionar upvote

Pode obter essa informação dentro do ciclo for com:


upvotes = post_html_element \
    .find_element(By.CSS_SELECTOR, '[data-click-id="upvote"]') \
    .find_element(By.XPATH, "following-sibling::*[1]") \
    .get_attribute('innerText')

Mais uma vez, o melhor é usar o botão “upvote”, que é fácil de selecionar, e depois apontar para o irmão seguinte para obter a informação alvo.

Inspecione os elementos autor e título da publicação:

Obter estes dados é um pouco mais fácil:


author = post_html_element \
    .find_element(By.CSS_SELECTOR, '[data-testid="post_author_link"]') \
    .text

title = post_html_element \
    .find_element(By.TAG_NAME, 'h3') \
    .text

Depois, pode coletar o número de comentários e link de saída:

Comentários e links externos

try:
    outbound_link = post_html_element \
        .find_element(By.CSS_SELECTOR, '[data-testid="outbound-link"]') \
        .get_attribute('href')
except NoSuchElementException:
    outbound_link = None

comments = post_html_element \
    .find_element(By.CSS_SELECTOR, '[data-click-id="comments"]') \
    .get_attribute('innerText') \
    .replace(' Comments', '')

Uma vez que o elemento de linkde saída é opcional, é necessário envolver a lógica de seleção com um bloco de tentativa.

Adicione estes dados à publicação e acrescente-os à matriz de publicações apenas se o título estiver presente. Este controlo adicional evita que as publicações de anúncios especiais colocadas por Reddit sejam raspadas:


# populate the dictionary with the retrieved data
post['upvotes'] = upvotes
post['title'] = title
post['outbound_link'] = outbound_link
post['comments'] = comments

# to avoid adding ad posts 
# to the list of scraped posts
if title:
    posts.append(post)

Por fim, adicione publicações ao dicionário do subreddit:

subreddit['posts'] = posts

Muito bem! Agora tem todos os dados desejados de Reddit!

Passo 7: Exportar os dados raspados para JSON

Os dados coletados estão agora dentro de um dicionário de Python. Este não é o melhor formato para o partilhar com outras equipas. Para o fazer, deve exportá-lo para JSON:


import json

# ...

with open('subreddit.json', 'w') as file:
    json.dump(video, file)

Importe json da biblioteca padrão de Python, crie um ficheiro subreddit.json com open(), e preencha-lo com json.dump(). Confira nosso guia para saber mais sobre como analisar JSON em Python.

Fantástico! Começou com dados em bruto contidos numa página HTML dinâmica e agora tem dados JSON semiestruturados. Está agora pronto para ver todo o raspador de Reddit.

Passo 8: Juntar tudo

Aqui está o script scraper.py completo:


from selenium import webdriver
from selenium.common import NoSuchElementException
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
import json

# enable the headless mode
options = Options()
options.add_argument('--headless=new')

# initialize a web driver to control Chrome
driver = webdriver.Chrome(
    service=ChromeService(ChromeDriverManager().install()),
    options=options
)
# maxime the controlled browser window
driver.fullscreen_window()

# the URL of the target page to scrape
url = 'https://www.reddit.com/r/technology/top/?t=week'
# connect to the target URL in Selenium
driver.get(url)

# initialize the dictionary that will contain
# the subreddit scraped data
subreddit = {}

# subreddit scraping logic
name = driver \
    .find_element(By.TAG_NAME, 'h1') \
    .text

description = driver \
    .find_element(By.CSS_SELECTOR, '[data-testid="no-edit-description-block"]') \
    .get_attribute('innerText')

creation_date = driver \
    .find_element(By.CSS_SELECTOR, '.icon-cake') \
    .find_element(By.XPATH, "following-sibling::*[1]") \
    .get_attribute('innerText') \
    .replace('Created ', '')

members = driver \
    .find_element(By.CSS_SELECTOR, '[id^="IdCard--Subscribers"]') \
    .find_element(By.XPATH, "preceding-sibling::*[1]") \
    .get_attribute('innerText')

# add the scraped data to the dictionary
subreddit['name'] = name
subreddit['description'] = description
subreddit['creation_date'] = creation_date
subreddit['members'] = members

# to store the post scraped data
posts = []

# retrieve the list of post HTML elements
post_html_elements = driver \
    .find_elements(By.CSS_SELECTOR, '[data-testid="post-container"]')

for post_html_element in post_html_elements:
    # to store the data scraped from the
    # post HTML element
    post = {}

    # subreddit post scraping logic
    upvotes = post_html_element \
        .find_element(By.CSS_SELECTOR, '[data-click-id="upvote"]') \
        .find_element(By.XPATH, "following-sibling::*[1]") \
        .get_attribute('innerText')

    author = post_html_element \
        .find_element(By.CSS_SELECTOR, '[data-testid="post_author_link"]') \
        .text

    title = post_html_element \
        .find_element(By.TAG_NAME, 'h3') \
        .text

    try:
        outbound_link = post_html_element \
            .find_element(By.CSS_SELECTOR, '[data-testid="outbound-link"]') \
            .get_attribute('href')
    except NoSuchElementException:
        outbound_link = None

    comments = post_html_element \
        .find_element(By.CSS_SELECTOR, '[data-click-id="comments"]') \
        .get_attribute('innerText') \
        .replace(' Comments', '')

    # populate the dictionary with the retrieved data
    post['upvotes'] = upvotes
    post['title'] = title
    post['outbound_link'] = outbound_link
    post['comments'] = comments

    # to avoid adding ad posts 
    # to the list of scraped posts
    if title:
        posts.append(post)

subreddit['posts'] = posts

# close the browser and free up the Selenium resources
driver.quit()

# export the scraped data to a JSON file
with open('subreddit.json', 'w', encoding='utf-8') as file:
    json.dump(subreddit, file, indent=4, ensure_ascii=False)

Fantástico! É possível criar um raspador web de Reddit em Python com pouco mais de 100 linhas de código!

Inicie o script, e o seguinte ficheiro subreddit.json aparecerá na pasta raiz do seu projeto:


{
    "name": "/r/Technology",
    "description": "Subreddit dedicated to the news and discussions about the creation and use of technology and its surrounding issues.",
    "creation_date": "Jan 25, 2008",
    "members": "14.4m",
    "posts": [
        {
            "upvotes": "63.2k",
            "title": "Mojang exits Reddit, says they '\"no longer feel that Reddit is an appropriate place to post official content or refer [its] players to\".",
            "outbound_link": "https://www.pcgamer.com/minecrafts-devs-exit-its-7-million-strong-subreddit-after-reddits-ham-fisted-crackdown-on-protest/",
            "comments": "2.9k"
        },
        {
            "upvotes": "35.7k",
            "title": "JP Morgan accidentally deletes evidence in multi-million record retention screwup",
            "outbound_link": "https://www.theregister.com/2023/06/26/jp_morgan_fined_for_deleting/",
            "comments": "2.0k"
        },
        # omitted for brevity ...        
        {
            "upvotes": "3.6k",
            "title": "Facebook content moderators in Kenya call the work 'torture.' Their lawsuit may ripple worldwide",
            "outbound_link": "https://techxplore.com/news/2023-06-facebook-content-moderators-kenya-torture.html",
            "comments": "188"
        },
        {
            "upvotes": "3.6k",
            "title": "Reddit is telling protesting mods their communities ‘will not’ stay private",
            "outbound_link": "https://www.theverge.com/2023/6/28/23777195/reddit-protesting-moderators-communities-subreddits-private-reopen",
            "comments": "713"
        }
    ]
}

Parabéns! Acabou de aprender a fazer raspagem de Reddit em Python!

Conclusão

A raspagem de Reddit é uma melhor forma de obter dados do que utilizar a sua API, especialmente após as novas políticas. Neste tutorial passo a passo, você aprendeu a construir um raspador em Python para recuperar dados do subreddit. Como mostrado aqui, requer apenas algumas linhas de código.

Ao mesmo tempo, tal como alteraram as suas políticas de API de um dia para o outro, Reddit poderá em breve implementar medidas rigorosas contra a raspagem. A extração de dados seria uma proeza, mas há uma solução! O Navegador de Raspagem da Bright Data é uma ferramenta que pode renderizar JavaScript da mesma forma que Selenium, enquanto lida automaticamente com impressões digitais, CAPTCHAs e antirraspagem por você.

Se isso não for de seu interesse, criámos um raspador de Reddit para satisfazer as suas necessidades. Graças a esta solução fiável e fácil de utilizar, pode obter todos os dados de Reddit que quiser sem preocupações.

Não quer lidar com a raspagem da web de Reddit, mas está interessado nos dados de subreddit? Adquira um conjunto de dados de Reddit.