Différences entre les versions de « Mondher »
(118 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
== Hello !== | == Hello !== | ||
<br> | <br> | ||
− | + | == '''travail en cours : ranking de termes médiatiques''' == | |
− | == ''' | ||
<br> | <br> | ||
<br> | <br> | ||
Ligne 9 : | Ligne 8 : | ||
<br> | <br> | ||
== '''Quoi ?''' == | == '''Quoi ?''' == | ||
− | |||
<br> | <br> | ||
Une page web affiche un ranking en temps réel des termes utilisés dans les articles d'une sélection de médias de Suisse romande. | Une page web affiche un ranking en temps réel des termes utilisés dans les articles d'une sélection de médias de Suisse romande. | ||
Ligne 17 : | Ligne 15 : | ||
== '''Comment ?''' == | == '''Comment ?''' == | ||
− | == | + | === Définir les sources === |
<br> | <br> | ||
Une sélection de quatorze journaux suisse-romands a été retenue. Cette sélection contient la totalité des quotidiens de la partie francophone de Suisse; chaque canton est représenté. Les critères de sélection sont les suivants : le journal doit être un quotidien suisse-romand, il doit être francophone, il doit traiter de l’actualité internationale, nationale et cantonale et il doit diffuser ses informations sur un site internet et/ou une application entre autre. | Une sélection de quatorze journaux suisse-romands a été retenue. Cette sélection contient la totalité des quotidiens de la partie francophone de Suisse; chaque canton est représenté. Les critères de sélection sont les suivants : le journal doit être un quotidien suisse-romand, il doit être francophone, il doit traiter de l’actualité internationale, nationale et cantonale et il doit diffuser ses informations sur un site internet et/ou une application entre autre. | ||
Ligne 39 : | Ligne 37 : | ||
· Le Matin (Romandie)<br> | · Le Matin (Romandie)<br> | ||
· 20 Minutes (Romandie)<br> | · 20 Minutes (Romandie)<br> | ||
− | |||
<br> | <br> | ||
− | == ''' | + | === '''Diagramme de la base de données''' === |
<br> | <br> | ||
Une structure de base de données a été créée sur sur phpMyAdmin. La base de données est structurée en catégories et sous-catégories distinctes. Toute informations récoltée est triée et classée dans la catégorie adéquate. Il y a trois catégories et trois sous-catégories : | Une structure de base de données a été créée sur sur phpMyAdmin. La base de données est structurée en catégories et sous-catégories distinctes. Toute informations récoltée est triée et classée dans la catégorie adéquate. Il y a trois catégories et trois sous-catégories : | ||
Ligne 56 : | Ligne 53 : | ||
<br> | <br> | ||
<br> | <br> | ||
− | —— '''La sous-catégorie aut·eur·ice''' regroupe l’ID de chaque aut·eur·rice·s, son nom, son prénom et une courte bio (s’il | + | —— '''La sous-catégorie aut·eur·ice''' regroupe l’ID de chaque aut·eur·rice·s, son nom, son prénom et une courte bio (s’il y en a une) |
<br> | <br> | ||
<br> | <br> | ||
Ligne 65 : | Ligne 62 : | ||
<br> | <br> | ||
<br> | <br> | ||
− | [[Fichier: | + | [[Fichier:diagramme_base_donnees_.jpg]] ''Schéma de la structure de la base de données'' |
<br> | <br> | ||
− | |||
<br> | <br> | ||
<br> | <br> | ||
− | == 3/ | + | <syntaxhighlight lang="sql"> |
+ | -- phpMyAdmin SQL Dump | ||
+ | -- version 4.9.5 | ||
+ | -- https://www.phpmyadmin.net/ | ||
+ | -- | ||
+ | -- Host: localhost:8889 | ||
+ | -- Generation Time: Feb 25, 2021 at 08:43 AM | ||
+ | -- Server version: 5.7.30 | ||
+ | -- PHP Version: 7.4.9 | ||
+ | |||
+ | SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; | ||
+ | SET time_zone = "+00:00"; | ||
+ | |||
+ | |||
+ | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; | ||
+ | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; | ||
+ | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; | ||
+ | /*!40101 SET NAMES utf8mb4 */; | ||
+ | |||
+ | -- | ||
+ | -- Database: `indice_popularité_termes` | ||
+ | -- | ||
+ | |||
+ | -- -------------------------------------------------------- | ||
+ | |||
+ | -- | ||
+ | -- Table structure for table `article` | ||
+ | -- | ||
+ | |||
+ | CREATE TABLE `article` ( | ||
+ | `id` int(10) UNSIGNED NOT NULL, | ||
+ | `titre` text NOT NULL, | ||
+ | `header` text NOT NULL, | ||
+ | `body` text NOT NULL, | ||
+ | `date_crea` datetime NOT NULL, | ||
+ | `date_modif` datetime NOT NULL, | ||
+ | `url` text NOT NULL, | ||
+ | `source_id` int(10) UNSIGNED NOT NULL | ||
+ | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; | ||
+ | |||
+ | -- | ||
+ | -- Dumping data for table `article` | ||
+ | -- | ||
+ | |||
+ | INSERT INTO `article` (`id`, `titre`, `header`, `body`, `date_crea`, `date_modif`, `url`, `source_id`) VALUES | ||
+ | (1, '1', '2', '3', '2020-10-07 12:11:40', '2020-10-14 12:11:40', '4', 4); | ||
+ | |||
+ | -- -------------------------------------------------------- | ||
+ | |||
+ | -- | ||
+ | -- Table structure for table `article_auteur` | ||
+ | -- | ||
+ | |||
+ | CREATE TABLE `article_auteur` ( | ||
+ | `id` int(10) UNSIGNED NOT NULL, | ||
+ | `id_article` int(10) UNSIGNED NOT NULL, | ||
+ | `id_auteurice` int(10) UNSIGNED NOT NULL | ||
+ | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; | ||
+ | |||
+ | -- -------------------------------------------------------- | ||
+ | |||
+ | -- | ||
+ | -- Table structure for table `article_mots` | ||
+ | -- | ||
+ | |||
+ | CREATE TABLE `article_mots` ( | ||
+ | `id` int(10) UNSIGNED NOT NULL, | ||
+ | `id_article` int(11) NOT NULL, | ||
+ | `id_mots` int(11) NOT NULL | ||
+ | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; | ||
+ | |||
+ | -- -------------------------------------------------------- | ||
+ | |||
+ | -- | ||
+ | -- Table structure for table `aut-eur-rice` | ||
+ | -- | ||
+ | |||
+ | CREATE TABLE `aut-eur-rice` ( | ||
+ | `id` int(10) UNSIGNED NOT NULL, | ||
+ | `nom` text NOT NULL, | ||
+ | `prénom` text NOT NULL, | ||
+ | `bio` text NOT NULL | ||
+ | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; | ||
+ | |||
+ | -- -------------------------------------------------------- | ||
+ | |||
+ | -- | ||
+ | -- Table structure for table `mots` | ||
+ | -- | ||
+ | |||
+ | CREATE TABLE `mots` ( | ||
+ | `id` int(10) UNSIGNED NOT NULL, | ||
+ | `mots` text NOT NULL, | ||
+ | `fréquence` int(11) NOT NULL | ||
+ | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; | ||
+ | |||
+ | -- -------------------------------------------------------- | ||
+ | |||
+ | -- | ||
+ | -- Table structure for table `source` | ||
+ | -- | ||
+ | |||
+ | CREATE TABLE `source` ( | ||
+ | `id` int(10) UNSIGNED NOT NULL, | ||
+ | `nom` text NOT NULL, | ||
+ | `url` text NOT NULL | ||
+ | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; | ||
+ | |||
+ | -- | ||
+ | -- Indexes for dumped tables | ||
+ | -- | ||
+ | |||
+ | -- | ||
+ | -- Indexes for table `article` | ||
+ | -- | ||
+ | ALTER TABLE `article` | ||
+ | ADD PRIMARY KEY (`id`); | ||
+ | |||
+ | -- | ||
+ | -- Indexes for table `article_auteur` | ||
+ | -- | ||
+ | ALTER TABLE `article_auteur` | ||
+ | ADD PRIMARY KEY (`id`); | ||
+ | |||
+ | -- | ||
+ | -- Indexes for table `article_mots` | ||
+ | -- | ||
+ | ALTER TABLE `article_mots` | ||
+ | ADD PRIMARY KEY (`id`); | ||
+ | |||
+ | -- | ||
+ | -- Indexes for table `aut-eur-rice` | ||
+ | -- | ||
+ | ALTER TABLE `aut-eur-rice` | ||
+ | ADD PRIMARY KEY (`id`); | ||
+ | |||
+ | -- | ||
+ | -- Indexes for table `mots` | ||
+ | -- | ||
+ | ALTER TABLE `mots` | ||
+ | ADD PRIMARY KEY (`id`); | ||
+ | |||
+ | -- | ||
+ | -- Indexes for table `source` | ||
+ | -- | ||
+ | ALTER TABLE `source` | ||
+ | ADD PRIMARY KEY (`id`); | ||
+ | |||
+ | -- | ||
+ | -- AUTO_INCREMENT for dumped tables | ||
+ | -- | ||
+ | |||
+ | -- | ||
+ | -- AUTO_INCREMENT for table `article` | ||
+ | -- | ||
+ | ALTER TABLE `article` | ||
+ | MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2; | ||
+ | |||
+ | -- | ||
+ | -- AUTO_INCREMENT for table `article_auteur` | ||
+ | -- | ||
+ | ALTER TABLE `article_auteur` | ||
+ | MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT; | ||
+ | |||
+ | -- | ||
+ | -- AUTO_INCREMENT for table `article_mots` | ||
+ | -- | ||
+ | ALTER TABLE `article_mots` | ||
+ | MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT; | ||
+ | |||
+ | -- | ||
+ | -- AUTO_INCREMENT for table `aut-eur-rice` | ||
+ | -- | ||
+ | ALTER TABLE `aut-eur-rice` | ||
+ | MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT; | ||
+ | |||
+ | -- | ||
+ | -- AUTO_INCREMENT for table `mots` | ||
+ | -- | ||
+ | ALTER TABLE `mots` | ||
+ | MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT; | ||
+ | |||
+ | -- | ||
+ | -- AUTO_INCREMENT for table `source` | ||
+ | -- | ||
+ | ALTER TABLE `source` | ||
+ | MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT; | ||
+ | |||
+ | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; | ||
+ | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; | ||
+ | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; | ||
+ | </syntaxhighlight> | ||
<br> | <br> | ||
+ | === Ecrire un programme (en python) pour récolter et stocker les informations contenues dans certains sélecteurs css :=== | ||
+ | <br> | ||
+ | 1. repère et pointe les sélecteurs ''css'' qui contiennent les informations nécessaires<br> | ||
+ | 2. initialise un objet "navigateur" pour se connecter au site web à l'aide de la librairie ''mechanize''<br> | ||
+ | 3. contourne une éventuelle demande de certificat de sécurité ''ssl'' pour accéder au contenu de la page<br> | ||
+ | 4. extrait les données de la page web contenues dans les sélecteurs ''css'' ciblés<br> | ||
+ | 5. lit les données<br> | ||
+ | 6. encode les données en ''utf-8''<br> | ||
+ | 7. récupère le contenu des sélecteurs css et les traduit en expressions ''xpath''<br> | ||
+ | 8. print les informations | ||
+ | <br> | ||
+ | <br> | ||
+ | [[Fichier:schema_indice_p.png]] ''Schéma du processus'' | ||
+ | <br> | ||
+ | <br> | ||
+ | == Test du programme == | ||
+ | <br> | ||
+ | Page cible <br> | ||
+ | https://www.24heures.ch/les-cours-en-ligne-engendrent-des-inegalites-800936194846 | ||
+ | <br> | ||
+ | <br> | ||
+ | Code <br> | ||
+ | <syntaxhighlight lang="python"> | ||
+ | #un programme qui récupère les données contenues dans les sélecteurs css suivants à partir d'une page d'article de 24heures.ch | ||
+ | #article h1, article h2, article h3, article p et article time | ||
+ | |||
+ | import mechanize | ||
+ | |||
+ | import lxml.html as lh | ||
+ | |||
+ | import cssselect | ||
+ | |||
+ | import ssl | ||
+ | |||
+ | #initialisation d'un objet "navigateur" avec la librairie mechanize | ||
+ | br = mechanize.Browser() | ||
+ | |||
+ | br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')] | ||
+ | |||
+ | br.set_handle_robots(False) | ||
+ | #fin de la configuration de mechanize | ||
+ | |||
+ | try: | ||
+ | _create_unverified_https_context = ssl._create_unverified_context | ||
+ | except AttributeError: | ||
+ | # Legacy Python that doesn't verify HTTPS certificates by default | ||
+ | pass | ||
+ | else: | ||
+ | # Handle target environment that doesn't support HTTPS verification | ||
+ | ssl._create_default_https_context = _create_unverified_https_context | ||
+ | |||
+ | data = br.open('https://www.24heures.ch/les-cours-en-ligne-engendrent-des-inegalites-800936194846', timeout=10.0) | ||
+ | |||
+ | |||
+ | rawdata = data.read() | ||
+ | unicode = rawdata.decode('utf-8', 'ignore') | ||
+ | src = lh.fromstring(unicode) | ||
+ | |||
+ | |||
+ | #on convertit un sélecteur css en objets de type "cssselector" | ||
+ | selecteurs = cssselect.parse('article h1, article h2, article h3, article p, article time') | ||
+ | |||
+ | for selecteur in selecteurs: | ||
+ | chemin_xpath = cssselect.HTMLTranslator().selector_to_xpath(selecteur, translate_pseudo_elements=True) | ||
+ | resultats = src.xpath(chemin_xpath) | ||
+ | for resultat in resultats: | ||
+ | print(resultat.text_content()) | ||
+ | </syntaxhighlight> | ||
+ | <br> | ||
+ | Lancement du programme dans le terminal<br> | ||
+ | [[Fichier:Mndr-test-launch.jpg]] | ||
+ | |||
+ | == Tentative de tri du texte avec des regex et de comptage de l'occurrence des mots == | ||
+ | <br> | ||
+ | [[Fichier:mndr-reg-1.jpg]] | ||
+ | [[Fichier:mndr-reg-2.jpg]] | ||
+ | [[Fichier:mndr-reg-3.jpg]] | ||
+ | [[Fichier:mndr-reg-4.jpg]] | ||
+ | [[Fichier:mndr-reg-5.jpg]] | ||
+ | <br> | ||
+ | Le résultat est pas trop mal. J'aimerais maintenant comprendre faire pour garder des mots composés comme "en ligne" et aussi comment rendre cette opération plus efficace et l'inclure dans le programme dans Python.. | ||
+ | <br> | ||
+ | [[Fichier:Mndr-python-count-1.jpg]] | ||
+ | <br> | ||
+ | <br> | ||
+ | = '''11.03.21 : Travail en binôme avec [http://curlybraces.be/wiki/Lea Lea]''' = | ||
+ | <br> | ||
+ | Étant donné qu'un certain nombre de journaux fonctionnent par abonnement et que, du coup, on a pas toujours accès au corps des articles, on a choisi de se concentrer sur les titres de ces articles. On récupère alors les titres, la source et la date de publication. | ||
+ | <br> | ||
+ | On a aussi étendu géographiquement la sélection de journaux. On retient les quotidiens nationaux de Belgique, de Suisse, de France, du Luxembourg et de Monaco, qui ont une présence sur le net : | ||
+ | <br> | ||
+ | <br> | ||
+ | == '''Mise à jour de la liste de quotidiens retenus''' == | ||
+ | <br> | ||
+ | Belgique | ||
+ | <ul> | ||
+ | <li>https://www.lesoir.be</li> | ||
+ | <li>https://www.lecho.be</li> | ||
+ | <li>https://www.lalibre.be</li> | ||
+ | <li>https://fr.metrotime.be</li> | ||
+ | </ul> | ||
+ | |||
+ | Suisse | ||
+ | <ul> | ||
+ | <li>https://www.24heures.ch</li> | ||
+ | <li>https://www.lematin.ch</li> | ||
+ | <li>https://www.lenouvelliste.ch</li> | ||
+ | <li>https://www.tdg.ch</li> | ||
+ | <li>https://www.arcinfo.ch</li> | ||
+ | <li>https://www.heidi.news</li> | ||
+ | <li>https://www.lacote.ch</li> | ||
+ | <li>https://lecourrier.ch</li> | ||
+ | <li>https://www.letemps.ch</li> | ||
+ | <li>https://www.agefi.fr</li> | ||
+ | <li>https://www.lqj.ch</li> | ||
+ | </ul> | ||
+ | |||
+ | France | ||
+ | <ul> | ||
+ | <li>https://www.lefigaro.fr</li> | ||
+ | <li>https://www.lemonde.fr</li> | ||
+ | <li>https://www.lesechos.fr</li> | ||
+ | <li>https://www.leparisien.fr</li> | ||
+ | <li>https://www.liberation.fr</li> | ||
+ | <li>https://www.la-croix.com</li> | ||
+ | <li>https://www.lequipe.fr</li> | ||
+ | <li>https://www.dhnet.be</li> | ||
+ | </ul> | ||
+ | |||
+ | Luxembourg | ||
+ | <ul> | ||
+ | <li>https://www.lessentiel.lu/fr/luxembourg/</li> | ||
+ | <li>https://lequotidien.lu</li> | ||
+ | </ul> | ||
+ | |||
+ | Monaco | ||
+ | <ul> | ||
+ | https://www.monacomatin.mc | ||
+ | </ul> | ||
+ | <br> | ||
+ | <br> | ||
+ | === '''Liste des sélecteurs''' === | ||
+ | <br> | ||
+ | <br> | ||
+ | [[Fichier:Mndr-selecteurs.jpg]][[Fichier:Mndr-selecteurs2.jpg]][[Fichier:Mndr-selecteurs3.jpg]][[Fichier:Mndr-selecteurs4.jpg]] | ||
+ | |||
+ | == '''Mise à jour du schéma et de la base de données''' == | ||
+ | [[Fichier:mndr-iptm-schema.png]] | ||
+ | <br> | ||
+ | [[Fichier:Mndr-bdd-tables.png]] | ||
+ | <br> | ||
+ | <br> | ||
+ | Informations de connexion à la base de données : | ||
+ | <br> | ||
+ | <br> | ||
+ | <syntaxhighlight lang="python"> | ||
+ | #initialisation de l'objet db, interface avec la base de données mysql | ||
+ | db = mysql.connect( | ||
+ | host = "localhost", | ||
+ | port = "8889", | ||
+ | user = "root", | ||
+ | passwd = "root", | ||
+ | database = "indice_popularité_termes" | ||
+ | ) | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | == '''Tableaux et dictionnaires : un code unique qui regroupe toutes les sources et leurs sélecteurs''' == | ||
+ | Afin d'avoir un unique code global dans lequel toutes les sources sont regroupées, on a vu l'utilisation de tableaux et de dictionnaires. Le tableau ''selecteurs'' contient les sources sous forme de dictionnaires répertoriant les sélecteurs nécessaires à la récupération des données visées (url de la source, url de l'article, titre de l'article et date de création de l'article). | ||
+ | <br> | ||
+ | <br> | ||
+ | <syntaxhighlight lang="python"> | ||
+ | selecteurs = [ | ||
+ | { | ||
+ | 'url':'http://lemonde.fr', | ||
+ | 'selecteur_links':'h1 a', | ||
+ | 'selecteurs_article':{ | ||
+ | 'titre':'article h1', | ||
+ | 'date_pub':'span.date_pub', | ||
+ | 'date_modif':'span.date_modif' | ||
+ | } | ||
+ | |||
+ | }, | ||
+ | { | ||
+ | 'url':'http://lesoir.be', | ||
+ | 'selecteur_links':'h1 a', | ||
+ | 'selecteurs_article':{ | ||
+ | 'titre':'article h1', | ||
+ | 'date_pub':'span.date_pub', | ||
+ | 'date_modif':'span.date_modif' | ||
+ | } | ||
+ | |||
+ | } | ||
+ | ] | ||
+ | |||
+ | print(selecteurs[0]['selecteurs_article']['date_pub']) | ||
+ | </syntaxhighlight> | ||
+ | <br> | ||
+ | <br> | ||
+ | À partir de là, le code se présente comme cela : | ||
+ | <br> | ||
+ | <br> | ||
+ | <syntaxhighlight lang="python"> | ||
+ | import time | ||
+ | import mechanize | ||
+ | import lxml.html as lh | ||
+ | import cssselect | ||
+ | |||
+ | |||
+ | def getSrcFromURL(url): | ||
+ | data = br.open(url) | ||
+ | |||
+ | rawdata = data.read() | ||
+ | unicode = rawdata.decode('utf-8', 'ignore') | ||
+ | src = lh.fromstring(unicode) | ||
+ | return src | ||
+ | |||
+ | def getResults(src, selecteurs): | ||
+ | results = [] | ||
+ | cssSelectSelecteurs = cssselect.parse(selecteurs) | ||
+ | for cssSelectSelecteur in cssSelectSelecteurs: | ||
+ | chemin_xpath = cssselect.HTMLTranslator().selector_to_xpath(cssSelectSelecteur, translate_pseudo_elements=True) | ||
+ | results = results + src.xpath(chemin_xpath) | ||
+ | |||
+ | return results | ||
+ | |||
+ | def getData(htmlElements, operation): | ||
+ | data = [] | ||
+ | for element in htmlElements: | ||
+ | if operation == 'get_href': | ||
+ | data.append(element.get('href')) | ||
+ | elif operation == 'text_content': | ||
+ | data.append(element.text_content()) | ||
+ | elif operation == 'get_datetime': | ||
+ | data.append(element.get('datetime')) | ||
+ | |||
+ | return data | ||
+ | |||
+ | |||
+ | |||
+ | selecteurs = [ | ||
+ | { | ||
+ | 'url':'http://lemonde.fr', | ||
+ | 'selecteur_links':{'name':'article_link', 'selecteur':'div.article a','operation':'get_href'}, | ||
+ | 'selecteurs_article':[ | ||
+ | {'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'}, | ||
+ | {'name': 'date_pub', 'selecteur': 'span.date_pub', 'operation':'text_content'}, | ||
+ | {'name': 'date_modif', 'selecteur': 'span.date_modif', 'operation':'text_content'} | ||
+ | ] | ||
+ | |||
+ | }, | ||
+ | { | ||
+ | 'url':'http://lesoir.be', | ||
+ | 'selecteur_links':{'name':'article_link', 'selecteur':'h1 a','operation':'get_href'}, | ||
+ | 'selecteurs_article':[ | ||
+ | {'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'}, | ||
+ | {'name': 'date_pub', 'selecteur': 'span.date_pub', 'operation':'text_content'}, | ||
+ | {'name': 'date_modif', 'selecteur': 'span.date_modif', 'operation':'text_content'} | ||
+ | ] | ||
+ | |||
+ | } | ||
+ | ] | ||
+ | |||
+ | #initialisation d'un objet "navigateur" avec la librairie mechanize | ||
+ | br = mechanize.Browser() | ||
+ | |||
+ | br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')] | ||
+ | |||
+ | br.set_handle_robots(False) | ||
+ | #fin de la configuration de mechanize | ||
+ | </syntaxhighlight> | ||
+ | <br> | ||
+ | |||
+ | == '''Déroulement des opérations sur le site d'une source et stockage des données dans un tableau''' == | ||
+ | Maintenant que nous avons écrit la structure de données générale des sources, il faut renseigner quoi faire avec les données visées par les sélecteurs. À cette étape, on stocke les données dans un tableau (dataArticle) qui sera en relation avec la base de données, dans laquelle les données seront classées comme dans le tableau dataArticle. | ||
+ | <br> | ||
+ | <br> | ||
+ | #0: pour chaque source dans le tableau sélecteurs | ||
+ | #1: récupérer la première page de la source | ||
+ | #2: appliquer le sélecteur contenu dans 'selecteur_links' | ||
+ | #3: appliquer l'opération sur chaque élément récupéré -> url articles | ||
+ | #4: pour chaque url article: | ||
+ | #5: récupérer la source de la page désignée par l'url | ||
+ | #6: déclarer un tableau vide qui contiendra les éléments à insérer dans la db "dataArticle" | ||
+ | #7: pour chaque sélecteur contenu dans "selecteurs_article": | ||
+ | #8: appliquer le sélecteur article sur la source de la page | ||
+ | #9: appliquer l'opération sur l'élément récupéré -> donnée | ||
+ | #10: ajouter la donnée au tableau dataArticle | ||
+ | #11: insérer le tableau dans la base de données | ||
+ | <br> | ||
+ | <br> | ||
+ | <syntaxhighlight lang="python"> | ||
+ | #0: pour chaque source dans le tableau sélecteurs | ||
+ | for source in selecteurs: | ||
+ | #0:récupérer l'id de la source dans la base de données | ||
+ | sourceId = source['source_id'] | ||
+ | #1: récupérer la première page de la source | ||
+ | premierePage = getSrcFromURL(source['url']) | ||
+ | #2: appliquer le sélecteur contenu dans 'selecteur_links' | ||
+ | liensArticles = getResults(premierePage, source['selecteur_links']['selecteur']) | ||
+ | #3: appliquer l'opération sur chaque élément récupéré -> url articles | ||
+ | urlsArticles = getData(liensArticles, source['selecteur_links']['operation']) | ||
+ | #4: pour chaque url article: | ||
+ | for urlArticle in urlsArticles: | ||
+ | #5: récupérer la source de la page désignée par l'url | ||
+ | #print(urlArticle) | ||
+ | sourceUrl = getSrcFromURL(urlArticle) | ||
+ | print(sourceUrl) | ||
+ | print(urlArticle) | ||
+ | #print(sourceUrl) | ||
+ | #6: déclarer un tableau vide qui contiendra les éléments à insérer dans la db -> dataArticle | ||
+ | dataArticle = [] | ||
+ | #7: pour chaque sélecteur contenu dans "selecteurs_article": | ||
+ | for selecteur_article in source['selecteurs_article']: | ||
+ | #8: appliquer le sélecteur article sur la source de la page | ||
+ | applySelector = getResults(sourceUrl, selecteur_article['selecteur']) | ||
+ | # print(applySelector) | ||
+ | #9: appliquer l'opération sur l'élément récupéré -> donnée | ||
+ | operateelement = getData(applySelector, selecteur_article['operation']) | ||
+ | #10: ajouter la donnée au tableau dataArticle | ||
+ | dataArticle.append(operateelement) | ||
+ | print(dataArticle) | ||
+ | </syntaxhighlight> | ||
+ | <br> | ||
+ | <br> | ||
+ | == '''Quoi faire avec le contenus des éléments HTML visés et uniformisation du format de date et heure de publication''' == | ||
+ | <br> | ||
+ | <br> | ||
+ | <syntaxhighlight lang="python"> | ||
+ | #1: Définir l'opération getData | ||
+ | def getData(htmlElements, operation): | ||
+ | #2: déclarer un tableau vide | ||
+ | data = [] | ||
+ | #3: pour chaque élément dans htmlElements: | ||
+ | for element in htmlElements: | ||
+ | #4: SI (condition) l'opération consiste à récupérer l'attribut href: | ||
+ | if operation == 'get_href': | ||
+ | #4bis: stocker la valeur de l'attribut href dans le tableau "data": | ||
+ | data.append(element.get('href')) | ||
+ | #5: aussi, si l'operation consiste à récupérer la date: | ||
+ | elif operation == 'get_content_date': | ||
+ | #2021-04-19T10:09:10+00:00 | ||
+ | #2021-04-19 10:09:10 | ||
+ | #6: déclarer une fonction "date" qui consiste à récupérer le contenu visé: | ||
+ | date = element.get('content') | ||
+ | #7: remplacer le caractère "T" au milieu de la châine par un espace pour le supprimer: | ||
+ | date = date.replace('T', ' ') | ||
+ | #8: ? | ||
+ | date = date[0:19] | ||
+ | #9: stocker la chaîne de caractère qui représente la date dans le tableau "data": | ||
+ | data.append(date) | ||
+ | #9.1: ou sinon, si l'opération consiste à récupérer la valeur de text_content: | ||
+ | elif operation == 'text_content': | ||
+ | #9.2: stocker la valeur de text_content dans le tableau "data": | ||
+ | data.append(element.text_content()) | ||
+ | #9.3: ou sinon, si l'opération consiste à récupérer la valeur de datetime: | ||
+ | elif operation == 'get_datetime': | ||
+ | #9.4: stocker la valeur de datetime dans le tableau "data": | ||
+ | data.append(element.get('datetime')) | ||
+ | #10: retourne la donnée récupérée | ||
+ | return data | ||
+ | </syntaxhighlight> | ||
+ | <br> | ||
+ | '''À partir de là, il est possible de récupérer les données et de les envoyer à la base de données pour qu'elles soient classées selon les critères retenus : la source et son ID, l'url de l'article et son ID, le titre de l'article et la date de publication''' | ||
+ | <br> | ||
+ | <br> | ||
+ | == '''Aperçu du code à cette étape''' == | ||
+ | <syntaxhighlight lang="python"> | ||
+ | import time | ||
+ | import mechanize | ||
+ | import lxml.html as lh | ||
+ | import cssselect | ||
+ | import ssl | ||
+ | import os | ||
+ | from urllib.parse import urlparse | ||
+ | import mysql.connector as mysql | ||
+ | |||
+ | def getSrcFromURL(url): | ||
+ | data = br.open(url) | ||
+ | |||
+ | rawdata = data.read() | ||
+ | unicode = rawdata.decode('utf-8', 'ignore') | ||
+ | src = lh.fromstring(unicode) | ||
+ | return src | ||
+ | |||
+ | def getResults(src, selecteurs): | ||
+ | results = [] | ||
+ | cssSelectSelecteurs = cssselect.parse(selecteurs) | ||
+ | for cssSelectSelecteur in cssSelectSelecteurs: | ||
+ | chemin_xpath = cssselect.HTMLTranslator().selector_to_xpath(cssSelectSelecteur, translate_pseudo_elements=True) | ||
+ | results = results + src.xpath(chemin_xpath) | ||
+ | |||
+ | return results | ||
+ | |||
+ | |||
+ | #initialisation de l'objet db, interface avec la base de données mysql | ||
+ | db = mysql.connect( | ||
+ | host = "localhost", | ||
+ | port = "8889", | ||
+ | user = "root", | ||
+ | passwd = "root", | ||
+ | database = "indice_popularité_termes" | ||
+ | ) | ||
+ | |||
+ | def getData(htmlElements, operation): | ||
+ | data = [] | ||
+ | for element in htmlElements: | ||
+ | if operation == 'get_href': | ||
+ | data.append(element.get('href')) | ||
+ | elif operation == 'get_content_date': | ||
+ | #2021-04-19T10:09:10+00:00 | ||
+ | #2021-04-19 10:09:10 | ||
+ | date = element.get('content') | ||
+ | date = date.replace('T', ' ') | ||
+ | date = date[0:19] | ||
+ | data.append(date) | ||
+ | elif operation == 'text_content': | ||
+ | data.append(element.text_content()) | ||
+ | elif operation == 'get_datetime': | ||
+ | data.append(element.get('datetime')) | ||
+ | |||
+ | return data | ||
+ | |||
+ | |||
+ | selecteurs = [ | ||
+ | # { | ||
+ | # 'url':'https://www.lenouvelliste.ch', | ||
+ | # 'selecteur_links':{'name':'article_link', 'selecteur':'h1 a, h3 a', 'operation':'get_href'}, | ||
+ | # 'selecteurs_article':[ | ||
+ | # {'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'}, | ||
+ | # {'name': 'date_pub', 'selecteur': 'article datetime', 'operation':'text_content'}, | ||
+ | # ] | ||
+ | |||
+ | # }, | ||
+ | # { | ||
+ | # 'url':'https://www.arcinfo.ch', | ||
+ | # 'selecteur_links':{'name':'article_link', 'selecteur':'h1 a, h3 a', 'operation':'get_href'}, | ||
+ | # 'selecteurs_article':[ | ||
+ | # {'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'}, | ||
+ | # {'name': 'date_pub', 'selecteur': 'article datetime', 'operation':'text_content'}, | ||
+ | # ] | ||
+ | |||
+ | # }, | ||
+ | # { | ||
+ | # 'url':'https://www.heidi.news', | ||
+ | # 'selecteur_links':{'name':'article_link', 'selecteur':'h3 a, h2 a', 'operation':'get_href'}, | ||
+ | # 'selecteurs_article':[ | ||
+ | # {'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'}, | ||
+ | # {'name': 'date_pub', 'selecteur': 'article datetime', 'operation':'text_content'}, | ||
+ | # ] | ||
+ | |||
+ | # }, | ||
+ | # { | ||
+ | # 'url':'https://www.lacote.ch', | ||
+ | # 'selecteur_links':{'name':'article_link', 'selecteur':'h3 a, h2 a', 'operation':'get_href'}, | ||
+ | # 'selecteurs_article':[ | ||
+ | # {'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'}, | ||
+ | # {'name': 'date_pub', 'selecteur': 'article datetime', 'operation':'text_content'}, | ||
+ | # ] | ||
+ | |||
+ | # }, | ||
+ | # { | ||
+ | # 'url':'https://lecourrier.ch', | ||
+ | # 'selecteur_links':{'name':'article_link', 'selecteur':'a.c-Card-permalink', 'operation':'get_href'}, | ||
+ | # 'selecteurs_article':[ | ||
+ | # {'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'}, | ||
+ | # {'name': 'date_pub', 'selecteur': 'article metaDate', 'operation':'text_content'}, | ||
+ | # ] | ||
+ | |||
+ | # }, | ||
+ | { | ||
+ | 'url':'https://lemonde.fr', | ||
+ | 'source_id':1, | ||
+ | 'selecteur_links':{'name':'article_link', 'selecteur':'div.article a','operation':'get_href'}, | ||
+ | 'selecteurs_article':[ | ||
+ | {'name': 'titre', 'selecteur': 'h1.article__title', 'operation':'text_content'}, | ||
+ | {'name': 'date_pub', 'selecteur': 'meta[property="og:article:published_time"]', 'operation':'get_content_date'}, | ||
+ | ] | ||
+ | |||
+ | }, | ||
+ | # { | ||
+ | # 'url':'https://www.lefigaro.fr', | ||
+ | # 'selecteur_links':{'name':'article_link', 'selecteur':'a.ensemble__link, a.fig-profile__link, fig-ensemble__link, a.fig-ensemble__first-article-link', 'operation':'get_href'}, | ||
+ | # 'selecteurs_article':[ | ||
+ | # {'name': 'titre', 'selecteur': 'h1', 'operation':'text_content'}, | ||
+ | # {'name': 'date_pub', 'selecteur': 'span.fig-content-metas__pub-date fig-content-metas__pub-date--hide-small', 'operation':'text_content'}, | ||
+ | # ] | ||
+ | |||
+ | # }, | ||
+ | # { | ||
+ | # 'url':'https://www.la-croix.com', | ||
+ | # 'selecteur_links':{'name':'article_link', 'selecteur':'a.block-item__title', 'operation':'get_href'}, | ||
+ | # 'selecteurs_article':[ | ||
+ | # {'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'}, | ||
+ | # {'name': 'date_pub', 'selecteur': 'div .font_xs color_grey margin-xxs-right font_tertiary', 'operation':'text_content'}, | ||
+ | # ] | ||
+ | |||
+ | # }, | ||
+ | ] | ||
+ | |||
+ | #initialisation d'un objet "navigateur" avec la librairie mechanize | ||
+ | br = mechanize.Browser() | ||
+ | |||
+ | br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')] | ||
+ | |||
+ | br.set_handle_robots(False) | ||
+ | #fin de la configuration de mechanize | ||
+ | |||
+ | try: | ||
+ | _create_unverified_https_context = ssl._create_unverified_context | ||
+ | except AttributeError: | ||
+ | # Legacy Python that doesn't verify HTTPS certificates by default | ||
+ | pass | ||
+ | else: | ||
+ | # Handle target environment that doesn't support HTTPS verification | ||
+ | ssl._create_default_https_context = _create_unverified_https_context | ||
+ | |||
+ | #0: pour chaque source dans le tableau sélecteurs | ||
+ | for source in selecteurs: | ||
+ | #0:récupérer l'id de la source dans la base de données | ||
+ | sourceId = source['source_id'] | ||
+ | #1: récupérer la première page de la source | ||
+ | premierePage = getSrcFromURL(source['url']) | ||
+ | #2: appliquer le sélecteur contenu dans 'selecteur_links' | ||
+ | liensArticles = getResults(premierePage, source['selecteur_links']['selecteur']) | ||
+ | #3: appliquer l'opération sur chaque élément récupéré -> url articles | ||
+ | urlsArticles = getData(liensArticles, source['selecteur_links']['operation']) | ||
+ | #4: pour chaque url article: | ||
+ | for urlArticle in urlsArticles: | ||
+ | #5: checker si l'url de l'article se trouve déjà dans la table article de la db. si il y est, skipper le reste des opérations | ||
+ | query = "SELECT * FROM article WHERE url='"+urlArticle+"'" | ||
+ | alreadyInDB = False | ||
+ | with db.cursor() as cursor: | ||
+ | cursor.execute(query) | ||
+ | result = cursor.fetchall() | ||
+ | if len(result) > 0: | ||
+ | alreadyInDB = True | ||
+ | |||
+ | if(alreadyInDB == True): | ||
+ | continue | ||
+ | |||
+ | #5bis: récupérer la source de la page désignée par l'url | ||
+ | # print(urlArticle) | ||
+ | sourceUrl = getSrcFromURL(urlArticle) | ||
+ | print(sourceUrl) | ||
+ | print(urlArticle) | ||
+ | # print(sourceUrl) | ||
+ | # #6: déclarer un tableau vide qui contiendra les éléments à insérer dans la db -> dataArticle | ||
+ | dataArticle = [] | ||
+ | # #7: pour chaque sélecteur contenu dans "selecteurs_article": | ||
+ | for selecteur_article in source['selecteurs_article']: | ||
+ | # #8: appliquer le sélecteur article sur la source de la page | ||
+ | applySelector = getResults(sourceUrl, selecteur_article['selecteur']) | ||
+ | # print(applySelector) | ||
+ | # #9: appliquer l'opération sur l'élément récupéré -> donnée | ||
+ | operateelement = getData(applySelector, selecteur_article['operation']) | ||
+ | #10: ajouter la donnée au tableau dataArticle | ||
+ | dataArticle.append(operateelement) | ||
+ | print(dataArticle) | ||
+ | |||
+ | if(len(dataArticle[0]) == 0): | ||
+ | continue | ||
+ | |||
+ | #[['Les Etats-Unis et la Chine «\xa0s’engagent à coopérer\xa0» sur la crise climatique'], ['2021-04-18 02:33:09']] | ||
+ | # #11: insérer le tableau dans la base de données | ||
+ | query = "INSERT INTO article (titre, date_crea, url, source_id) VALUES ('"+dataArticle[0][0]+"', '"+dataArticle[1][0]+"', '"+urlArticle+"', '"+str(sourceId)+"')" | ||
+ | print(query) | ||
+ | # query = "INSERT INTO article (titre, date_crea, source_id) VALUES ('%s', '%s', 1)" % (dataArticle[0], dataArticle[1]) | ||
+ | |||
+ | cursor = db.cursor() | ||
+ | cursor.execute(query) | ||
+ | db.commit() | ||
+ | </syntaxhighlight> | ||
+ | <br> | ||
+ | <br> | ||
+ | == '''Test de récupération de données et d'insertion dans la base de données avec une source (lemonde.fr)''' == | ||
+ | <br> | ||
+ | Résultats obtenus après lancement via le terminal : | ||
+ | <br> | ||
+ | <br> | ||
+ | [[Fichier:Mndr_test_lemonde.png]] | ||
+ | [[Fichier:mndr_test_lemonde2.png]] | ||
+ | <br> | ||
+ | Avec cette source, toutes les opérations fonctionnent. On remarque quu'il y a systématiquement et dans le même ordre : l'élément source de la page, l'url de l'article, son titre, sa date de publication dans le format que nous avons uniformisé, puis la requête sql pour insérer ces données dans les tables titre, date_crea, url et source_id de la base de données. | ||
+ | <br> | ||
+ | En regardant dans la base de données, on constate que celles-ci ont bien été récupérées et stockées de la manière attendue (c'est très satisfaisant à constater). | ||
+ | <br> | ||
+ | <br> | ||
+ | [[Fichier:Mndr_test_lemonde-bdd.png]] | ||
+ | |||
+ | === '''C'est cool mais : problèmes rencontrés à cette étape''' === | ||
+ | <br> | ||
+ | Apparemment, beaucoup de nos sélecteurs ne sont pas les bons. En lançant l'opération de récupération à partir d'une autre source (ici, leparisien.fr), le terminal nous indique qu'il n'y a pas les chaînes de caractères attendues dans les sélecteurs renseignés. | ||
+ | <br> | ||
+ | <br> | ||
+ | [[Fichier:Mndr_test_leparisien.png]] | ||
+ | <br> | ||
+ | <br> | ||
+ | À partir de là, il nous reste à repérer les sélecteurs adéquats pour chaque source, et tester le code source par source pour être sûr de ces sélecteurs. Malheureusement, nous n'avons pas encore pu terminer cette étape pour aujourd'hui 25.04, veille de la cotation du deuxième quadri. C'est un peu frustrant, mais c'est un travail qui sera fait très prochainement. | ||
<br> | <br> | ||
<br> | <br> | ||
− | + | Ensuite, nous pourrons exécuter des requêtes afin de récupérer certaines données, puis les utiliser dans le cadre de la deuxième partie de ce travail. |
Version actuelle datée du 25 avril 2021 à 17:17
Hello !
travail en cours : ranking de termes médiatiques
Quoi ?
Une page web affiche un ranking en temps réel des termes utilisés dans les articles d'une sélection de médias de Suisse romande.
Pour une sélection de sites internets, un classement des X mots les plus utilisés est présenté sous la forme d'un tableau de type indice boursier, avec les gains et les pertes en pourcentages de chaque terme.
Comment ?
Définir les sources
Une sélection de quatorze journaux suisse-romands a été retenue. Cette sélection contient la totalité des quotidiens de la partie francophone de Suisse; chaque canton est représenté. Les critères de sélection sont les suivants : le journal doit être un quotidien suisse-romand, il doit être francophone, il doit traiter de l’actualité internationale, nationale et cantonale et il doit diffuser ses informations sur un site internet et/ou une application entre autre.
Sélection:
· 24 heures (Canton de Vaud)
· Tribune de Genève (Canton de Genève)
· Le Temps (Canton de Genève)
· La Liberté (Canton de Fribourg)
· La Gruyère (Canton de Fribourg)
· Le Nouvelliste (Canton du Valais)
· Arc Info (Canton de Neuchâtel)
· Le Journal du Jura (partie francophone du Canton de Berne)
· Le Quotidien jurassien (Canton du Jura)
· La Côte (Région lémanique)
· La Région (Région Nord-vaudoise)
· Le Courrier (Romandie)
· Le Matin (Romandie)
· 20 Minutes (Romandie)
Diagramme de la base de données
Une structure de base de données a été créée sur sur phpMyAdmin. La base de données est structurée en catégories et sous-catégories distinctes. Toute informations récoltée est triée et classée dans la catégorie adéquate. Il y a trois catégories et trois sous-catégories :
—La catégorie article : elle regroupe l’ID de la catégorie article, le titre de l’article, son header, son corps de texte, sa date de publication, sa date de modification (s’il y en a une), son url, sa source
—— La sous-catégorie source : elle regroupe l’ID de la source , son nom et son url
—La catégorie article_aut·eur·ice: elle regroupe l’ID de la catégorie aut·eur·rice·s, l’ID de la sous-catégorie aut·eur·ice·s et l’ID des articles par aut·eur·rice·s
—— La sous-catégorie aut·eur·ice regroupe l’ID de chaque aut·eur·rice·s, son nom, son prénom et une courte bio (s’il y en a une)
—La catégorie article_mots: elle regroupe l’ID de la catégorie article_mots, l’ID de chaque article, l’ID de la sous-catégorie mots
—— La sous-catégorie mots : elle regroupe l’ID de chaque mot retenu et classé, le mot en question et la valeur de sa fréquence d’apparition
Schéma de la structure de la base de données
-- phpMyAdmin SQL Dump
-- version 4.9.5
-- https://www.phpmyadmin.net/
--
-- Host: localhost:8889
-- Generation Time: Feb 25, 2021 at 08:43 AM
-- Server version: 5.7.30
-- PHP Version: 7.4.9
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- Database: `indice_popularité_termes`
--
-- --------------------------------------------------------
--
-- Table structure for table `article`
--
CREATE TABLE `article` (
`id` int(10) UNSIGNED NOT NULL,
`titre` text NOT NULL,
`header` text NOT NULL,
`body` text NOT NULL,
`date_crea` datetime NOT NULL,
`date_modif` datetime NOT NULL,
`url` text NOT NULL,
`source_id` int(10) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Dumping data for table `article`
--
INSERT INTO `article` (`id`, `titre`, `header`, `body`, `date_crea`, `date_modif`, `url`, `source_id`) VALUES
(1, '1', '2', '3', '2020-10-07 12:11:40', '2020-10-14 12:11:40', '4', 4);
-- --------------------------------------------------------
--
-- Table structure for table `article_auteur`
--
CREATE TABLE `article_auteur` (
`id` int(10) UNSIGNED NOT NULL,
`id_article` int(10) UNSIGNED NOT NULL,
`id_auteurice` int(10) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Table structure for table `article_mots`
--
CREATE TABLE `article_mots` (
`id` int(10) UNSIGNED NOT NULL,
`id_article` int(11) NOT NULL,
`id_mots` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Table structure for table `aut-eur-rice`
--
CREATE TABLE `aut-eur-rice` (
`id` int(10) UNSIGNED NOT NULL,
`nom` text NOT NULL,
`prénom` text NOT NULL,
`bio` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Table structure for table `mots`
--
CREATE TABLE `mots` (
`id` int(10) UNSIGNED NOT NULL,
`mots` text NOT NULL,
`fréquence` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Table structure for table `source`
--
CREATE TABLE `source` (
`id` int(10) UNSIGNED NOT NULL,
`nom` text NOT NULL,
`url` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `article`
--
ALTER TABLE `article`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `article_auteur`
--
ALTER TABLE `article_auteur`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `article_mots`
--
ALTER TABLE `article_mots`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `aut-eur-rice`
--
ALTER TABLE `aut-eur-rice`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `mots`
--
ALTER TABLE `mots`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `source`
--
ALTER TABLE `source`
ADD PRIMARY KEY (`id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `article`
--
ALTER TABLE `article`
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
--
-- AUTO_INCREMENT for table `article_auteur`
--
ALTER TABLE `article_auteur`
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `article_mots`
--
ALTER TABLE `article_mots`
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `aut-eur-rice`
--
ALTER TABLE `aut-eur-rice`
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `mots`
--
ALTER TABLE `mots`
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `source`
--
ALTER TABLE `source`
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
Ecrire un programme (en python) pour récolter et stocker les informations contenues dans certains sélecteurs css :
1. repère et pointe les sélecteurs css qui contiennent les informations nécessaires
2. initialise un objet "navigateur" pour se connecter au site web à l'aide de la librairie mechanize
3. contourne une éventuelle demande de certificat de sécurité ssl pour accéder au contenu de la page
4. extrait les données de la page web contenues dans les sélecteurs css ciblés
5. lit les données
6. encode les données en utf-8
7. récupère le contenu des sélecteurs css et les traduit en expressions xpath
8. print les informations
Schéma du processus
Test du programme
Page cible
https://www.24heures.ch/les-cours-en-ligne-engendrent-des-inegalites-800936194846
Code
#un programme qui récupère les données contenues dans les sélecteurs css suivants à partir d'une page d'article de 24heures.ch
#article h1, article h2, article h3, article p et article time
import mechanize
import lxml.html as lh
import cssselect
import ssl
#initialisation d'un objet "navigateur" avec la librairie mechanize
br = mechanize.Browser()
br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]
br.set_handle_robots(False)
#fin de la configuration de mechanize
try:
_create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
# Legacy Python that doesn't verify HTTPS certificates by default
pass
else:
# Handle target environment that doesn't support HTTPS verification
ssl._create_default_https_context = _create_unverified_https_context
data = br.open('https://www.24heures.ch/les-cours-en-ligne-engendrent-des-inegalites-800936194846', timeout=10.0)
rawdata = data.read()
unicode = rawdata.decode('utf-8', 'ignore')
src = lh.fromstring(unicode)
#on convertit un sélecteur css en objets de type "cssselector"
selecteurs = cssselect.parse('article h1, article h2, article h3, article p, article time')
for selecteur in selecteurs:
chemin_xpath = cssselect.HTMLTranslator().selector_to_xpath(selecteur, translate_pseudo_elements=True)
resultats = src.xpath(chemin_xpath)
for resultat in resultats:
print(resultat.text_content())
Lancement du programme dans le terminal
Tentative de tri du texte avec des regex et de comptage de l'occurrence des mots
Le résultat est pas trop mal. J'aimerais maintenant comprendre faire pour garder des mots composés comme "en ligne" et aussi comment rendre cette opération plus efficace et l'inclure dans le programme dans Python..
11.03.21 : Travail en binôme avec Lea
Étant donné qu'un certain nombre de journaux fonctionnent par abonnement et que, du coup, on a pas toujours accès au corps des articles, on a choisi de se concentrer sur les titres de ces articles. On récupère alors les titres, la source et la date de publication.
On a aussi étendu géographiquement la sélection de journaux. On retient les quotidiens nationaux de Belgique, de Suisse, de France, du Luxembourg et de Monaco, qui ont une présence sur le net :
Mise à jour de la liste de quotidiens retenus
Belgique
Suisse
- https://www.24heures.ch
- https://www.lematin.ch
- https://www.lenouvelliste.ch
- https://www.tdg.ch
- https://www.arcinfo.ch
- https://www.heidi.news
- https://www.lacote.ch
- https://lecourrier.ch
- https://www.letemps.ch
- https://www.agefi.fr
- https://www.lqj.ch
France
- https://www.lefigaro.fr
- https://www.lemonde.fr
- https://www.lesechos.fr
- https://www.leparisien.fr
- https://www.liberation.fr
- https://www.la-croix.com
- https://www.lequipe.fr
- https://www.dhnet.be
Luxembourg
Monaco
Liste des sélecteurs
Mise à jour du schéma et de la base de données
Informations de connexion à la base de données :
#initialisation de l'objet db, interface avec la base de données mysql
db = mysql.connect(
host = "localhost",
port = "8889",
user = "root",
passwd = "root",
database = "indice_popularité_termes"
)
Tableaux et dictionnaires : un code unique qui regroupe toutes les sources et leurs sélecteurs
Afin d'avoir un unique code global dans lequel toutes les sources sont regroupées, on a vu l'utilisation de tableaux et de dictionnaires. Le tableau selecteurs contient les sources sous forme de dictionnaires répertoriant les sélecteurs nécessaires à la récupération des données visées (url de la source, url de l'article, titre de l'article et date de création de l'article).
selecteurs = [
{
'url':'http://lemonde.fr',
'selecteur_links':'h1 a',
'selecteurs_article':{
'titre':'article h1',
'date_pub':'span.date_pub',
'date_modif':'span.date_modif'
}
},
{
'url':'http://lesoir.be',
'selecteur_links':'h1 a',
'selecteurs_article':{
'titre':'article h1',
'date_pub':'span.date_pub',
'date_modif':'span.date_modif'
}
}
]
print(selecteurs[0]['selecteurs_article']['date_pub'])
À partir de là, le code se présente comme cela :
import time
import mechanize
import lxml.html as lh
import cssselect
def getSrcFromURL(url):
data = br.open(url)
rawdata = data.read()
unicode = rawdata.decode('utf-8', 'ignore')
src = lh.fromstring(unicode)
return src
def getResults(src, selecteurs):
results = []
cssSelectSelecteurs = cssselect.parse(selecteurs)
for cssSelectSelecteur in cssSelectSelecteurs:
chemin_xpath = cssselect.HTMLTranslator().selector_to_xpath(cssSelectSelecteur, translate_pseudo_elements=True)
results = results + src.xpath(chemin_xpath)
return results
def getData(htmlElements, operation):
data = []
for element in htmlElements:
if operation == 'get_href':
data.append(element.get('href'))
elif operation == 'text_content':
data.append(element.text_content())
elif operation == 'get_datetime':
data.append(element.get('datetime'))
return data
selecteurs = [
{
'url':'http://lemonde.fr',
'selecteur_links':{'name':'article_link', 'selecteur':'div.article a','operation':'get_href'},
'selecteurs_article':[
{'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'},
{'name': 'date_pub', 'selecteur': 'span.date_pub', 'operation':'text_content'},
{'name': 'date_modif', 'selecteur': 'span.date_modif', 'operation':'text_content'}
]
},
{
'url':'http://lesoir.be',
'selecteur_links':{'name':'article_link', 'selecteur':'h1 a','operation':'get_href'},
'selecteurs_article':[
{'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'},
{'name': 'date_pub', 'selecteur': 'span.date_pub', 'operation':'text_content'},
{'name': 'date_modif', 'selecteur': 'span.date_modif', 'operation':'text_content'}
]
}
]
#initialisation d'un objet "navigateur" avec la librairie mechanize
br = mechanize.Browser()
br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]
br.set_handle_robots(False)
#fin de la configuration de mechanize
Déroulement des opérations sur le site d'une source et stockage des données dans un tableau
Maintenant que nous avons écrit la structure de données générale des sources, il faut renseigner quoi faire avec les données visées par les sélecteurs. À cette étape, on stocke les données dans un tableau (dataArticle) qui sera en relation avec la base de données, dans laquelle les données seront classées comme dans le tableau dataArticle.
#0: pour chaque source dans le tableau sélecteurs #1: récupérer la première page de la source #2: appliquer le sélecteur contenu dans 'selecteur_links' #3: appliquer l'opération sur chaque élément récupéré -> url articles #4: pour chaque url article: #5: récupérer la source de la page désignée par l'url #6: déclarer un tableau vide qui contiendra les éléments à insérer dans la db "dataArticle" #7: pour chaque sélecteur contenu dans "selecteurs_article": #8: appliquer le sélecteur article sur la source de la page #9: appliquer l'opération sur l'élément récupéré -> donnée #10: ajouter la donnée au tableau dataArticle #11: insérer le tableau dans la base de données
#0: pour chaque source dans le tableau sélecteurs
for source in selecteurs:
#0:récupérer l'id de la source dans la base de données
sourceId = source['source_id']
#1: récupérer la première page de la source
premierePage = getSrcFromURL(source['url'])
#2: appliquer le sélecteur contenu dans 'selecteur_links'
liensArticles = getResults(premierePage, source['selecteur_links']['selecteur'])
#3: appliquer l'opération sur chaque élément récupéré -> url articles
urlsArticles = getData(liensArticles, source['selecteur_links']['operation'])
#4: pour chaque url article:
for urlArticle in urlsArticles:
#5: récupérer la source de la page désignée par l'url
#print(urlArticle)
sourceUrl = getSrcFromURL(urlArticle)
print(sourceUrl)
print(urlArticle)
#print(sourceUrl)
#6: déclarer un tableau vide qui contiendra les éléments à insérer dans la db -> dataArticle
dataArticle = []
#7: pour chaque sélecteur contenu dans "selecteurs_article":
for selecteur_article in source['selecteurs_article']:
#8: appliquer le sélecteur article sur la source de la page
applySelector = getResults(sourceUrl, selecteur_article['selecteur'])
# print(applySelector)
#9: appliquer l'opération sur l'élément récupéré -> donnée
operateelement = getData(applySelector, selecteur_article['operation'])
#10: ajouter la donnée au tableau dataArticle
dataArticle.append(operateelement)
print(dataArticle)
Quoi faire avec le contenus des éléments HTML visés et uniformisation du format de date et heure de publication
#1: Définir l'opération getData
def getData(htmlElements, operation):
#2: déclarer un tableau vide
data = []
#3: pour chaque élément dans htmlElements:
for element in htmlElements:
#4: SI (condition) l'opération consiste à récupérer l'attribut href:
if operation == 'get_href':
#4bis: stocker la valeur de l'attribut href dans le tableau "data":
data.append(element.get('href'))
#5: aussi, si l'operation consiste à récupérer la date:
elif operation == 'get_content_date':
#2021-04-19T10:09:10+00:00
#2021-04-19 10:09:10
#6: déclarer une fonction "date" qui consiste à récupérer le contenu visé:
date = element.get('content')
#7: remplacer le caractère "T" au milieu de la châine par un espace pour le supprimer:
date = date.replace('T', ' ')
#8: ?
date = date[0:19]
#9: stocker la chaîne de caractère qui représente la date dans le tableau "data":
data.append(date)
#9.1: ou sinon, si l'opération consiste à récupérer la valeur de text_content:
elif operation == 'text_content':
#9.2: stocker la valeur de text_content dans le tableau "data":
data.append(element.text_content())
#9.3: ou sinon, si l'opération consiste à récupérer la valeur de datetime:
elif operation == 'get_datetime':
#9.4: stocker la valeur de datetime dans le tableau "data":
data.append(element.get('datetime'))
#10: retourne la donnée récupérée
return data
À partir de là, il est possible de récupérer les données et de les envoyer à la base de données pour qu'elles soient classées selon les critères retenus : la source et son ID, l'url de l'article et son ID, le titre de l'article et la date de publication
Aperçu du code à cette étape
import time
import mechanize
import lxml.html as lh
import cssselect
import ssl
import os
from urllib.parse import urlparse
import mysql.connector as mysql
def getSrcFromURL(url):
data = br.open(url)
rawdata = data.read()
unicode = rawdata.decode('utf-8', 'ignore')
src = lh.fromstring(unicode)
return src
def getResults(src, selecteurs):
results = []
cssSelectSelecteurs = cssselect.parse(selecteurs)
for cssSelectSelecteur in cssSelectSelecteurs:
chemin_xpath = cssselect.HTMLTranslator().selector_to_xpath(cssSelectSelecteur, translate_pseudo_elements=True)
results = results + src.xpath(chemin_xpath)
return results
#initialisation de l'objet db, interface avec la base de données mysql
db = mysql.connect(
host = "localhost",
port = "8889",
user = "root",
passwd = "root",
database = "indice_popularité_termes"
)
def getData(htmlElements, operation):
data = []
for element in htmlElements:
if operation == 'get_href':
data.append(element.get('href'))
elif operation == 'get_content_date':
#2021-04-19T10:09:10+00:00
#2021-04-19 10:09:10
date = element.get('content')
date = date.replace('T', ' ')
date = date[0:19]
data.append(date)
elif operation == 'text_content':
data.append(element.text_content())
elif operation == 'get_datetime':
data.append(element.get('datetime'))
return data
selecteurs = [
# {
# 'url':'https://www.lenouvelliste.ch',
# 'selecteur_links':{'name':'article_link', 'selecteur':'h1 a, h3 a', 'operation':'get_href'},
# 'selecteurs_article':[
# {'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'},
# {'name': 'date_pub', 'selecteur': 'article datetime', 'operation':'text_content'},
# ]
# },
# {
# 'url':'https://www.arcinfo.ch',
# 'selecteur_links':{'name':'article_link', 'selecteur':'h1 a, h3 a', 'operation':'get_href'},
# 'selecteurs_article':[
# {'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'},
# {'name': 'date_pub', 'selecteur': 'article datetime', 'operation':'text_content'},
# ]
# },
# {
# 'url':'https://www.heidi.news',
# 'selecteur_links':{'name':'article_link', 'selecteur':'h3 a, h2 a', 'operation':'get_href'},
# 'selecteurs_article':[
# {'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'},
# {'name': 'date_pub', 'selecteur': 'article datetime', 'operation':'text_content'},
# ]
# },
# {
# 'url':'https://www.lacote.ch',
# 'selecteur_links':{'name':'article_link', 'selecteur':'h3 a, h2 a', 'operation':'get_href'},
# 'selecteurs_article':[
# {'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'},
# {'name': 'date_pub', 'selecteur': 'article datetime', 'operation':'text_content'},
# ]
# },
# {
# 'url':'https://lecourrier.ch',
# 'selecteur_links':{'name':'article_link', 'selecteur':'a.c-Card-permalink', 'operation':'get_href'},
# 'selecteurs_article':[
# {'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'},
# {'name': 'date_pub', 'selecteur': 'article metaDate', 'operation':'text_content'},
# ]
# },
{
'url':'https://lemonde.fr',
'source_id':1,
'selecteur_links':{'name':'article_link', 'selecteur':'div.article a','operation':'get_href'},
'selecteurs_article':[
{'name': 'titre', 'selecteur': 'h1.article__title', 'operation':'text_content'},
{'name': 'date_pub', 'selecteur': 'meta[property="og:article:published_time"]', 'operation':'get_content_date'},
]
},
# {
# 'url':'https://www.lefigaro.fr',
# 'selecteur_links':{'name':'article_link', 'selecteur':'a.ensemble__link, a.fig-profile__link, fig-ensemble__link, a.fig-ensemble__first-article-link', 'operation':'get_href'},
# 'selecteurs_article':[
# {'name': 'titre', 'selecteur': 'h1', 'operation':'text_content'},
# {'name': 'date_pub', 'selecteur': 'span.fig-content-metas__pub-date fig-content-metas__pub-date--hide-small', 'operation':'text_content'},
# ]
# },
# {
# 'url':'https://www.la-croix.com',
# 'selecteur_links':{'name':'article_link', 'selecteur':'a.block-item__title', 'operation':'get_href'},
# 'selecteurs_article':[
# {'name': 'titre', 'selecteur': 'article h1', 'operation':'text_content'},
# {'name': 'date_pub', 'selecteur': 'div .font_xs color_grey margin-xxs-right font_tertiary', 'operation':'text_content'},
# ]
# },
]
#initialisation d'un objet "navigateur" avec la librairie mechanize
br = mechanize.Browser()
br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]
br.set_handle_robots(False)
#fin de la configuration de mechanize
try:
_create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
# Legacy Python that doesn't verify HTTPS certificates by default
pass
else:
# Handle target environment that doesn't support HTTPS verification
ssl._create_default_https_context = _create_unverified_https_context
#0: pour chaque source dans le tableau sélecteurs
for source in selecteurs:
#0:récupérer l'id de la source dans la base de données
sourceId = source['source_id']
#1: récupérer la première page de la source
premierePage = getSrcFromURL(source['url'])
#2: appliquer le sélecteur contenu dans 'selecteur_links'
liensArticles = getResults(premierePage, source['selecteur_links']['selecteur'])
#3: appliquer l'opération sur chaque élément récupéré -> url articles
urlsArticles = getData(liensArticles, source['selecteur_links']['operation'])
#4: pour chaque url article:
for urlArticle in urlsArticles:
#5: checker si l'url de l'article se trouve déjà dans la table article de la db. si il y est, skipper le reste des opérations
query = "SELECT * FROM article WHERE url='"+urlArticle+"'"
alreadyInDB = False
with db.cursor() as cursor:
cursor.execute(query)
result = cursor.fetchall()
if len(result) > 0:
alreadyInDB = True
if(alreadyInDB == True):
continue
#5bis: récupérer la source de la page désignée par l'url
# print(urlArticle)
sourceUrl = getSrcFromURL(urlArticle)
print(sourceUrl)
print(urlArticle)
# print(sourceUrl)
# #6: déclarer un tableau vide qui contiendra les éléments à insérer dans la db -> dataArticle
dataArticle = []
# #7: pour chaque sélecteur contenu dans "selecteurs_article":
for selecteur_article in source['selecteurs_article']:
# #8: appliquer le sélecteur article sur la source de la page
applySelector = getResults(sourceUrl, selecteur_article['selecteur'])
# print(applySelector)
# #9: appliquer l'opération sur l'élément récupéré -> donnée
operateelement = getData(applySelector, selecteur_article['operation'])
#10: ajouter la donnée au tableau dataArticle
dataArticle.append(operateelement)
print(dataArticle)
if(len(dataArticle[0]) == 0):
continue
#[['Les Etats-Unis et la Chine «\xa0s’engagent à coopérer\xa0» sur la crise climatique'], ['2021-04-18 02:33:09']]
# #11: insérer le tableau dans la base de données
query = "INSERT INTO article (titre, date_crea, url, source_id) VALUES ('"+dataArticle[0][0]+"', '"+dataArticle[1][0]+"', '"+urlArticle+"', '"+str(sourceId)+"')"
print(query)
# query = "INSERT INTO article (titre, date_crea, source_id) VALUES ('%s', '%s', 1)" % (dataArticle[0], dataArticle[1])
cursor = db.cursor()
cursor.execute(query)
db.commit()
Test de récupération de données et d'insertion dans la base de données avec une source (lemonde.fr)
Résultats obtenus après lancement via le terminal :
Avec cette source, toutes les opérations fonctionnent. On remarque quu'il y a systématiquement et dans le même ordre : l'élément source de la page, l'url de l'article, son titre, sa date de publication dans le format que nous avons uniformisé, puis la requête sql pour insérer ces données dans les tables titre, date_crea, url et source_id de la base de données.
En regardant dans la base de données, on constate que celles-ci ont bien été récupérées et stockées de la manière attendue (c'est très satisfaisant à constater).
C'est cool mais : problèmes rencontrés à cette étape
Apparemment, beaucoup de nos sélecteurs ne sont pas les bons. En lançant l'opération de récupération à partir d'une autre source (ici, leparisien.fr), le terminal nous indique qu'il n'y a pas les chaînes de caractères attendues dans les sélecteurs renseignés.
À partir de là, il nous reste à repérer les sélecteurs adéquats pour chaque source, et tester le code source par source pour être sûr de ces sélecteurs. Malheureusement, nous n'avons pas encore pu terminer cette étape pour aujourd'hui 25.04, veille de la cotation du deuxième quadri. C'est un peu frustrant, mais c'est un travail qui sera fait très prochainement.
Ensuite, nous pourrons exécuter des requêtes afin de récupérer certaines données, puis les utiliser dans le cadre de la deuxième partie de ce travail.