Skip to content
Extraits de code Groupes Projets

Comparer les révisions

Les modifications sont affichées comme si la révision source était fusionnée avec la révision cible. En savoir plus sur la comparaison des révisions.

Source

Sélectionner le projet cible
No results found

Cible

Sélectionner le projet cible
  • la-quadrature-du-net/Attrap
  • foggyfrog/Attrap
  • skhwiz/Attrap
  • precambrien/Attrap
  • ketsapiwiq/Attrap
  • Joseki/Attrap
  • kr1p/attrap-pref-12
  • kr1p/attrap-pref-46
  • kr1p/attrap-pi
  • Guinness/Attrap
  • astroidgritty/attrap-pref-84
  • davinov/Attrap
  • maettellite/attrap-pref-01
  • m242/Attrap
  • multi/Attrap
  • mverdeil/Attrap
  • olpo/Attrap
17 résultats
Afficher les modifications
Validations sur la source (10)
......@@ -158,6 +158,11 @@ test_pref44:
PREF: "pref44"
extends: .default_pref
test_pref47:
variables:
PREF: "pref47"
extends: .default_pref
test_pref59:
variables:
PREF: "pref59"
......
......@@ -41,6 +41,8 @@ logger = logging.getLogger(__name__)
class Attrap:
class RAA:
"""La classe représentant un Recueil des actes administratifs. La plupart du temps, il s'agit d'un PDF avec plusieurs arrêtés."""
url = ""
date = datetime.datetime(1970, 1, 1)
date_str = ""
......@@ -59,11 +61,13 @@ class Attrap:
self.name = name
def get_sha256(self):
"""Calcule et met en cache le hash sha256 de l'URL du PDF, pour servir d'identifiant unique."""
if (self.sha256 == ""):
self.sha256 = hashlib.sha256(self.url.encode('utf-8')).hexdigest()
return self.sha256
def get_pdf_dates(self, data_dir):
"""Extrait les dates des PDF pour les ajouter à l'objet du RAA."""
raa_data_dir = f'{data_dir}/raa/'
reader = PdfReader(f'{raa_data_dir}{self.get_sha256()}.pdf')
......@@ -77,6 +81,7 @@ class Attrap:
self.pdf_modification_date = pdf_metadata.modification_date
def extract_content(self, data_dir):
"""Extrait le contenu du PDF OCRisé pour l'écrire dans le fichier qui servira à faire la recherche de mots-clés. Supprime tous les PDF à la fin."""
raa_data_dir = f'{data_dir}/raa/'
text = ""
......@@ -100,6 +105,7 @@ class Attrap:
os.remove(f'{raa_data_dir}{self.get_sha256()}.flat.pdf')
def write_properties(self, data_dir):
"""Écris les propriétés du RAA dans un fichier JSON."""
raa_data_dir = f'{data_dir}/raa/'
pdf_creation_date_json = None
......@@ -123,6 +129,7 @@ class Attrap:
f.close()
def parse_metadata(self, data_dir):
"""Lance l'extraction des dates du PDF puis l'écriture de ses propriétés dans un fichier JSON."""
self.get_pdf_dates(data_dir)
self.write_properties(data_dir)
......@@ -140,7 +147,7 @@ class Attrap:
self.tor_enabled = False
self.tor_max_requests = 0
self.tor_requests = 0
self.not_before = datetime.datetime(2024, 1, 1)
self.not_before = datetime.datetime(2015, 1, 1)
self.smtp_configured = False
self.mastodon = None
self.mastodon_prefix = ''
......@@ -155,6 +162,7 @@ class Attrap:
self.print_output(str(self.__class__.__name__))
def configure_mastodon(self, access_token, instance, mastodon_prefix, mastodon_suffix):
"""Configuration de Mastodon afin de publier un toot à chaque RAA détecté."""
if access_token and access_token != "" and instance and instance != "":
self.mastodon = Mastodon(
access_token=access_token,
......@@ -164,6 +172,7 @@ class Attrap:
self.mastodon_suffix = mastodon_suffix
def mastodon_toot(self, content):
"""Publie le toot en ajoutant le header et footer configurés."""
if self.mastodon:
toot = content
if not self.mastodon_prefix == '':
......@@ -173,6 +182,7 @@ class Attrap:
self.mastodon.toot(toot)
def enable_tor(self, max_requests=0):
"""Active l'utilisation de Tor pour effectuer les requêtes."""
proxies = {
"http": f"socks5h://127.0.0.1:9050",
"https": f"socks5h://127.0.0.1:9050",
......@@ -184,6 +194,7 @@ class Attrap:
self.tor_get_new_id()
def disable_tor(self):
"""Désactive l'utilisation de Tor."""
proxies = {}
self.tor_enabled = False
self.tor_max_requests = 0
......@@ -191,6 +202,7 @@ class Attrap:
self.session.proxies.update(proxies)
def tor_get_new_id(self):
"""Change d'identité Tor. Cela permet de changer de noeud de sortie donc d'IP."""
if self.tor_enabled:
logger.info('Changement d\'identité Tor')
try:
......@@ -204,6 +216,14 @@ class Attrap:
logger.debug(f'Impossible de changer d\'identité Tor: {exc}')
def get_sub_pages(self, page_content, element, host, recursive_until_pdf):
"""
Récupère, à partir d'un chemin CSS, les sous-pages d'une page.
page_content -- Un contenu HTML à analyser
element -- Le chemin CSS vers l'objet renvoyant vers la sous-page recherchée
host -- Le nom d'hôte du site
recursive_until_pdf -- Un booléen pour savoir s'il faut rechercher un fichier PDF dans le chemin CSS. Le cas échéant, relance la recherche sur la sous-page si le lien n'est pas un PDF.
"""
soup = BeautifulSoup(page_content, 'html.parser')
sub_pages = []
for a in soup.select(element):
......@@ -237,6 +257,15 @@ class Attrap:
return sub_pages
def get_sub_pages_with_pager(self, page, sub_page_element, pager_element, details_element, host):
"""
Récupère, à partir d'un chemin CSS, les sous-pages d'une page contenant un pager.
page -- L'URL de la page à analyser
sub_page_element -- Le chemin CSS vers l'objet renvoyant vers la sous-page recherchée
pager_element -- Le chemin CSS vers le lien de page suivante du pager
details_element -- Le chemin CSS vers l'objet contenant les détails de la sous-page recherchée
host -- Le nom d'hôte du site
"""
pages = []
page_content = self.get_page(page, 'get').content
......@@ -276,6 +305,13 @@ class Attrap:
return pages
def get_raa_with_pager(self, pages_list, pager_element, host):
"""
Récupère et analyse les RAA d'une page contenant un pager.
pages_list -- Un tableau contenant la liste des pages
pager_element -- Le chemin CSS vers le lien de page suivante du pager
host -- Le nom d'hôte du site
"""
elements = []
# On parse chaque page passée en paramètre
for page in pages_list:
......@@ -303,9 +339,15 @@ class Attrap:
return elements
def set_sleep_time(self, sleep_time):
"""Configure le temps de temporisation"""
self.sleep_time = sleep_time
def has_pdf(self, page_content):
"""
Renvoie un booléen Vrai si la page contient un lien vers un PDF
page_content -- Un contenu HTML à analyser
"""
elements = []
soup = BeautifulSoup(page_content, 'html.parser')
for a in soup.find_all('a', href=True):
......@@ -315,6 +357,13 @@ class Attrap:
# On démarre le navigateur
def get_session(self, url, wait_element, remaining_retries=0):
"""
Lance un navigateur avec Selenium.
url -- URL à interroger
wait_element -- Élement (désigné par son identifiant CSS) qui indique que la page est chargée
remaining_retries -- Nombre d'échecs autorisé avant de soulever une erreur
"""
webdriver_options = webdriver.ChromeOptions()
webdriver_options.add_argument("--no-sandbox")
webdriver_options.add_argument("--disable-extensions")
......@@ -369,6 +418,7 @@ class Attrap:
return page_content
def print_output(self, data):
"""Affiche dans le terminal et dans le fichier de log un texte"""
print(data)
data = data.replace('\033[92m', '')
data = data.replace('\033[0m', '')
......@@ -378,6 +428,13 @@ class Attrap:
f.close()
def get_page(self, url, method, data={}):
"""
Récupère le contenu HTML d'une page web
url -- L'URL de la page demandée
method -- 'post' ou 'get', selon le type de requête
data -- Un dictionnaire contenant les données à envoyer au site
"""
try:
logger.debug(f'Chargement de la page {url}')
if self.sleep_time > 0:
......@@ -405,17 +462,19 @@ class Attrap:
except requests.exceptions.ConnectionError:
logger.warning(f'Erreur de connexion, temporisation...')
self.tor_get_new_id()
time.sleep(55)
time.sleep(60)
return self.get_page(url, method, data)
except requests.exceptions.Timeout:
logger.warning(f'Timeout, on relance la requête...')
return self.get_page(url, method, data)
def update_user_agent(self, user_agent):
"""Change la valeur du user-agent"""
self.user_agent = user_agent
self.session.headers.update({'User-Agent': self.user_agent})
def download_file(self, raa):
"""Télécharge un RAA"""
try:
os.makedirs(
os.path.dirname(f'{self.data_dir}/raa/{raa.get_sha256()}.pdf'),
......@@ -433,6 +492,7 @@ class Attrap:
logger.warning(f'ATTENTION: Impossible de télécharger le fichier {raa.url}: {exc}')
def ocr(self, raa, retry_on_failure=True):
"""OCRise un RAA"""
cmd = [
'ocrmypdf',
'-l', 'eng+fra',
......@@ -460,7 +520,7 @@ class Attrap:
shutil.copy(f'{self.data_dir}/raa/{raa.get_sha256()}.pdf', f'{self.data_dir}/raa/{raa.get_sha256()}.ocr.pdf')
def flatten_pdf(self, raa):
# OCRmyPDF ne sait pas gérer les formulaires, donc on les enlève avant OCRisation
"""Supprime les formulaires d'un PDF pour pouvoir les OCRiser après dans OCRmyPDF."""
reader = PdfReader(f'{self.data_dir}/raa/{raa.get_sha256()}.pdf')
writer = PdfWriter()
......@@ -475,6 +535,7 @@ class Attrap:
writer.write(f'{self.data_dir}/raa/{raa.get_sha256()}.flat.pdf')
def search_keywords(self, raa, keywords):
"""Recherche des mots-clés dans le texte extrait du PDF"""
if keywords and not keywords == '':
text = open(f'{self.data_dir}/raa/{raa.get_sha256()}.txt').read()
......@@ -503,6 +564,12 @@ class Attrap:
)
def parse_raa(self, elements, keywords):
"""
Démarre l'analyse des RAA.
elements -- Un tableau contenant les RAA à analyser
keywords -- Les mots-clés à rechercher dans chaque RAA
"""
self.print_output(f'Termes recherchés: {keywords}')
self.print_output('')
......@@ -525,6 +592,19 @@ class Attrap:
def configure_mailer(self, smtp_host, smtp_username, smtp_password,
smtp_port, smtp_starttls, smtp_ssl, email_from,
email_to, email_object):
"""
Configure les courriers électroniques.
smtp_host -- Nom d'hôte du serveur SMTP
smtp_username -- Nom d'utilisateur pour se connecter au serveur SMTP
smtp_password -- Mot de passe pour se connecter au serveur SMTP
smtp_port -- Port de connexion au serveur SMTP
smtp_starttls -- Booléen. Si vrai, se connecte avec STARTTLS
smtp_ssl -- Booléen. Si vrai, se connecte avec SSL/TLS
email_from -- Adresse électronique de l'expéditeur des notifications
email_to -- Tableau contenant les adresses électroniques à notifier
email_object -- Objet du courrier électronique de notification
"""
self.smtp_host = smtp_host
self.smtp_username = smtp_username
self.smtp_password = smtp_password
......@@ -585,9 +665,11 @@ class Attrap:
except Exception as exc:
logger.warning(f'Impossible d\'envoyer le courrier électronique : {exc}')
# Fonction qui essaie de deviner la date d'un RAA à partir de son nom.
# Utile pour limiter les requêtes lors de l'obtention des RAA à scanner.
def guess_date(string, regex):
"""
Essaie de deviner la date d'un RAA à partir de son nom.
Utile pour limiter les requêtes lors de l'obtention des RAA à scanner.
"""
try:
search = re.search(regex, string, re.IGNORECASE)
guessed_date = dateparser.parse(search.group(1))
......
import os
import datetime
from bs4 import BeautifulSoup
from urllib.parse import unquote
import re
from Attrap import Attrap
class Attrap_pref46(Attrap):
# Config
__HOST = 'https://www.aveyron.gouv.fr'
__RAA_PAGE = f'{__HOST}/Publications/Recueil-des-actes-administratifs'
__USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0'
full_name = "Préfecture de l'Aveyron"
short_code = 'pref12'
def __init__(self, data_dir):
super().__init__(data_dir, self.__USER_AGENT)
self.enable_tor(10)
self.not_before = datetime.datetime(2020, 1, 1)# avant 2020 page "archives"
#supercharge pour classes css avec whitespaces
def get_sub_pages_with_pager(self, page, sub_page_element, pager_element, details_element, host):
pages = []
page_content = self.get_page(page, 'get').content
# On initialise le parser
soup = BeautifulSoup(page_content, 'html.parser')
# On recherche les sous-pages
sub_pages = soup.select(sub_page_element)
sub_pages_details = None
if details_element is not None:
sub_pages_details = soup.select(details_element)
i = 0
for sub_page in sub_pages:
print(sub_page)
if sub_page.get('href'):
page = {
'url': f"{host}{sub_page['href']}",
'name': sub_page.get_text().strip(),
'details': ''
}
if details_element is not None:
page['details'] = sub_pages_details[i].get_text().strip()
pages.append(page)
i = i + 1
# On recherche un pager, et si on le trouve on le suit
# modif ici, le parametre pager_element ne doit contenir
# que le contenu de la classe
# et pas l'element pour les classes avec whitespaces
pager = soup.find_all("a",class_=pager_element)
print(pager)
if pager and len(pager)>0 and pager[0].get('href'):
for sub_page in self.get_sub_pages_with_pager(
f"{host}{pager[0]['href']}",
sub_page_element,
pager_element,
details_element,
host
):
pages.append(sub_page)
return pages
def get_raa(self, keywords):
elements = []
page_content = self.get_page(self.__RAA_PAGE, 'get').content
soup = BeautifulSoup(page_content, 'html.parser')
#selection des grey cards
for a in soup.select('div.fr-card--grey div.fr-card__body div.fr-card__content h2.fr-card__title a'):
#regular
if Attrap.guess_date(f'{a.get_text().strip()}', '([0-9]{4}).*').year >= self.not_before.year and "Archives" not in f'{a.get_text().strip()}':
page_content = self.get_page(f"{self.__HOST}{a['href']}", 'get').content
for sub_page in self.get_sub_pages_with_pager(f"{self.__HOST}{a['href']}", 'div.fr-card__body div.fr-card__content h2.fr-card__title a', 'fr-pagination__link fr-pagination__link--next fr-pagination__link--lg-label', None, self.__HOST):
sub_page_content = self.get_page(sub_page['url'], 'get').content
for element in self.get_raa_elements(sub_page_content):
elements.append(element)
#archives
elif self.not_before.year<2021 and "Archives" in f'{a.get_text().strip()}':
page_content = self.get_page(f"{self.__HOST}{a['href']}", 'get').content
for sub_page in self.get_sub_pages(page_content,
'div.fr-card__body div.fr-card__content h2.fr-card__title a',
self.__HOST,
True):
sub_page_content = self.get_page(sub_page['url'], 'get').content
for a in sub_page_content.select('div.fr-card__body div.fr-card__content h2.fr-card__title a'):
sub_sub_page_content = self.get_page(a['url'], 'get').content
for element in self.get_raa_elements(sub_sub_page_content):
elements.append(element)
#selection des "spécials"
for div in soup.select("div.fr-card.fr-card--horizontal.fr-card--sm.fr-enlarge-link.fr-mb-3w"):
for a in div.select("div.fr-card__body div.fr-card__content h2.fr-card__title a"):
print(a)
search_pattern=re.search('(?<=Publié le).*',f'{a.parent.parent.get_text()}')
if search_pattern:
if Attrap.guess_date(search_pattern[0], '([0-9]{4}).*').year>=self.not_before.year:
page_content = self.get_page(f"{self.__HOST}{a['href']}", 'get').content
for sub_page in self.get_sub_pages(page_content,
'div.fr-card__body div.fr-card__content h2.fr-card__title a',
self.__HOST,
True):
sub_page_content = self.get_page(sub_page['url'], 'get').content
for element in self.get_raa_elements(sub_page_content):
elements.append(element)
#bug sur ocrmypdf sur mon ubuntu 20.04 (test avec arch prochainement)
#sur --invalidate-digital-signatures bien que dans la doc
#ici https://ocrmypdf.readthedocs.io/en/latest/pdfsecurity.html
self.parse_raa(elements, keywords)
self.mailer()
def get_raa_elements(self, page_content):
elements = []
# On charge le parser
soup = BeautifulSoup(page_content, 'html.parser')
# Pour chaque balise a, on regarde si c'est un PDF, et si oui on le
# parse
print(soup.find_all("a",{"id":'class="fr-link'}))
print(len(soup.find_all("a",{"id":'class="fr-link'})))
for a in soup.find_all("a",{"id":'class="fr-link'}):
if a.get('href') and a['href'].endswith('.pdf'):
if a['href'].startswith('/'):
url = f"{self.__HOST}{a['href']}"
else:
url = a['href']
url = unquote(url)
name = a.find('span').previous_sibling.replace('Télécharger ', '').strip()
date = datetime.datetime.strptime(a.find('span').get_text().split(' - ')[-1].strip(), '%d/%m/%Y')
raa = Attrap.RAA(url, date, name)
self.download_file(raa)
elements.append(raa)
print(elements)
return elements
import os
import datetime
from bs4 import BeautifulSoup
from urllib.parse import unquote
from Attrap import Attrap
class Attrap_pref40(Attrap):
# Config
__HOST = 'https://www.landes.gouv.fr'
__RAA_PAGE = f'{__HOST}/Publications/Publications-legales/Le-Recueil-des-Actes-Administratifs-RAA'
__USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0'
full_name = 'Préfecture des Landes'
short_code = 'pref40'
def __init__(self, data_dir):
super().__init__(data_dir, self.__USER_AGENT)
self.enable_tor(10)
self.not_before = datetime.datetime(2016, 1, 1)# avant 2016 page "années antérieures"
#supercharge pour classes css avec whitespaces
def get_sub_pages_with_pager(self, page, sub_page_element, pager_element, details_element, host):
pages = []
page_content = self.get_page(page, 'get').content
# On initialise le parser
soup = BeautifulSoup(page_content, 'html.parser')
# On recherche les sous-pages
sub_pages = soup.select(sub_page_element)
sub_pages_details = None
if details_element is not None:
sub_pages_details = soup.select(details_element)
i = 0
for sub_page in sub_pages:
print(sub_page)
if sub_page.get('href'):
page = {
'url': f"{host}{sub_page['href']}",
'name': sub_page.get_text().strip(),
'details': ''
}
if details_element is not None:
page['details'] = sub_pages_details[i].get_text().strip()
pages.append(page)
i = i + 1
# On recherche un pager, et si on le trouve on le suit
# modif ici, le parametre pager_element ne doit contenir
# que le contenu de la classe
# et pas l'element pour les classes avec whitespaces
pager = soup.find_all("a",class_=pager_element)
print(pager)
if pager and len(pager)>0 and pager[0].get('href'):
for sub_page in self.get_sub_pages_with_pager(
f"{host}{pager[0]['href']}",
sub_page_element,
pager_element,
details_element,
host
):
pages.append(sub_page)
return pages
def get_raa(self, keywords):
elements = []
page_content = self.get_page(self.__RAA_PAGE, 'get').content
soup = BeautifulSoup(page_content, 'html.parser')
for a in soup.select('div.fr-card__body div.fr-card__content h2.fr-card__title a'):
# Annee archivees
if self.not_before.year<2016 and "antérieures" in a.get_text().strip():
print(f"{a.get_text().strip()}")
page_content = self.get_page(f"{self.__HOST}{a['href']}", 'get').content
for sub_page in self.get_sub_pages_with_pager(f"{self.__HOST}{a['href']}", 'div.fr-card__body div.fr-card__content h2.fr-card__title a', 'fr-pagination__link fr-pagination__link--next fr-pagination__link--lg-label', None, self.__HOST):
if Attrap.guess_date(f'{sub_page.get_text().strip()}', '([0-9]{4}).*').year>=self.not_before.year:
sub_page_content = self.get_page(sub_page['url'], 'get').content
for element in self.get_raa_elements(sub_page_content):
elements.append(element)
# Annees non archivees
elif Attrap.guess_date(f'{a.get_text().strip()}', '([0-9]{4}).*').year >= self.not_before.year and "antérieures" not in a.get_text().strip():
print(f"{a.get_text().strip()}")
page_content = self.get_page(f"{self.__HOST}{a['href']}", 'get').content
for sub_page in self.get_sub_pages_with_pager(f"{self.__HOST}{a['href']}", 'div.fr-card__body div.fr-card__content h2.fr-card__title a', 'fr-pagination__link fr-pagination__link--next fr-pagination__link--lg-label', None, self.__HOST):
sub_page_content = self.get_page(sub_page['url'], 'get').content
for element in self.get_raa_elements(sub_page_content):
elements.append(element)
#bug sur ocrmypdf sur mon ubuntu 20.04 (test avec arch prochainement)
#sur --invalidate-digital-signatures bien que dans la doc
#ici https://ocrmypdf.readthedocs.io/en/latest/pdfsecurity.html
self.parse_raa(elements, keywords)
self.mailer()
def get_raa_elements(self, page_content):
elements = []
# On charge le parser
soup = BeautifulSoup(page_content, 'html.parser')
# Pour chaque balise a, on regarde si c'est un PDF, et si oui on le
# parse
for a in soup.find_all("a",{"id":'class="fr-link'}):
if a.get('href') and a['href'].endswith('.pdf'):
if a['href'].startswith('/'):
url = f"{self.__HOST}{a['href']}"
else:
url = a['href']
url = unquote(url)
name = a.find('span').previous_sibling.replace('Télécharger ', '').strip()
date = datetime.datetime.strptime(a.find('span').get_text().split(' - ')[-1].strip(), '%d/%m/%Y')
raa = Attrap.RAA(url, date, name)
self.download_file(raa)
elements.append(raa)
print(elements)
return elements
import os
import datetime
from bs4 import BeautifulSoup
from urllib.parse import unquote
import re
from Attrap import Attrap
class Attrap_pref46(Attrap):
# Config
__HOST = 'https://www.lot.gouv.fr'
__RAA_PAGE = f'{__HOST}/Publications/Recueil-des-Actes-Administratifs'
__USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0'
full_name = 'Préfecture du Lot'
short_code = 'pref46'
def __init__(self, data_dir):
super().__init__(data_dir, self.__USER_AGENT)
self.enable_tor(10)
self.not_before = datetime.datetime(2020, 1, 1)# avant 2020 page "archives"
#supercharge pour classes css avec whitespaces
def get_sub_pages_with_pager(self, page, sub_page_element, pager_element, details_element, host):
pages = []
page_content = self.get_page(page, 'get').content
# On initialise le parser
soup = BeautifulSoup(page_content, 'html.parser')
# On recherche les sous-pages
sub_pages = soup.select(sub_page_element)
sub_pages_details = None
if details_element is not None:
sub_pages_details = soup.select(details_element)
i = 0
for sub_page in sub_pages:
print(sub_page)
if sub_page.get('href'):
page = {
'url': f"{host}{sub_page['href']}",
'name': sub_page.get_text().strip(),
'details': ''
}
if details_element is not None:
page['details'] = sub_pages_details[i].get_text().strip()
pages.append(page)
i = i + 1
# On recherche un pager, et si on le trouve on le suit
# modif ici, le parametre pager_element ne doit contenir
# que le contenu de la classe
# et pas l'element pour les classes avec whitespaces
pager = soup.find_all("a",class_=pager_element)
print(pager)
if pager and len(pager)>0 and pager[0].get('href'):
for sub_page in self.get_sub_pages_with_pager(
f"{host}{pager[0]['href']}",
sub_page_element,
pager_element,
details_element,
host
):
pages.append(sub_page)
return pages
def get_raa(self, keywords):
elements = []
page_content = self.get_page(self.__RAA_PAGE, 'get').content
soup = BeautifulSoup(page_content, 'html.parser')
#selection des grey cards
for a in soup.select('div.fr-card--grey div.fr-card__body div.fr-card__content h2.fr-card__title a'):
#regular
if Attrap.guess_date(f'{a.get_text().strip()}', '([0-9]{4}).*').year >= self.not_before.year and "Archives" not in f'{a.get_text().strip()}':
page_content = self.get_page(f"{self.__HOST}{a['href']}", 'get').content
for sub_page in self.get_sub_pages_with_pager(f"{self.__HOST}{a['href']}", 'div.fr-card__body div.fr-card__content h2.fr-card__title a', 'fr-pagination__link fr-pagination__link--next fr-pagination__link--lg-label', None, self.__HOST):
sub_page_content = self.get_page(sub_page['url'], 'get').content
for element in self.get_raa_elements(sub_page_content):
elements.append(element)
#archives
elif self.not_before.year<2020 and "Archives" in f'{a.get_text().strip()}':
page_content = self.get_page(f"{self.__HOST}{a['href']}", 'get').content
for sub_page in self.get_sub_pages(page_content,
'div.fr-card__body div.fr-card__content h2.fr-card__title a',
self.__HOST,
True):
sub_page_content = self.get_page(sub_page['url'], 'get').content
for element in self.get_raa_elements(sub_page_content):
elements.append(element)
#selection des "spécials"
for div in soup.select("div.fr-card.fr-card--horizontal.fr-card--sm.fr-enlarge-link.fr-mb-3w"):
for a in div.select("div.fr-card__body div.fr-card__content h2.fr-card__title a"):
print(a)
search_pattern=re.search('(?<=Publié le).*',f'{a.parent.parent.get_text()}')
if search_pattern:
if Attrap.guess_date(search_pattern[0], '([0-9]{4}).*').year>=self.not_before.year:
page_content = self.get_page(f"{self.__HOST}{a['href']}", 'get').content
for sub_page in self.get_sub_pages(page_content,
'div.fr-card__body div.fr-card__content h2.fr-card__title a',
self.__HOST,
True):
sub_page_content = self.get_page(sub_page['url'], 'get').content
for element in self.get_raa_elements(sub_page_content):
elements.append(element)
#bug sur ocrmypdf sur mon ubuntu 20.04 (test avec arch prochainement)
#sur --invalidate-digital-signatures bien que dans la doc
#ici https://ocrmypdf.readthedocs.io/en/latest/pdfsecurity.html
self.parse_raa(elements, keywords)
self.mailer()
def get_raa_elements(self, page_content):
elements = []
# On charge le parser
soup = BeautifulSoup(page_content, 'html.parser')
# Pour chaque balise a, on regarde si c'est un PDF, et si oui on le
# parse
print(soup.find_all("a",{"id":'class="fr-link'}))
print(len(soup.find_all("a",{"id":'class="fr-link'})))
for a in soup.find_all("a",{"id":'class="fr-link'}):
if a.get('href') and a['href'].endswith('.pdf'):
if a['href'].startswith('/'):
url = f"{self.__HOST}{a['href']}"
else:
url = a['href']
url = unquote(url)
name = a.find('span').previous_sibling.replace('Télécharger ', '').strip()
date = datetime.datetime.strptime(a.find('span').get_text().split(' - ')[-1].strip(), '%d/%m/%Y')
raa = Attrap.RAA(url, date, name)
self.download_file(raa)
elements.append(raa)
print(elements)
return elements
import os
import datetime
from bs4 import BeautifulSoup
from urllib.parse import unquote
from Attrap import Attrap
class Attrap_pref47(Attrap):
# Config
__HOST = 'https://www.lot-et-garonne.gouv.fr'
__RAA_PAGE = f'{__HOST}/Publications/Publications-legales/RAA'
__USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0'
full_name = 'Préfecture du Lot-et-garonne'
short_code = 'pref47'
def __init__(self, data_dir):
super().__init__(data_dir, self.__USER_AGENT)
self.enable_tor(10)
self.not_before = datetime.datetime(2024, 1, 1) # mettre 2020 et decommenter lignes 86 a 91 pour dl les années précédentes
#supercharge pour classes css avec whitespaces
def get_sub_pages_with_pager(self, page, sub_page_element, pager_element, details_element, host):
pages = []
page_content = self.get_page(page, 'get').content
# On initialise le parser
soup = BeautifulSoup(page_content, 'html.parser')
# On recherche les sous-pages
sub_pages = soup.select(sub_page_element)
sub_pages_details = None
if details_element is not None:
sub_pages_details = soup.select(details_element)
i = 0
for sub_page in sub_pages:
print(sub_page)
if sub_page.get('href'):
page = {
'url': f"{host}{sub_page['href']}",
'name': sub_page.get_text().strip(),
'details': ''
}
if details_element is not None:
page['details'] = sub_pages_details[i].get_text().strip()
pages.append(page)
i = i + 1
# On recherche un pager, et si on le trouve on le suit
# modif ici, le parametre pager_element ne doit contenir
# que le contenu de la classe
# et pas l'element pour les classes avec whitespaces
pager = soup.find_all("a",class_=pager_element)
print(pager)
if pager and len(pager)>0 and pager[0].get('href'):
for sub_page in self.get_sub_pages_with_pager(
f"{host}{pager[0]['href']}",
sub_page_element,
pager_element,
details_element,
host
):
pages.append(sub_page)
return pages
def get_raa(self, keywords):
elements = []
page_content = self.get_page(self.__RAA_PAGE, 'get').content
soup = BeautifulSoup(page_content, 'html.parser')
for a in soup.select('div.fr-card__body div.fr-card__content h2.fr-card__title a'):
# Annees sans pager
if Attrap.guess_date(f'{self.__HOST}{a.get_text().strip()}', '([0-9]{4}).*').year >= self.not_before.year:
page_content = self.get_page(f"{self.__HOST}{a['href']}", 'get').content
for sub_page in self.get_sub_pages(page_content,
'div.fr-card__body div.fr-card__content h2.fr-card__title a',
self.__HOST,
True):
sub_page_content = self.get_page(sub_page['url'], 'get').content
for element in self.get_raa_elements(sub_page_content):
elements.append(element)
#les raa de 2019 et années précédentes ont des pagers
#else:
# page_content = self.get_page(f"{self.__HOST}{a['href']}", 'get').content
# for sub_page in self.get_sub_pages_with_pager(f"{self.__HOST}{a['href']}", 'div.fr-card__body div.fr-card__content h2.fr-card__title a', 'fr-pagination__link fr-pagination__link--next fr-pagination__link--lg-label', None, self.__HOST):
# sub_page_content = self.get_page(sub_page['url'], 'get').content
# for element in self.get_raa_elements(sub_page_content):
# elements.append(element)
#bug sur ocrmypdf sur mon ubuntu 20.04 (test avec arch prochainement)
#sur --invalidate-digital-signatures bien que dans la doc
#ici https://ocrmypdf.readthedocs.io/en/latest/pdfsecurity.html
self.parse_raa(elements, keywords)
self.mailer()
def get_raa_elements(self, page_content):
elements = []
# On charge le parser
soup = BeautifulSoup(page_content, 'html.parser')
# Pour chaque balise a, on regarde si c'est un PDF, et si oui on le
# parse
print(soup.find_all("a",{"id":'class="fr-link'}))
print(len(soup.find_all("a",{"id":'class="fr-link'})))
for a in soup.find_all("a",{"id":'class="fr-link'}):
if a.get('href') and a['href'].endswith('.pdf'):
if a['href'].startswith('/'):
url = f"{self.__HOST}{a['href']}"
else:
url = a['href']
url = unquote(url)
name = a.find('span').previous_sibling.replace('Télécharger ', '').strip()
date = datetime.datetime.strptime(a.find('span').get_text().split(' - ')[-1].strip(), '%d/%m/%Y')
raa = Attrap.RAA(url, date, name)
self.download_file(raa)
elements.append(raa)
print(elements)
return elements
make: ppparis pref04 pref05 pref06 pref09 pref13 pref31 pref33 pref34 pref35 pref38 pref42 pref44 pref59 pref62 pref63 pref64 pref65 pref66 pref69 pref80 pref81 pref83 pref87 pref93 pref976
make: ppparis pref04 pref05 pref06 pref09 pref13 pref31 pref33 pref34 pref35 pref38 pref42 pref44 pref47 pref59 pref62 pref63 pref64 pref65 pref66 pref69 pref80 pref81 pref83 pref87 pref93 pref976
ppparis:
bin/python3 cli.py ppparis
pref04:
......@@ -9,6 +9,8 @@ pref06:
bin/python3 cli.py pref06
pref09:
bin/python3 cli.py pref09
pref12:
bin/python3 cli.py pref12
pref13:
bin/python3 cli.py pref13
pref31:
......@@ -21,10 +23,16 @@ pref35:
bin/python3 cli.py pref35
pref38:
bin/python3 cli.py pref38
pref40:
bin/python3 cli.py pref40
pref42:
bin/python3 cli.py pref42
pref44:
bin/python3 cli.py pref44
pref46:
bin/python3 cli.py pref46
pref47:
bin/python3 cli.py pref47
pref59:
bin/python3 cli.py pref59
pref62:
......
......@@ -18,6 +18,14 @@ pip3 install -r requirements.txt
Vous devez avoir installé OCRmyPDF, les données `eng` et `fra` de Tesseract, et le démon Tor.
### Configuration du démon Tor
Il faut que le port de contrôle 9051 du démon Tor soit ouvert pour permettre à Attrap de s'y connecter. Vous pouvez exécuter la ligne suivante pour faire cela :
```bash
sed -i '/^#ControlPort 9051/s/^#//' /etc/tor/torrc
```
## Utilisation
Pour lancer la récupération de toutes les administrations supportées :
......@@ -77,14 +85,28 @@ Les options suivantes peuvent être précisées, par un paramètre si l'utilitai
- Préfecture du Tarn (identifiant : `pref81`)
- Préfecture du Var (identifiant : `pref83`)
- Préfecture de la Haute-Vienne (identifiant : `pref87`)
- Préfecture de Seine-Saint-Denis(identifiant : `pref93`)
- Préfecture de Seine-Saint-Denis (identifiant : `pref93`)
- Préfecture de Mayotte (identifiant : `pref976`)
## Contributions
Les contributions à ce projet sont les bienvenues !
Les contributions à ce projet sont les bienvenues !
Chaque administration est gérée par un fichier dont le nom correspond à son identifiant (`Attrap_XXX.py`). Commencez par copier un de ces fichiers puis adaptez son code à l'administration que vous voulez ajouter. Il est impératif de lancer le moins de requêtes possibles vers le site de l'administration : lorsqu'une administration a une page par année ou par mois, ne lancez une requête que vers les pages qui correspondent à la plage temporelle demandée dans la valeur de configuration `NOT_BEFORE`.
Vous pouvez lancer la commande suivante pour connaître fonctions disponibles pour récupérer les RAA sur le site d'une administration :
```
bin/python -m pydoc Attrap
```
Avant d'ouvrir une merge request, assurez-vous que :
- l'administration est activée dans `cli.py` et dans `Makefile` ;
- il existe un job dans la CI (`.gitlab-ci.yml`) pour l'administration ;
- le fichier de README indique que la nouvelle administration est supportée ;
- vous n'avez qu'un seul commit par nouvelle préfecture (sinon, il faut faire un squash), de la forme `identifiant_de_l'administration: ajout de nom_complet_de_l'administration`.
Vous pouvez rejoindre le salon de discussion à ce sujet sur Matrix : `#Attrap:laquadrature.net`
Vous pouvez rejoindre le salon de discussion Matrix du projet : `#Attrap:laquadrature.net`.
## Licence
......
#!/usr/bin/env python3
import os
import argparse
import logging
......@@ -42,14 +44,18 @@ available_administrations = [
'pref05',
'pref06',
'pref09',
'pref12',
'pref13',
'pref31',
'pref33',
'pref34',
'pref35',
'pref38',
'pref40',
'pref42',
'pref44',
'pref46',
'pref47',
'pref59',
'pref62',
'pref63',
......
......@@ -2,6 +2,7 @@ beautifulsoup4>=4.12.3
dateparser>=1.2.0
ftfy>=6.2.0
Mastodon.py>=1.8.1
ocrmypdf>=16.3.1
pycodestyle>=2.11.1
pypdf>=4.2.0
PyVirtualDisplay>=3.0
......