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.
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.
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.
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.