01/05/2024

Consultando o WordPress, usando Python

WordPress é uma das plataformas mais populares do mundo para construção de Websites e blogs. A Ontologia da EximiaCo foi construída com WordPress.

Os consultores da EximiaCo colaboram com a produção de conteúdo que está devidamente organizado em classes.

Buscando dados do WordPress

O WordPress disponibiliza uma API RESTish que permite obtenção fácil de informações a respeito de um site.

Python
import pandas as pd
import requests
from cachetools import cached, TTLCache
from datetime import datetime


class WordPress:
    def __init__(self, url):
        self.url = url

    @cached(cache=TTLCache(maxsize=100, ttl=300))
    def get_post_types(self):
        endpoint = '/wp-json/wp/v2/types'
        api_url = f'{self.url}{endpoint}'

        response = requests.get(api_url)
        post_types = response.json()

        if response.status_code == 200:
            data = [{'key': key, 'name': value['name']} for key, value in post_types.items()]
            return pd.DataFrame(data)
        else:
            return pd.DataFrame()

    @cached(cache=TTLCache(maxsize=100, ttl=300))
    def get_users(self):
        api_url = f'{self.url}/wp-json/wp/v2/users'

        response = requests.get(api_url)

        if response.status_code == 200:
            users_data = response.json()
            df = pd.DataFrame(users_data)
            return df
        else:
            return pd.DataFrame()

    @cached(cache=TTLCache(maxsize=100, ttl=300))
    def get_posts(self, post_type_slug, start_date, end_date):

        start_iso = (datetime.strptime(start_date, '%Y-%m-%d')
                     .replace(hour=0, minute=0, second=0)
                     .strftime('%Y-%m-%dT%H:%M:%S'))
        end_iso = (datetime.strptime(end_date, '%Y-%m-%d')
                   .replace(hour=23, minute=59, second=59)
                   .strftime('%Y-%m-%dT%H:%M:%S'))

        endpoint = f'/wp-json/wp/v2/{post_type_slug}'
        api_url = f'{self.url}{endpoint}'

        params = {
            'after': start_iso,
            'before': end_iso,
            'date_gmt': ''
        }

        response = requests.get(api_url, params=params)
        posts = response.json()

        if response.status_code == 200 and isinstance(posts, list):
            data = [
                {'ID': post['id'],
                 'Title': post['title']['rendered'],
                 'Modified': post['modified'],
                 'Created': post['date_gmt'],
                 'AuthorId': post.get('author', 1)
                 } for post in posts]
            df = pd.DataFrame(data)
            return df
        else:
            return pd.DataFrame()

Tenho optado por retornar Dataframes do Pandas quase sempre. Isso torna a interação com os dados algo fácil.

Outra coisa que resolvi fazer nessa implementação foi utilizar um cache em memória mesmo. A ideia é reduzir a quantidade de “ataques” no servidor para informações que considero estáveis.

Facilitando as pesquisas envolvendo períodos semanais

Boa parte das consultas que irei fazer contra meu WordPress serão relacionadas a períodos semanais – ou seja, começando em um domingo e encerrando no próximo sábado. Para tornar a especificação desses intervalos mais fácil, criei uma classe helper.

Python
from datetime import timedelta, datetime


class Weeks:
    @staticmethod
    def get_week_dates(reference_date):
        start_of_week = reference_date - timedelta(days=reference_date.weekday() + 1)
        end_of_week = start_of_week + timedelta(days=6)
        return start_of_week.strftime('%Y-%m-%d'), end_of_week.strftime('%Y-%m-%d')

    @staticmethod
    def get_current():
        today = datetime.now()
        return Weeks.get_week_dates(today)

    @staticmethod
    def get_previous(n = 1):
        today = datetime.now()
        last_week_date = today - timedelta(days=7 * n)
        return Weeks.get_week_dates(last_week_date)

    @staticmethod
    def get_week_string_from_date(reference_date):
        start, end = Weeks.get_week_dates(reference_date)
        return Weeks.get_week_string_from_interval(start, end)

    @staticmethod
    def get_week_string_from_interval(start, end):
        star_f = datetime.fromisoformat(start).strftime('%d/%m')
        end_f = datetime.fromisoformat(end).strftime('%d/%m')
        return f'{star_f} - {end_f}'

Adicionando semântica

O WordPress é uma plataforma de propósito geral. A classe que desenvolvi consegue lidar bem com qualquer WordPress, mas deixa um bocado de interpretações a cargo do desenvolvedor.

Quase sempre, considero criar uma “camada semântica” sobre classes de propósito geral como a que desenvolvemos, adicionando significado específico para o contexto (domínio) em que estou trabalhando.

