From fc6bd9f2554d31a5903aba1baed1e3c92d425fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pelletier?= Date: Sat, 30 Nov 2024 22:03:49 -0500 Subject: [PATCH] =?UTF-8?q?Am=C3=A9liorations=20au=20moteur=20de=20recherc?= =?UTF-8?q?he?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- import_data/00_creer_data_repo.py | 4 +- import_data/00_creer_reseauxsociaux.py | 13 +- import_data/00_delete_collection.py | 3 +- ..._importation_facebook_page_publications.py | 28 ++-- ...12_importation_facebook_profil_comments.py | 18 +-- ...on_facebook_profil_uncategorized_photos.py | 29 ++-- .../21_importation_instagram_publications.py | 25 ++-- import_data/22_importation_instagram_reels.py | 30 ++-- .../23_importation_instagram_stories.py | 29 ++-- .../24_importation_instagram_post_comments.py | 25 ++-- ...25_importation_instagram_reels_comments.py | 25 ++-- import_data/26_importation_threads.py | 25 ++-- import_data/31_importation_linkedin_shares.py | 31 ++-- .../32_importation_linkedin_comments.py | 30 ++-- import_data/41_importation_wordpress.py | 32 ++--- import_data/51_importation_podcast.py | 20 +-- import_data/requirements.txt | 38 ++++- import_data/utils/documents_to_database.py | 61 ++++++++ search_app_ui/run_streamlit_app.py | 25 ++++ search_app_ui/streamlit_app.py | 135 +++++++++++++----- typesense_stats/typesense_stats.py | 106 ++++++++++++++ 21 files changed, 509 insertions(+), 223 deletions(-) create mode 100644 search_app_ui/run_streamlit_app.py create mode 100644 typesense_stats/typesense_stats.py diff --git a/import_data/00_creer_data_repo.py b/import_data/00_creer_data_repo.py index a12915e..da77fe4 100644 --- a/import_data/00_creer_data_repo.py +++ b/import_data/00_creer_data_repo.py @@ -1,11 +1,11 @@ import os import utils.reseau_social_data as rs_data -# %% Créer le répertoire data s'il n'existe pas +## %% Créer le répertoire data s'il n'existe pas if not os.path.exists('data'): os.makedirs('data') -# %% Créer les répertoires pour chaque réseau sociaux +## %% Créer les répertoires pour chaque réseau sociaux for reseau_social in rs_data.reseau_social_data: if not os.path.exists(f'data/{reseau_social["nom"]}/'): os.makedirs(f'data/{reseau_social["nom"]}/') diff --git a/import_data/00_creer_reseauxsociaux.py b/import_data/00_creer_reseauxsociaux.py index d5ba767..44849a7 100644 --- a/import_data/00_creer_reseauxsociaux.py +++ b/import_data/00_creer_reseauxsociaux.py @@ -2,7 +2,7 @@ from typesense.exceptions import TypesenseClientError, ObjectAlreadyExists from utils.typesense_client import client -# Create a collection +## %% Create a collection try: client.collections.create({ 'name': 'social_media_posts', @@ -13,10 +13,13 @@ try: {'name': 'index', 'type': 'string', 'facet': True}, {'name': 'chemin', 'type': 'string'}, {'name': 'texte', 'type': 'string'}, + {'name': 'langue', 'type': 'string', 'facet': True}, + {'name': 'texte_urls', 'type': 'string[]'}, + {'name': 'nombre_de_mots', 'type': 'int64'}, {'name': 'creation_timestamp', 'type': 'int64'}, { - "name" : "embedding", - "type" : "float[]", + "name": "embedding", + "type": "float[]", "embed": { "from": [ "texte" @@ -31,10 +34,8 @@ try: }) print("Collection 'social_media_posts' created successfully.") except TypesenseClientError as e: - if e==ObjectAlreadyExists: + if e == ObjectAlreadyExists: print("Collection 'social_media_posts' already exists. Skipping creation.") else: print(f"Error creating collection: {str(e)}") raise - - diff --git a/import_data/00_delete_collection.py b/import_data/00_delete_collection.py index 37c4f66..7fbd378 100644 --- a/import_data/00_delete_collection.py +++ b/import_data/00_delete_collection.py @@ -1,3 +1,4 @@ -# Utiliser au besoin seulement pour supprimer la collection 'social_media_posts' dans Typesense +## %% Utiliser au besoin seulement pour supprimer la collection 'social_media_posts' dans Typesense from utils.typesense_client import client +## %% client.collections['social_media_posts'].delete() \ No newline at end of file diff --git a/import_data/11_importation_facebook_page_publications.py b/import_data/11_importation_facebook_page_publications.py index da86afc..a09ce60 100644 --- a/import_data/11_importation_facebook_page_publications.py +++ b/import_data/11_importation_facebook_page_publications.py @@ -6,21 +6,18 @@ from pathlib import Path from utils.documents_to_database import documents_to_database from utils.convert_encoding_meta import convert_encoding_meta -#%% In[ ]: - -#%% In[ ]: - -# Get the current file's directory +## %% Déterminer le chemin du répertoire du script try: - # This will work when running as a script + # Ceci fonctionnera lors de l'exécution en tant que script script_dir = Path(__file__).parent.parent except NameError: - # This will work in interactive environments + # Ceci fonctionnera dans des environnements interactifs script_dir = Path().absolute() project_root = script_dir fb_data_path = [os.path.join(project_root, 'import_data', 'data', 'FacebookBusiness', 'posts', 'profile_posts_1.json')] +## %% Lire et parser le fichier JSON des posts Facebook try: with open(fb_data_path[0], "r", encoding="raw-unicode-escape") as posts: posts_json = json.loads(convert_encoding_meta(posts.read())) @@ -28,17 +25,18 @@ except Exception as e: print(f"Error reading JSON file: {e}") exit(1) -#%% In[ ]: +## %% Extraire les données pertinentes de chaque post posts_medias = [] for post in posts_json: - # data + # Extraire le texte du post data_post_items = post['data'] texte_post_list = [] for item in data_post_items: if item.get('post'): texte_post_list.append(item['post']) texte = "\n".join(texte_post_list) - # attachments + + # Traiter les pièces jointes du post for attachment in post['attachments']: if attachment.get('data'): for data_item in attachment['data']: @@ -52,14 +50,14 @@ for post in posts_json: "texte": texte, "creation_timestamp": media["creation_timestamp"]}) -#%% In[ ]: +## %% Créer un DataFrame à partir des données extraites posts_medias_df = pd.DataFrame(posts_medias) -#%% In[ ]: +## %% Remplacer les valeurs NaN par des chaînes vides posts_medias_df.fillna(value="", inplace=True) -#%% In[ ]: +## %% Supprimer les doublons basés sur le texte et le timestamp de création posts_medias_df.drop_duplicates(subset=['texte', 'creation_timestamp'], inplace=True) -#%% In[ ]: -documents_to_database(posts_medias_df) +## %% Envoyer les données à la base de données +documents_to_database(posts_medias_df) \ No newline at end of file diff --git a/import_data/12_importation_facebook_profil_comments.py b/import_data/12_importation_facebook_profil_comments.py index 6b06d6b..6c4c9c0 100644 --- a/import_data/12_importation_facebook_profil_comments.py +++ b/import_data/12_importation_facebook_profil_comments.py @@ -6,23 +6,23 @@ from pathlib import Path from utils.documents_to_database import documents_to_database from utils.convert_encoding_meta import convert_encoding_meta -#%% In[ ]: - -# Get the current file's directory +## %% Obtenir le répertoire du fichier courant +# Obtenir le répertoire du fichier courant try: - # This will work when running as a script + # Ceci fonctionnera lors de l'exécution en tant que script script_dir = Path(__file__).parent.parent except NameError: - # This will work in interactive environments + # Ceci fonctionnera dans des environnements interactifs script_dir = Path().absolute() project_root = script_dir fb_data_path = [os.path.join(project_root, 'import_data', 'data', 'Facebook', 'comments_and_reactions', 'comments.json')] +## %% Lire et parser le fichier JSON des commentaires Facebook with open(fb_data_path[0], "r", encoding="raw-unicode-escape") as posts: comments_json = json.loads(convert_encoding_meta(posts.read())) -#%% In[ ]: +## %% Extraire les données pertinentes de chaque commentaire facebook_comments = [] for comment in comments_json['comments_v2']: if comment.get('data'): @@ -36,8 +36,8 @@ for comment in comments_json['comments_v2']: "texte": comment["comment"], "creation_timestamp": comment["timestamp"]}) -#%% In[ ]: +## %% Créer un DataFrame à partir des données extraites facebook_comments_df = pd.DataFrame(facebook_comments) -#%% In[ ]: -documents_to_database(facebook_comments_df) +## %% Envoyer les données à la base de données +documents_to_database(facebook_comments_df) \ No newline at end of file diff --git a/import_data/13_importation_facebook_profil_uncategorized_photos.py b/import_data/13_importation_facebook_profil_uncategorized_photos.py index eed3501..d4510b4 100644 --- a/import_data/13_importation_facebook_profil_uncategorized_photos.py +++ b/import_data/13_importation_facebook_profil_uncategorized_photos.py @@ -6,46 +6,45 @@ from pathlib import Path from utils.documents_to_database import documents_to_database from utils.convert_encoding_meta import convert_encoding_meta -#%% In[ ]: - -# Get the current file's directory +## %% Obtenir le répertoire du fichier courant +# Obtenir le répertoire du fichier courant try: - # This will work when running as a script + # Ceci fonctionnera lors de l'exécution en tant que script script_dir = Path(__file__).parent.parent except NameError: - # This will work in interactive environments + # Ceci fonctionnera dans des environnements interactifs script_dir = Path().absolute() project_root = script_dir fb_data_path = os.path.join(project_root, 'import_data', 'data', 'Facebook', 'posts', 'your_uncategorized_photos.json') +## %% Lire et parser le fichier JSON des photos Facebook non catégorisées with open(fb_data_path, "r", encoding="raw-unicode-escape") as posts: photos_json = json.loads(convert_encoding_meta(posts.read())) -#%% In[ ]: +## %% Extraire les photos non catégorisées du JSON facebook_photos = photos_json['other_photos_v2'] -#%% In[ ]: +## %% Créer un DataFrame à partir des photos extraites facebook_photos_df = pd.DataFrame(facebook_photos) -#%% In[ ]: -# Filter out posts without a description +## %% Filtrer les publications sans description facebook_photos_df = facebook_photos_df[~facebook_photos_df['description'].isnull()] -#%% In[ ]: +## %% Ajouter des métadonnées au DataFrame facebook_photos_df['index'] = "rs_facebook_posts" facebook_photos_df['network'] = "Facebook" facebook_photos_df['type'] = "posts" facebook_photos_df['chemin'] = fb_data_path -#%% In[ ]: +## %% Renommer la colonne 'description' en 'texte' facebook_photos_df.rename(columns={"description": "texte"}, inplace=True) -#%% In[ ]: +## %% Supprimer la colonne 'media_metadata' del facebook_photos_df['media_metadata'] -#%% In[ ]: +## %% Remplacer les valeurs NaN par des chaînes vides facebook_photos_df.fillna(value="", inplace=True) -#%% In[ ]: -documents_to_database(facebook_photos_df) +## %% Envoyer les données à la base de données +documents_to_database(facebook_photos_df) \ No newline at end of file diff --git a/import_data/21_importation_instagram_publications.py b/import_data/21_importation_instagram_publications.py index 28dffe8..917f71a 100644 --- a/import_data/21_importation_instagram_publications.py +++ b/import_data/21_importation_instagram_publications.py @@ -6,23 +6,23 @@ from pathlib import Path from utils.documents_to_database import documents_to_database from utils.convert_encoding_meta import convert_encoding_meta -#%% In[ ]: -# Get the current file's directory +## %% Obtenir le répertoire du fichier courant +# Obtenir le répertoire du fichier courant try: - # This will work when running as a script + # Ceci fonctionnera lors de l'exécution en tant que script script_dir = Path(__file__).parent.parent except NameError: - # This will work in interactive environments + # Ceci fonctionnera dans des environnements interactifs script_dir = Path().absolute() project_root = script_dir instagram_data_path = os.path.join(project_root, 'import_data', 'data', 'Instagram', 'content', 'posts_1.json') - +## %% Lire et parser le fichier JSON des publications Instagram with open(instagram_data_path, "r", encoding="raw-unicode-escape") as posts: posts_json = json.loads(convert_encoding_meta(posts.read())) -#%% In[ ]: +## %% Extraire et structurer les données des publications Instagram posts_medias = [] for post in posts_json: medias = post['media'] @@ -56,18 +56,17 @@ for post in posts_json: "texte": title, "creation_timestamp": creation_timestamp}) -#%% In[ ]: +## %% Créer un DataFrame à partir des données extraites posts_medias_df = pd.DataFrame(posts_medias) -#%% In[ ]: +## %% Remplacer les valeurs NaN par des chaînes vides posts_medias_df.fillna(value="", inplace=True) -#%% In[ ]: +## %% Supprimer les doublons basés sur le texte et la date de création posts_medias_df.drop_duplicates(subset=['texte', 'creation_timestamp'], inplace=True) -#%% In[ ]: -# Filter empty texte +## %% Filtrer les publications avec un texte vide posts_medias_df = posts_medias_df[~posts_medias_df['texte'].str.strip().eq('')] -#%% In[ ]: -documents_to_database(posts_medias_df) +## %% Envoyer les données à la base de données +documents_to_database(posts_medias_df) \ No newline at end of file diff --git a/import_data/22_importation_instagram_reels.py b/import_data/22_importation_instagram_reels.py index 56d5ec7..1eb63c9 100644 --- a/import_data/22_importation_instagram_reels.py +++ b/import_data/22_importation_instagram_reels.py @@ -6,50 +6,50 @@ from pathlib import Path from utils.documents_to_database import documents_to_database from utils.convert_encoding_meta import convert_encoding_meta -#%% In[ ]: -# Get the current file's directory +## %% Obtenir le répertoire du fichier courant +# Obtenir le répertoire du fichier courant try: - # This will work when running as a script + # Ceci fonctionnera lors de l'exécution en tant que script script_dir = Path(__file__).parent.parent except NameError: - # This will work in interactive environments + # Ceci fonctionnera dans des environnements interactifs script_dir = Path().absolute() project_root = script_dir instagram_data_path = os.path.join(project_root, 'import_data', 'data', 'Instagram', 'content', 'reels.json') +## %% Lire et parser le fichier JSON des reels Instagram with open(instagram_data_path, "r", encoding="raw-unicode-escape") as posts: reels_json = json.loads(convert_encoding_meta(posts.read())) -#%% In[ ]: +## %% Extraire les données média des reels ig_reels_media = [x['media'][0] for x in reels_json['ig_reels_media']] -#%% In[ ]: +## %% Créer un DataFrame à partir des données extraites ig_reels_df = pd.DataFrame(ig_reels_media) -#%% In[ ]: +## %% Ajouter des colonnes supplémentaires au DataFrame ig_reels_df['index'] = "rs_instagram_content" ig_reels_df['type'] = "reels" ig_reels_df['network'] = "Instagram" ig_reels_df['chemin'] = instagram_data_path -#%% In[ ]: +## %% Renommer la colonne 'title' en 'texte' ig_reels_df.rename(columns={"title": "texte"}, inplace=True) -#%% In[ ]: +## %% Supprimer les colonnes non nécessaires del ig_reels_df['media_metadata'] del ig_reels_df['cross_post_source'] del ig_reels_df['dubbing_info'] -#%% In[ ]: +## %% Remplacer les valeurs NaN par des chaînes vides ig_reels_df.fillna(value="", inplace=True) -#%% In[ ]: +## %% Supprimer les doublons basés sur le texte et la date de création ig_reels_df.drop_duplicates(subset=['texte', 'creation_timestamp'], inplace=True) -#%% In[ ]: -# Filter empty texte +## %% Filtrer les reels avec un texte vide ig_reels_df = ig_reels_df[~ig_reels_df['texte'].str.strip().eq('')] -#%% In[ ]: -documents_to_database(ig_reels_df) +## %% Envoyer les données à la base de données +documents_to_database(ig_reels_df) \ No newline at end of file diff --git a/import_data/23_importation_instagram_stories.py b/import_data/23_importation_instagram_stories.py index b74739c..53a92e6 100644 --- a/import_data/23_importation_instagram_stories.py +++ b/import_data/23_importation_instagram_stories.py @@ -6,47 +6,48 @@ from pathlib import Path from utils.documents_to_database import documents_to_database from utils.convert_encoding_meta import convert_encoding_meta -#%% In[ ]: -# Get the current file's directory +## %% Obtenir le répertoire du fichier courant +# Obtenir le répertoire du fichier courant try: - # This will work when running as a script + # Ceci fonctionnera lors de l'exécution en tant que script script_dir = Path(__file__).parent.parent except NameError: - # This will work in interactive environments + # Ceci fonctionnera dans des environnements interactifs script_dir = Path().absolute() project_root = script_dir instagram_data_path = os.path.join(project_root, 'import_data', 'data', 'Instagram', 'content', 'stories.json') + +## %% Lire et parser le fichier JSON des stories Instagram with open(instagram_data_path, "r", encoding="raw-unicode-escape") as posts: stories_json = json.loads(convert_encoding_meta(posts.read())) -#%% In[ ]: +## %% Créer un DataFrame à partir des données des stories ig_stories_df = pd.DataFrame(stories_json['ig_stories']) -#%% In[ ]: +## %% Ajouter des colonnes supplémentaires au DataFrame ig_stories_df['index'] = "rs_instagram_content" ig_stories_df['type'] = "stories" ig_stories_df['network'] = "Instagram" ig_stories_df['chemin'] = instagram_data_path -#%% In[ ]: +## %% Renommer la colonne 'title' en 'texte' ig_stories_df.rename(columns={"title": "texte"}, inplace=True) -#%% In[ ]: +## %% Supprimer les colonnes non nécessaires del ig_stories_df['media_metadata'] del ig_stories_df['cross_post_source'] del ig_stories_df['ai_stickers'] del ig_stories_df['dubbing_info'] -#%% In[ ]: +## %% Remplacer les valeurs NaN par des chaînes vides ig_stories_df.fillna(value="", inplace=True) -#%% In[ ]: +## %% Supprimer les doublons basés sur le texte et la date de création ig_stories_df.drop_duplicates(subset=['texte', 'creation_timestamp'], inplace=True) -#%% In[ ]: -# Filter empty texte +## %% Filtrer les stories avec un texte vide ig_stories_df = ig_stories_df[~ig_stories_df['texte'].str.strip('\n').str.strip().eq('')] -#%% In[ ]: -documents_to_database(ig_stories_df) +## %% Envoyer les données à la base de données +documents_to_database(ig_stories_df) \ No newline at end of file diff --git a/import_data/24_importation_instagram_post_comments.py b/import_data/24_importation_instagram_post_comments.py index 9721595..95585e0 100644 --- a/import_data/24_importation_instagram_post_comments.py +++ b/import_data/24_importation_instagram_post_comments.py @@ -6,22 +6,23 @@ from pathlib import Path from utils.documents_to_database import documents_to_database from utils.convert_encoding_meta import convert_encoding_meta -#%% In[ ]: -# Get the current file's directory +## %% Obtenir le répertoire du fichier courant +# Obtenir le répertoire du fichier courant try: - # This will work when running as a script + # Ceci fonctionnera lors de l'exécution en tant que script script_dir = Path(__file__).parent.parent except NameError: - # This will work in interactive environments + # Ceci fonctionnera dans des environnements interactifs script_dir = Path().absolute() project_root = script_dir instagram_data_path = os.path.join(project_root, 'import_data', 'data', 'Instagram', 'comments', 'post_comments_1.json') +## %% Lire et parser le fichier JSON des commentaires Instagram with open(instagram_data_path, "r", encoding="raw-unicode-escape") as posts: post_comments_1 = json.loads(convert_encoding_meta(posts.read())) -#%% In[ ]: +## %% Extraire les données pertinentes de chaque commentaire ig_comments = [] for comment in post_comments_1: ig_comments.append({"texte": comment['string_map_data']['Comment']['value'], @@ -31,18 +32,18 @@ for comment in post_comments_1: "type": "comments", "network": "Instagram"}) -#%% In[ ]: +## %% Créer un DataFrame à partir des données extraites ig_comments_df = pd.DataFrame(ig_comments) -#%% In[ ]: +## %% Remplacer les valeurs NaN par des chaînes vides ig_comments_df.fillna(value="", inplace=True) -#%% In[ ]: +## %% Supprimer les doublons basés sur le texte et la date de création ig_comments_df.drop_duplicates(subset=['texte', 'creation_timestamp'], inplace=True) -#%% In[ ]: -# Filter empty texte +## %% Filtrer les commentaires avec un texte vide +# Filtrer les textes vides ig_comments_df = ig_comments_df[~ig_comments_df['texte'].str.strip('\n').str.strip().eq('')] -#%% In[ ]: -documents_to_database(ig_comments_df) +## %% Envoyer les données à la base de données +documents_to_database(ig_comments_df) \ No newline at end of file diff --git a/import_data/25_importation_instagram_reels_comments.py b/import_data/25_importation_instagram_reels_comments.py index 5149616..c474bc7 100644 --- a/import_data/25_importation_instagram_reels_comments.py +++ b/import_data/25_importation_instagram_reels_comments.py @@ -6,22 +6,23 @@ from pathlib import Path from utils.documents_to_database import documents_to_database from utils.convert_encoding_meta import convert_encoding_meta -#%% In[ ]: -# Get the current file's directory +## %% Obtenir le répertoire du fichier courant +# Obtenir le répertoire du fichier courant try: - # This will work when running as a script + # Ceci fonctionnera lors de l'exécution en tant que script script_dir = Path(__file__).parent.parent except NameError: - # This will work in interactive environments + # Ceci fonctionnera dans des environnements interactifs script_dir = Path().absolute() project_root = script_dir instagram_data_path = os.path.join(project_root, 'import_data', 'data', 'Instagram', 'comments', 'reels_comments.json') +## %% Lire et parser le fichier JSON des commentaires Instagram Reels with open(instagram_data_path, "r", encoding="raw-unicode-escape") as posts: reels_comments = json.loads(convert_encoding_meta(posts.read())) -#%% In[ ]: +## %% Extraire les données pertinentes de chaque commentaire Reels ig_comments = [] for comment in reels_comments['comments_reels_comments']: ig_comments.append({"texte": comment['string_map_data']['Comment']['value'], @@ -31,18 +32,18 @@ for comment in reels_comments['comments_reels_comments']: "type": "comments", "network": "Instagram"}) -#%% In[ ]: +## %% Créer un DataFrame à partir des données extraites ig_comments_df = pd.DataFrame(ig_comments) -#%% In[ ]: +## %% Remplacer les valeurs NaN par des chaînes vides ig_comments_df.fillna(value="", inplace=True) -#%% In[ ]: +## %% Supprimer les doublons basés sur le texte et la date de création ig_comments_df.drop_duplicates(subset=['texte', 'creation_timestamp'], inplace=True) -#%% In[ ]: -# Filter empty texte +## %% Filtrer les commentaires avec un texte vide +# Filtrer les textes vides ig_comments_df = ig_comments_df[~ig_comments_df['texte'].str.strip('\n').str.strip().eq('')] -#%% In[ ]: -documents_to_database(ig_comments_df) +## %% Envoyer les données à la base de données +documents_to_database(ig_comments_df) \ No newline at end of file diff --git a/import_data/26_importation_threads.py b/import_data/26_importation_threads.py index 573650e..a04de77 100644 --- a/import_data/26_importation_threads.py +++ b/import_data/26_importation_threads.py @@ -6,22 +6,23 @@ from pathlib import Path from utils.documents_to_database import documents_to_database from utils.convert_encoding_meta import convert_encoding_meta -#%% In[ ]: -# Get the current file's directory +## %% Obtenir le répertoire du fichier courant +# Obtenir le répertoire du fichier courant try: - # This will work when running as a script + # Ceci fonctionnera lors de l'exécution en tant que script script_dir = Path(__file__).parent.parent except NameError: - # This will work in interactive environments + # Ceci fonctionnera dans des environnements interactifs script_dir = Path().absolute() project_root = script_dir instagram_data_path = os.path.join(project_root, 'import_data', 'data', 'Instagram', 'threads', 'threads_and_replies.json') +## %% Lire et parser le fichier JSON des posts Threads with open(instagram_data_path, "r") as posts: post_comments_1 = json.loads(convert_encoding_meta(posts.read())) -#%% In[ ]: +## %% Extraire les données pertinentes de chaque post Threads threads_comments = [] for post in post_comments_1['text_post_app_text_posts']: for element in post['media']: @@ -32,18 +33,18 @@ for post in post_comments_1['text_post_app_text_posts']: "type": "posts", "network": "Threads"}) -#%% In[ ]: +## %% Créer un DataFrame à partir des données extraites ig_comments_df = pd.DataFrame(threads_comments) -#%% In[ ]: +## %% Remplacer les valeurs NaN par des chaînes vides ig_comments_df.fillna(value="", inplace=True) -#%% In[ ]: +## %% Supprimer les doublons basés sur le texte et la date de création ig_comments_df.drop_duplicates(subset=['texte', 'creation_timestamp'], inplace=True) -#%% In[ ]: -# Filter empty texte +## %% Filtrer les posts avec un texte vide +# Filtrer les textes vides ig_comments_df = ig_comments_df[~ig_comments_df['texte'].str.strip('\n').str.strip().eq('')] -#%% In[ ]: -documents_to_database(ig_comments_df) +## %% Envoyer les données à la base de données +documents_to_database(ig_comments_df) \ No newline at end of file diff --git a/import_data/31_importation_linkedin_shares.py b/import_data/31_importation_linkedin_shares.py index 52094b8..b7b61c3 100644 --- a/import_data/31_importation_linkedin_shares.py +++ b/import_data/31_importation_linkedin_shares.py @@ -6,13 +6,13 @@ from pathlib import Path from utils.documents_to_database import documents_to_database -#%% In[ ]: -# Get the current file's directory +## %% Obtenir le répertoire du fichier courant +# Obtenir le répertoire du fichier courant try: - # This will work when running as a script + # Ceci fonctionnera lors de l'exécution en tant que script script_dir = Path(__file__).parent.parent except NameError: - # This will work in interactive environments + # Ceci fonctionnera dans des environnements interactifs script_dir = Path().absolute() project_root = script_dir @@ -20,39 +20,38 @@ linkedin_data_path = os.path.join(project_root, 'import_data', 'data', 'LinkedIn raw_shares = pd.read_csv(linkedin_data_path) -#%% In[ ]: +## %% Ajouter des colonnes d'identification au DataFrame raw_shares['index'] = "rs_linkedin_shares" raw_shares['type'] = "posts" raw_shares['network'] = "LinkedIn" raw_shares['chemin'] = linkedin_data_path -#%% In[ ]: +## %% Convertir la date en timestamp et supprimer la colonne originale raw_shares["creation_timestamp"] = raw_shares["Date"].apply( lambda x: int(datetime.datetime.fromisoformat(x).timestamp()) ) del raw_shares["Date"] -#%% In[ ]: +## %% Renommer les colonnes pour correspondre au format standard raw_shares.rename(columns={"ShareLink": "uri", "ShareCommentary": "texte"}, inplace=True) -#%% In[ ]: +## %% Convertir la colonne 'texte' en type string raw_shares["texte"] = raw_shares["texte"].apply(lambda x: str(x)) -#%% In[ ]: +## %% Supprimer les colonnes non nécessaires del raw_shares["SharedUrl"] del raw_shares["MediaUrl"] del raw_shares["Visibility"] -#%% In[ ]: +## %% Remplacer les valeurs NaN par des chaînes vides raw_shares.fillna(value="", inplace=True) -#%% In[ ]: +## %% Supprimer les doublons basés sur le texte et la date de création raw_shares.drop_duplicates(subset=['texte', 'creation_timestamp'], inplace=True) -#%% In[ ]: -# Filter empty texte +## %% Filtrer les partages avec un texte vide +# Filtrer les textes vides raw_shares = raw_shares[~raw_shares['texte'].str.strip('\n').str.strip().eq('')] -#%% In[ ]: -documents_to_database(raw_shares) - +## %% Envoyer les données à la base de données +documents_to_database(raw_shares) \ No newline at end of file diff --git a/import_data/32_importation_linkedin_comments.py b/import_data/32_importation_linkedin_comments.py index e235afc..51a80a3 100644 --- a/import_data/32_importation_linkedin_comments.py +++ b/import_data/32_importation_linkedin_comments.py @@ -6,19 +6,19 @@ from pathlib import Path from utils.documents_to_database import documents_to_database -#%% In[ ]: -# Get the current file's directory +## %% Obtenir le répertoire du fichier courant +# Obtenir le répertoire du fichier courant try: - # This will work when running as a script + # Ceci fonctionnera lors de l'exécution en tant que script script_dir = Path(__file__).parent.parent except NameError: - # This will work in interactive environments + # Ceci fonctionnera dans des environnements interactifs script_dir = Path().absolute() project_root = script_dir linkedin_data_path = os.path.join(project_root, 'import_data', 'data', 'LinkedIn', 'comments', 'Comments.csv') -#%% In[ ]: +## %% Lire et nettoyer les données CSV des commentaires LinkedIn raw_comments_csv = pd.read_csv(linkedin_data_path, escapechar='\\', skipinitialspace=True) @@ -26,33 +26,33 @@ raw_comments_csv['MessageFix'] = raw_comments_csv['Message'].str.replace(r'[\r\n raw_comments_csv = raw_comments_csv.drop(columns=['Message']) raw_comments = raw_comments_csv[(raw_comments_csv['MessageFix'] != "")].drop_duplicates() -#%% In[ ]: +## %% Ajouter des colonnes d'identification au DataFrame raw_comments['index'] = "rs_linkedin_comments" raw_comments['type'] = "comments" raw_comments['network'] = "LinkedIn" raw_comments['chemin'] = linkedin_data_path -#%% In[ ]: +## %% Convertir la date en timestamp et supprimer la colonne originale raw_comments["creation_timestamp"] = raw_comments["Date"].apply( lambda x: int(datetime.datetime.fromisoformat(x).timestamp()) ) del raw_comments["Date"] -#%% In[ ]: +## %% Renommer les colonnes pour correspondre au format standard raw_comments.rename(columns={"Link": "uri", "MessageFix": "texte"}, inplace=True) -#%% In[ ]: +## %% Ajouter le chemin du fichier source raw_comments["chemin"] = linkedin_data_path -#%% In[ ]: +## %% Remplacer les valeurs NaN par des chaînes vides raw_comments.fillna(value="", inplace=True) -#%% In[ ]: +## %% Supprimer les doublons basés sur le texte et la date de création raw_comments.drop_duplicates(subset=['texte', 'creation_timestamp'], inplace=True) -#%% In[ ]: -# Filter empty texte +## %% Filtrer les commentaires avec un texte vide +# Filtrer les textes vides raw_comments = raw_comments[~raw_comments['texte'].str.strip('\n').str.strip().eq('')] -#%% In[ ]: -documents_to_database(raw_comments) +## %% Envoyer les données à la base de données +documents_to_database(raw_comments) \ No newline at end of file diff --git a/import_data/41_importation_wordpress.py b/import_data/41_importation_wordpress.py index a3e9110..424ac0c 100644 --- a/import_data/41_importation_wordpress.py +++ b/import_data/41_importation_wordpress.py @@ -10,13 +10,13 @@ from pathlib import Path from utils.documents_to_database import documents_to_database -#%% In[ ]: -# Get the current file's directory +## %% Obtenir le répertoire du fichier courant +# Obtenir le répertoire du fichier courant try: - # This will work when running as a script + # Ceci fonctionnera lors de l'exécution en tant que script script_dir = Path(__file__).parent.parent except NameError: - # This will work in interactive environments + # Ceci fonctionnera dans des environnements interactifs script_dir = Path().absolute() project_root = script_dir @@ -25,22 +25,21 @@ wordpress_xml_path = os.path.join(project_root, 'import_data', 'data', 'Wordpres with open(wordpress_xml_path, "r") as xml_file: wordpress_xml = xml_file.read() -#%% In[ ]: +## %% Convertir le XML en dictionnaire Python wordpress_dict = xmltodict.parse(wordpress_xml) -#%% In[ ]: +## %% Créer un DataFrame à partir des éléments du canal RSS items_df = pd.DataFrame(wordpress_dict['rss']['channel']['item']) -#%% In[ ]: +## %% Filtrer les éléments pour ne garder que les pages et les articles publiés items_df_filter = items_df[ (items_df['wp:post_type'].isin(['page', 'post'])) & (items_df['wp:status'] == 'publish')].copy() -#%% In[ ]: +## %% Convertir la date de publication en timestamp items_df_filter['creation_timestamp'] = items_df_filter['wp:post_date'].apply( lambda x: int(datetime.datetime.fromisoformat(x).timestamp())) - -#%% In[ ]: +## %% Définir une fonction pour convertir le contenu WordPress en Markdown def wp_to_markdown(x): try: md_text = re.sub(r'\n+', ' ', markdownify.markdownify(x, heading_style='ATX')).strip() @@ -50,22 +49,21 @@ def wp_to_markdown(x): pass return md_text - -#%% In[ ]: +## %% Appliquer la conversion Markdown au contenu items_df_filter['texte'] = items_df_filter['content:encoded'].apply(lambda x: wp_to_markdown(x)) -#%% In[ ]: +## %% Renommer les colonnes pour correspondre au format standard items_df_filter.rename(columns={"link": "uri", "wp:post_type": "type"}, inplace=True) -#%% In[ ]: +## %% Ajouter des colonnes d'identification items_df_filter['index'] = "rs_wordpress_jevalideca" items_df_filter['network'] = "Wordpress" items_df_filter['chemin'] = wordpress_xml_path -#%% In[ ]: +## %% Remplacer les valeurs NaN par des chaînes vides items_df_filter.fillna(value="", inplace=True) -#%% In[ ]: +## %% Envoyer les données à la base de données documents_to_database(items_df_filter[['title', 'uri', 'type', @@ -73,4 +71,4 @@ documents_to_database(items_df_filter[['title', 'texte', 'index', 'network', - 'chemin']]) + 'chemin']]) \ No newline at end of file diff --git a/import_data/51_importation_podcast.py b/import_data/51_importation_podcast.py index 1fb1495..3ddea81 100644 --- a/import_data/51_importation_podcast.py +++ b/import_data/51_importation_podcast.py @@ -3,7 +3,8 @@ from pathlib import Path from tqdm import tqdm from faster_whisper import WhisperModel -# Get the current file's directory +## %% Configuration du chemin et des répertoires +# Obtenir le répertoire du fichier courant try: script_dir = Path(__file__).parent.parent except NameError: @@ -13,15 +14,17 @@ project_root = script_dir podcast_dir = os.path.join(project_root, 'import_data', 'data', 'Podcast', 'audio') output_dir = os.path.join(project_root, 'import_data', 'data', 'Podcast', 'transcripts') -# Create output directory if it doesn't exist +# Créer le répertoire de sortie s'il n'existe pas os.makedirs(output_dir, exist_ok=True) -# Load Faster-Whisper model +## %% Chargement du modèle Faster-Whisper +# Charger le modèle Faster-Whisper model = WhisperModel("small", device="cpu", compute_type="int8") +## %% Définition des fonctions de transcription et de création de fichiers SRT def transcribe_audio(audio_path): l_segments, _ = model.transcribe(audio_path, language="fr", task="transcribe") - return list(l_segments) # Convert generator to list + return list(l_segments) # Convertir le générateur en liste def create_srt(l_segments, output_path): with open(output_path, 'w', encoding='utf-8') as f: @@ -38,15 +41,16 @@ def format_time(seconds): milliseconds = int((seconds % 1) * 1000) return f"{hours:02d}:{minutes:02d}:{seconds:02d},{milliseconds:03d}" -# Process all MP3 files +## %% Traitement de tous les fichiers MP3 +# Traiter tous les fichiers MP3 for filename in tqdm(os.listdir(podcast_dir)): if filename.endswith(".mp3"): mp3_path = os.path.join(podcast_dir, filename) srt_path = os.path.join(output_dir, filename.replace(".mp3", ".srt")) - print(f"Transcribing {filename}...") + print(f"Transcription de {filename}...") segments = transcribe_audio(mp3_path) create_srt(segments, srt_path) - print(f"Transcription saved to {srt_path}") + print(f"Transcription sauvegardée dans {srt_path}") -print("All podcasts have been transcribed.") \ No newline at end of file +print("Tous les podcasts ont été transcrits.") \ No newline at end of file diff --git a/import_data/requirements.txt b/import_data/requirements.txt index 138e505..6446f73 100644 --- a/import_data/requirements.txt +++ b/import_data/requirements.txt @@ -1,17 +1,28 @@ altair==5.5.0 +annotated-types==0.7.0 attrs==24.2.0 av==13.1.0 beautifulsoup4==4.12.3 blinker==1.9.0 +blis==1.0.1 cachetools==5.5.0 +catalogue==2.0.10 certifi==2024.8.30 charset-normalizer==3.4.0 click==8.1.7 +cloudpathlib==0.20.0 coloredlogs==15.0.1 +confection==0.1.5 +contourpy==1.3.1 ctranslate2==4.5.0 +cycler==0.12.1 +cymem==2.0.10 +en_core_web_sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl#sha256=1932429db727d4bff3deed6b34cfc05df17794f4a52eeb26cf8928f7c1a0fb85 faster-whisper==1.1.0 filelock==3.16.1 flatbuffers==24.3.25 +fonttools==4.55.0 +fr_core_news_sm @ https://github.com/explosion/spacy-models/releases/download/fr_core_news_sm-3.8.0/fr_core_news_sm-3.8.0-py3-none-any.whl#sha256=7d6ad14cd5078e53147bfbf70fb9d433c6a3865b695fda2657140bbc59a27e29 fsspec==2024.10.0 gitdb==4.0.11 GitPython==3.1.43 @@ -21,22 +32,33 @@ idna==3.10 Jinja2==3.1.4 jsonschema==4.23.0 jsonschema-specifications==2024.10.1 +kiwisolver==1.4.7 +langcodes==3.5.0 +langdetect==1.0.9 +language_data==1.3.0 +marisa-trie==1.2.1 markdown-it-py==3.0.0 markdownify==0.11.6 MarkupSafe==3.0.2 +matplotlib==3.9.3 mdurl==0.1.2 mpmath==1.3.0 +murmurhash==1.0.11 narwhals==1.15.0 -numpy==1.26.4 +numpy==2.0.2 onnxruntime==1.20.1 packaging==24.2 -pandas==2.2.0 +pandas==2.2.3 pillow==11.0.0 plotly==5.24.1 +preshed==3.0.9 protobuf==5.29.0 pyarrow==17.0.0 +pydantic==2.10.2 +pydantic_core==2.27.1 pydeck==0.9.1 Pygments==2.18.0 +pyparsing==3.2.0 python-dateutil==2.9.0.post0 python-dotenv==1.0.1 pytz==2024.2 @@ -46,18 +68,30 @@ requests==2.31.0 rich==13.9.4 rpds-py==0.21.0 setuptools==75.6.0 +shellingham==1.5.4 six==1.16.0 +smart-open==7.0.5 smmap==5.0.1 soupsieve==2.6 +spacy==3.8.2 +spacy-language-detection==0.2.1 +spacy-legacy==3.0.12 +spacy-loggers==1.0.5 +srsly==2.4.8 streamlit==1.40.2 sympy==1.13.3 tenacity==9.0.0 +thinc==8.3.2 tokenizers==0.21.0 toml==0.10.2 tornado==6.4.2 tqdm==4.67.1 +typer==0.14.0 typesense==0.21.0 typing_extensions==4.12.2 tzdata==2024.2 urllib3==2.2.3 +wasabi==1.1.3 +weasel==0.4.1 +wrapt==1.17.0 xmltodict==0.13.0 diff --git a/import_data/utils/documents_to_database.py b/import_data/utils/documents_to_database.py index cbcdbe9..0d2d4c5 100644 --- a/import_data/utils/documents_to_database.py +++ b/import_data/utils/documents_to_database.py @@ -1,11 +1,72 @@ import tqdm +import spacy +from spacy.language import Language +from spacy_language_detection import LanguageDetector +from urlextract import URLExtract +import requests from .typesense_client import client +# Load spaCy models +nlp_en = spacy.load("en_core_web_sm") +nlp_fr = spacy.load("fr_core_news_sm") + +# Create language detector +def get_lang_detector(nlp, name): + return LanguageDetector(seed=42) + +Language.factory("language_detector", func=get_lang_detector) +nlp_en.add_pipe("language_detector", last=True) + +# Initialize URL extractor +extractor = URLExtract() + +def count_words(text, lang): + if lang == 'en': + doc = nlp_en(text) + elif lang == 'fr': + doc = nlp_fr(text) + else: + # Default to English if language is not supported + doc = nlp_en(text) + + # Count words excluding stopwords + word_count = len([token for token in doc if not token.is_stop and not token.is_punct]) + return word_count + +def resolve_shortened_url(url): + try: + response = requests.head(url, allow_redirects=True, timeout=5) + return response.url + except: + return url + +def extract_and_resolve_urls(text): + urls = extractor.find_urls(text) + resolved_urls = [resolve_shortened_url(url) for url in urls] + return list(set(resolved_urls)) # Remove duplicates + def documents_to_database(documents_list, os_client=client): try: for document in tqdm.tqdm(documents_list.to_dict(orient='records')): + # Detect language + doc = nlp_en(document['texte']) + lang = doc._.language['language'] + + # Count words + word_count = count_words(document['texte'], lang) + + # Extract and resolve URLs + urls = extract_and_resolve_urls(document['texte']) + + # Add language, word count, and URLs to the document + document['langue'] = lang + document['nombre_de_mots'] = word_count + document['texte_urls'] = urls + + # Create document in Typesense os_client.collections['social_media_posts'].documents.create(document) + print(f"Successfully inserted {len(documents_list)} documents.") except Exception as e: print(f"Error inserting documents: {str(e)}") \ No newline at end of file diff --git a/search_app_ui/run_streamlit_app.py b/search_app_ui/run_streamlit_app.py new file mode 100644 index 0000000..4fddb77 --- /dev/null +++ b/search_app_ui/run_streamlit_app.py @@ -0,0 +1,25 @@ +import subprocess +import os + + +def run_streamlit_app(): + # Obtenir le chemin du répertoire courant + current_dir = os.path.dirname(os.path.abspath(__file__)) + + # Construire le chemin vers streamlit_app.py + app_path = os.path.join(current_dir, 'streamlit_app.py') + + # Commande pour exécuter l'application Streamlit + command = f"streamlit run {app_path}" + + try: + # Exécuter la commande + subprocess.run(command, shell=True, check=True) + except subprocess.CalledProcessError as e: + print(f"Erreur lors du lancement de l'application Streamlit : {e}") + except Exception as e: + print(f"Une erreur inattendue s'est produite : {e}") + + +if __name__ == "__main__": + run_streamlit_app() diff --git a/search_app_ui/streamlit_app.py b/search_app_ui/streamlit_app.py index c185461..2e249ba 100644 --- a/search_app_ui/streamlit_app.py +++ b/search_app_ui/streamlit_app.py @@ -77,12 +77,15 @@ client = typesense.Client({ 'connection_timeout_seconds': 2 }) + def rechercher_documents(cette_requete, ces_filtres=None, facette_par=None): parametres_recherche = { 'q': cette_requete, - 'query_by': 'texte', - 'sort_by': 'creation_timestamp:desc', - 'per_page': 100, + 'query_by': 'texte,embedding', + 'sort_by': '_text_match(buckets: 10):desc,creation_timestamp:desc', + "exclude_fields": "embedding", + "prefix": "false", + 'per_page': 10, 'page': 1 } @@ -92,31 +95,72 @@ def rechercher_documents(cette_requete, ces_filtres=None, facette_par=None): if facette_par: parametres_recherche['facet_by'] = facette_par - all_results = [] - while True: - results = client.collections['social_media_posts'].documents.search(parametres_recherche) - all_results.extend(results['hits']) - if len(all_results) >= results['found']: - break - parametres_recherche['page'] += 1 + st.write("Search parameters:", parametres_recherche) + + all_results = [] + try: + while True: + results = client.collections['social_media_posts'].documents.search(parametres_recherche) + all_results.extend(results['hits']) + if len(all_results) >= results['found']: + break + parametres_recherche['page'] += 1 + results['hits'] = all_results + return results + + except Exception as e: + st.error(f"Error during search: {str(e)}") # Error handling + + +# Récupérer dynamiquement les réseaux depuis Typesense +def get_networks(): + search_parameters = { + 'q': '*', + 'query_by': 'network', + 'facet_by': 'network', + 'per_page': 0 + } + try: + results = client.collections['social_media_posts'].documents.search(search_parameters) + networks = [facet['value'] for facet in results['facet_counts'][0]['counts']] + return networks + except Exception as e: + st.error(f"Erreur lors de la récupération des réseaux : {str(e)}") + return ['Facebook', 'Instagram', 'Threads', 'LinkedIn', 'WordPress'] # Valeurs par défaut en cas d'erreur - results['hits'] = all_results - return results # Interface utilisateur Streamlit st.title('Recherche dans tes contenus publiés sur le web') +# Indiquer le nombre total de documents indexés dans Typesense +collections = client.collections.retrieve() +total_documents = sum(collection['num_documents'] for collection in collections) + +st.write(f"Total documents indexés: {total_documents}") + # Champ de recherche requete = st.text_input('Entrez votre requête de recherche') # Filtre de plage de dates col1, col2 = st.columns(2) -date_debut = col1.date_input('Date de début') -date_fin = col2.date_input('Date de fin') +date_debut = col1.date_input('Date de début', value=datetime.now() - pd.DateOffset(years=1)) +date_fin = col2.date_input('Date de fin', value=datetime.now()) # Filtre de réseau social -reseaux = ['Facebook', 'Instagram', 'Threads' ,'LinkedIn', 'WordPress'] -reseaux_selectionnes = st.multiselect('Sélectionnez les réseaux sociaux', reseaux) + +reseaux = get_networks() +reseaux_selectionnes = st.multiselect('Sélectionnez les réseaux sociaux', reseaux, + default=reseaux[0] if reseaux else None) + +# Filtre de langue +langues = [('fr', 'Français'), ('en', 'English')] +langue_selectionnees = st.multiselect('Sélectionnez la langue', + options=[label for code, label in langues], + format_func=lambda x: x, + default='Français') + +# Convertir les étiquettes en codes de langage +selected_lang_codes = [code for code, label in langues if label in langue_selectionnees] if st.button('Rechercher'): # Préparer les filtres @@ -124,8 +168,9 @@ if st.button('Rechercher'): fin_datetime = datetime.combine(date_fin, time.max) filtre_date = f"creation_timestamp:[{int(debut_datetime.timestamp())}..{int(fin_datetime.timestamp())}]" filtre_reseau = f"network:[{' '.join(reseaux_selectionnes)}]" if reseaux_selectionnes else None + filtre_langue = f"langue:[{' '.join(selected_lang_codes)}]" if selected_lang_codes else None - filtres = ' && '.join(filter(None, [filtre_date, filtre_reseau])) + filtres = ' && '.join(filter(None, [filtre_date, filtre_reseau, filtre_langue])) # Effectuer la recherche pour tous les résultats tous_resultats = rechercher_documents(requete, ces_filtres=filtres, facette_par='network') @@ -133,14 +178,44 @@ if st.button('Rechercher'): # Afficher le nombre total de résultats st.subheader(f"Trouvé {nombre_total_resultats} résultats") - + + # Affichage des résultats (100 maximum) + st.subheader("Résultats de la recherche") + + for hit in tous_resultats['hits'][:100]: # Limite à 100 résultats + col1, col2 = st.columns([1, 4]) + + with col1: + st.markdown(f"**{hit['document']['network']}**") + st.markdown( + f"**{datetime.fromtimestamp(hit['document']['creation_timestamp']).strftime('%Y-%m-%d %H:%M:%S')}**") + + # Étiquettes de couleur pour les facettes + st.markdown(f""" + + {hit['document']['langue']} + + """, unsafe_allow_html=True) + + with col2: + # Boîte de texte pour le contenu + st.text_area("Contenu", hit['document']['texte'], height=150) + + # URI en dessous + if 'uri' in hit['document']: + st.markdown(f"[Lien vers le post original]({hit['document']['uri']})") + + st.markdown("---") + # Afficher les facettes if 'facet_counts' in tous_resultats: - facettes_reseau = {facette['value']: facette['count'] for facette in tous_resultats['facet_counts'][0]['counts']} + facettes_reseau = {facette['value']: facette['count'] for facette in + tous_resultats['facet_counts'][0]['counts']} st.subheader("Résultats par Réseau") - + # Graphique en camembert pour montrer la distribution des résultats par réseau social - fig = px.pie(values=list(facettes_reseau.values()), names=list(facettes_reseau.keys()), title="Distribution par Réseau") + fig = px.pie(values=list(facettes_reseau.values()), names=list(facettes_reseau.keys()), + title="Distribution par Réseau") # Ce graphique montre la proportion de résultats pour chaque réseau social st.plotly_chart(fig) @@ -180,21 +255,3 @@ if st.button('Rechercher'): df_pivot['mois'] = df_pivot['mois'].dt.strftime('%B %Y') # Ce tableau fournit un résumé détaillé du nombre de posts par réseau social pour chaque mois st.dataframe(df_pivot) - - # Créer une chaîne pour contenir tous les résultats - all_results_text = "" - - # Peupler la chaîne avec tous les résultats - for hit in tous_resultats['hits']: - horodatage = hit['document']['creation_timestamp'] - all_results_text += f"**{hit['document']['network']}** - {datetime.fromtimestamp(horodatage).strftime('%Y-%m-%d %H:%M:%S')}\n\n" - - paragraphes = hit['document']['texte'].split('\n') - for paragraphe in paragraphes: - if paragraphe.strip(): - all_results_text += f"{paragraphe}\n\n" - - all_results_text += "---\n\n" - - # Afficher les résultats dans une zone de texte - st.text_area("Résultats de la recherche", all_results_text, height=400) \ No newline at end of file diff --git a/typesense_stats/typesense_stats.py b/typesense_stats/typesense_stats.py new file mode 100644 index 0000000..b6e72e5 --- /dev/null +++ b/typesense_stats/typesense_stats.py @@ -0,0 +1,106 @@ +import typesense +from dotenv import load_dotenv +import os +from datetime import datetime +import pandas as pd + +## %% Configuration initiale +# Charger les variables d'environnement et initialiser le client Typesense +load_dotenv() + +# Initialiser le client Typesense +client = typesense.Client({ + 'nodes': [{ + 'host': os.getenv('TYPESENSE_HOST', 'localhost'), + 'port': os.getenv('TYPESENSE_PORT', '8108'), + 'protocol': os.getenv('TYPESENSE_PROTOCOL', 'http') + }], + 'api_key': os.getenv('TYPESENSE_API_KEY'), + 'connection_timeout_seconds': 2 +}) + +## %% Fonction pour obtenir les statistiques d'index +def get_index_stats(): + collections = client.collections.retrieve() + total_documents = sum(collection['num_documents'] for collection in collections) + + print(f"Nombre total de documents indexés : {total_documents}") + print("\nStatistiques par collection :") + for collection in collections: + print(f" {collection['name']}: {collection['num_documents']} documents") + +## %% Fonction pour obtenir la distribution par réseau social +def get_network_distribution(): + search_parameters = { + 'q': '*', + 'query_by': 'texte', + 'facet_by': 'network', + 'per_page': 0 + } + + result = client.collections['social_media_posts'].documents.search(search_parameters) + + network_counts = {facet['value']: facet['count'] for facet in result['facet_counts'][0]['counts']} + + print("\nDistribution par réseau social :") + for network, count in network_counts.items(): + print(f" {network}: {count}") + +## %% Fonction pour obtenir la distribution temporelle +def get_temporal_distribution(): + search_parameters = { + 'q': '*', + 'query_by': 'texte', + 'per_page': 250, # Maximum autorisé par Typesense + 'sort_by': 'creation_timestamp:asc', + 'page': 1 + } + + all_dates = [] + while True: + result = client.collections['social_media_posts'].documents.search(search_parameters) + + if not result['hits']: + break + + dates = [datetime.fromtimestamp(hit['document']['creation_timestamp']) for hit in result['hits']] + all_dates.extend(dates) + + search_parameters['page'] += 1 + + df = pd.DataFrame({'date': all_dates}) + df['month'] = df['date'].dt.to_period('M') + monthly_counts = df['month'].value_counts().sort_index() + + print("\nDistribution temporelle (par mois) :") + for month, count in monthly_counts.items(): + print(f" {month}: {count}") + +## %% Fonction pour obtenir un échantillon de documents +def get_document_sample(sample_size=5): + search_parameters = { + 'q': '*', + 'query_by': 'texte', + 'per_page': sample_size, + 'sort_by': 'creation_timestamp:desc' + } + + result = client.collections['social_media_posts'].documents.search(search_parameters) + + print(f"\nÉchantillon de {sample_size} documents récents :") + for hit in result['hits']: + doc = hit['document'] + print(f"\n ID: {doc.get('id', 'N/A')}") + print(f" Réseau: {doc.get('network', 'N/A')}") + print(f" Type: {doc.get('type', 'N/A')}") + print(f" Date: {datetime.fromtimestamp(doc['creation_timestamp']).strftime('%Y-%m-%d %H:%M:%S')}") + print(f" Texte: {doc.get('texte', 'N/A')[:100]}...") # Afficher les 100 premiers caractères du texte + print(f" URL: {doc.get('uri', 'N/A')}") + print(f" Langue: {doc.get('langue', 'N/A')}") + +## %% Point d'entrée principal du script +if __name__ == "__main__": + get_index_stats() + get_network_distribution() + get_temporal_distribution() + get_document_sample() \ No newline at end of file