Python
import pandas as pd
from datetime import datetime

from src.wordpress import WordPress
from src.weeks import Weeks

class Ontologia:
    def __init__(self):
        self.wp = WordPress('https://ontologia.eximia.co')

    def get_classes(self):
        df = self.wp.get_post_types()
        classes = df[
            ~(df['key'].str.startswith('wp_') |
              df['key'].str.startswith('nav_') |
              df['key'].str.contains('jet-engine') |
              df['key'].str.contains('assets') |
              df['key'].str.contains('classes') |
              df['key'].str.contains('page') |
              df['key'].str.contains('post') |
              df['key'].str.contains('links') |
              df['key'].str.contains('media') |
              df['key'].str.contains('attachment') |
              df['key'].str.contains('visibility_preset') |
              df['key'].str.endswith('_links') |
              df['key'].str.endswith('-links')
              )
        ]
        return classes

    def get_authors(self):
        df = self.wp.get_users()
        return df[~(df['name'].str.contains('admin'))]

    def get_entries(self, start_date, end_date):
        classes_df = self.get_classes()
        entries_list = []

        for index, row in classes_df.iterrows():
            post_type_slug = row['key']
            posts_df = self.wp.get_posts(post_type_slug, start_date, end_date)

            posts_df['ClassKey'] = row['key']
            posts_df['ClassName'] = row['name']

            entries_list.append(posts_df)

        final_df = pd.concat(entries_list, ignore_index=True)
        final_df = final_df[~(final_df['AuthorId'] == 1)]

        user_name_map = self.get_authors().set_index('id')['name'].to_dict()
        final_df['AuthorName'] = final_df['AuthorId'].map(user_name_map)
        return final_df

    def get_summary_by_author(self, start_date, end_date):
        entries_df = self.get_entries(start_date, end_date)
        summary_df = entries_df.groupby('AuthorName').size().reset_index(name='Count')

        start_date_formatted = pd.to_datetime(start_date).strftime('%d/%m')
        end_date_formatted = pd.to_datetime(end_date).strftime('%d/%m')
        date_column_title = f"{start_date_formatted} - {end_date_formatted}"

        summary_df.columns = ['Author', date_column_title]

        return summary_df

    def get_summary_by_class(self, start_date, end_date):
        entries_df = self.get_entries(start_date, end_date)
        summary_df = entries_df.groupby('ClassName').size().reset_index(name='Count')

        start_date_formatted = pd.to_datetime(start_date).strftime('%d/%m')
        end_date_formatted = pd.to_datetime(end_date).strftime('%d/%m')
        date_column_title = f"{start_date_formatted} - {end_date_formatted}"

        summary_df.columns = ['Class', date_column_title]

        return summary_df

    def get_weekly_summary_by(self, by, alias, number_of_weeks):
        start, _ = Weeks.get_previous(number_of_weeks)
        _, end = Weeks.get_current()

        entries = self.get_entries(start, end)

        def week(row):
            ref = datetime.fromisoformat(row["Created"])
            return Weeks.get_week_string_from_date(ref)

        entries["Week"] = entries.apply(week, axis=1)

        result = pd.DataFrame(columns=[alias])

        for i in range(number_of_weeks):
            start, end = Weeks.get_previous(i)
            week = Weeks.get_week_string_from_interval(start, end)
            df = entries[entries['Week'] == week]

            summary_df = df.groupby(by).size().reset_index(name='Count')
            summary_df.columns = [alias, week]
            result = pd.merge(summary_df, result, on=alias, how='outer')

        result.fillna(0, inplace=True)

        return result

Aqui, a classe semântica substitui a ideia de “usuários” por “autores”, “tipos de postagem” por “classes”, “postagem” por “entradas”.

Além disso, adicionei algumas consultas de sumarização.

Deixe seu comentário

Subscribe
Notify of
0 Comentários
Oldest
Newest Most Voted
Feedbacks interativos
Ver todos os comentários

Últimos posts

0
Quero saber a sua opinião, deixe seu comentáriox

Inscrição realizada com sucesso!

No dia da masterclass você receberá um e-mail com um link para acompanhar a aula ao vivo. Até lá!

A sua subscrição foi enviada com sucesso!

Aguarde, em breve entraremos em contato com você para lhe fornecer mais informações sobre como participar da mentoria.

Crie sua conta

Preencha os dados para iniciar o seu cadastro no plano anual do Clube de Estudos:

Crie sua conta

Preencha os dados para iniciar o seu cadastro no plano mensal do Clube de Estudos:

× Precisa de ajuda?