On est plusieurs à avoir constaté une augmentation du spam sur Zeste de Savoir ces dernières années. J’ai eu envie d’estimer l’ampleur de ce fléau donc j’ai fait quelques statistiques !
- Analyse des inscriptions
- Analyse des inscriptions via les réseaux sociaux
- Analyse des bannissements permanents
- Analyse des signalements
- Analyse des signalements émis par l'antispam des biographies
Analyse des inscriptions
Comptes créés… | depuis la création du site | en 2014 | en 2015 | en 2016 | en 2017 | en 2018 | en 2019 | en 2020 | en 2021 | en 2022 | en 2023 | en 2024 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Comptes utilisateurs | 30328 (100 %) | 1526 (100 %) | 2465 (100 %) | 2629 (100 %) | 2736 (100 %) | 2733 (100 %) | 2891 (100 %) | 3400 (100 %) | 3149 (100 %) | 3012 (100 %) | 3028 (100 %) | 2739 (100 %) |
Comptes utilisateurs activés | 27017 (89 %) | 1430 (94 %) | 2243 (91 %) | 2373 (90 %) | 2480 (91 %) | 2306 (84 %) | 2535 (88 %) | 3030 (89 %) | 2782 (88 %) | 2700 (90 %) | 2708 (89 %) | 2411 (88 %) |
Comptes utilisateurs activés et bannis | 3726 (12 %) | 8 (1 %) | 75 (3 %) | 52 (2 %) | 49 (2 %) | 294 (11 %) | 308 (11 %) | 458 (13 %) | 453 (14 %) | 533 (18 %) | 802 (26 %) | 688 (25 %) |
Les inscriptions sont relativement stables depuis 2015 avec une moyenne 2900 comptes créés chaque année. Environ 10% de ces comptes sont inactifs, c’est-à-dire que l’adresse de courriel n’a pas été validée par l’utilisateur en cliquant sur le lien reçu par courriel. Je n’imaginais pas que ce nombre soit aussi élevé ! Enfin, on observe depuis 2018 une croissance des comptes bannis de façon permanente. Ils représentent un quart des inscriptions ces deux dernières années !
Analyse des inscriptions via les réseaux sociaux
Comptes créés… | depuis la création du site | en 2014 | en 2015 | en 2016 | en 2017 | en 2018 | en 2019 | en 2020 | en 2021 | en 2022 | en 2023 | en 2024 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Utilisateurs inscrits | 27017 | 1430 | 2243 | 2373 | 2480 | 2306 | 2535 | 3030 | 2782 | 2700 | 2708 | 2411 |
Utilisateurs inscrits via les réseaux sociaux | 11162 (100 %) | 31 (100 %) | 518 (100 %) | 898 (100 %) | 1221 (100 %) | 1001 (100 %) | 802 (100 %) | 1270 (100 %) | 1338 (100 %) | 1345 (100 %) | 1449 (100 %) | 1278 (100 %) |
Utilisateurs inscrits via les réseaux sociaux et bannis | 1224 (11 %) | 0 (0 %) | 9 (2 %) | 14 (2 %) | 9 (1 %) | 95 (9 %) | 50 (6 %) | 100 (8 %) | 150 (11 %) | 184 (14 %) | 320 (22 %) | 291 (23 %) |
Utilisateurs inscrits via Google | 8706 (78 %) | 26 (84 %) | 502 (97 %) | 616 (69 %) | 684 (56 %) | 640 (64 %) | 534 (67 %) | 884 (70 %) | 1112 (83 %) | 1175 (87 %) | 1328 (92 %) | 1194 (93 %) |
Utilisateurs inscrits via Facebook | 2446 (22 %) | 5 (16 %) | 6 (1 %) | 282 (31 %) | 537 (44 %) | 361 (36 %) | 268 (33 %) | 386 (30 %) | 226 (17 %) | 170 (13 %) | 121 (8 %) | 84 (7 %) |
Utilisateurs inscrits via Twitter1 | 10 (0 %) | 0 (0 %) | 10 (2 %) | 0 (0 %) | 0 (0 %) | 0 (0 %) | 0 (0 %) | 0 (0 %) | 0 (0 %) | 0 (0 %) | 0 (0 %) | 0 (0 %) |
Environ 40 % des comptes créés sur le site l’ont été via les réseaux sociaux et la moitié des inscriptions se font via les réseaux sociaux ces dernières années. 80 % de ces comptes proviennent de Google et 20 % de Facebook.
On retrouve dans ce tableau les même proportions de comptes bannis de façon permanente que dans le tableau précédent : 10 % des comptes depuis la création du site et environ 25 % ces deux dernières années.
- La possibilité de s’inscrire via Twitter a été enlevée en février 2015.↩
Analyse des bannissements permanents
Bannissements de comptes créés… | depuis la création du site | en 2014 | en 2015 | en 2016 | en 2017 | en 2018 | en 2019 | en 2020 | en 2021 | en 2022 | en 2023 | en 2024 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Bannissements permanents | 37782 (100 %) | 7 (100 %) | 86 (100 %) | 52 (100 %) | 49 (100 %) | 301 (100 %) | 310 (100 %) | 466 (100 %) | 459 (100 %) | 536 (100 %) | 808 (100 %) | 698 (100 %) |
Bannissements permanents pour spam1 | 3341 (88 %) | 5 (71 %) | 29 (34 %) | 29 (56 %) | 34 (69 %) | 208 (69 %) | 257 (83 %) | 403 (86 %) | 420 (92 %) | 497 (93 %) | 776 (96 %) | 677 (97 %) |
Bannissements permanents pour spam1 (moins de 7 jours après l’inscription) | 2855 (76 %) | 3 (43 %) | 28 (33 %) | 21 (40 %) | 23 (47 %) | 100 (33 %) | 213 (69 %) | 371 (80 %) | 385 (84 %) | 450 (84 %) | 667 (83 %) | 588 (84 %) |
Bannissements permanents pour spam1 (moins de 1 jour après l’inscription) | 2624 (69 %) | 2 (29 %) | 23 (27 %) | 21 (40 %) | 21 (43 %) | 75 (25 %) | 159 (51 %) | 336 (72 %) | 367 (80 %) | 423 (79 %) | 631 (78 %) | 560 (80 %) |
Bannissements permanents pour spam1 (moins de 1 heure après l’inscription) | 1371 (36 %) | 2 (29 %) | 18 (21 %) | 17 (33 %) | 11 (22 %) | 46 (15 %) | 65 (21 %) | 178 (38 %) | 191 (42 %) | 221 (41 %) | 310 (38 %) | 309 (44 %) |
Ces deux dernières années, plus de 95 % des bannissements le sont pour cause de spam (publicité, arnaque…) avec une très bonne réactivité de la part de l’équipe du site car 80 % de ces bannissements ont lieu moins d’un jour après l’inscription et 45 % moins d’une heure après celle-ci !
- Je n’ai gardé ici que les bannissements dont l’explication donnée par le modérateur contient le mot 'spam’.↩
- Il y a bien 3778 bannissements permanents mais seulement 3726 comptes utilisateurs activés et bannis (cf. le premier tableau du billet). Un bannissement correspond ici à l’action de bannir et non pas à l’état actuel d’un compte utilisateur. Cette différence numéraire s’explique donc par la possibilité d’enlever un bannissement (puis éventuellement de le remettre… puis de l’enlever à nouveau…).↩
Analyse des signalements
Signalements émis… | depuis la création du site | en 2016 | en 2017 | en 2018 | en 2019 | en 2020 | en 2021 | en 2022 | en 2023 | en 2024 |
---|---|---|---|---|---|---|---|---|---|---|
Signalements | 6017 (100 %) | 58 (100 %) | 553 (100 %) | 529 (100 %) | 557 (100 %) | 782 (100 %) | 614 (100 %) | 914 (100 %) | 1068 (100 %) | 937 (100 %) |
Signalements de profils | 1242 (21 %) | 0 (0 %) | 0 (0 %) | 0 (0 %) | 50 (9 %) | 91 (12 %) | 70 (11 %) | 355 (39 %) | 323 (30 %) | 351 (37 %) |
Signalements de messages sur le forum | 3901 (65 %) | 57 (98 %) | 473 (86 %) | 441 (83 %) | 437 (78 %) | 623 (80 %) | 474 (77 %) | 459 (50 %) | 508 (48 %) | 426 (45 %) |
Signalements de contenus | 122 (2 %) | 0 (0 %) | 34 (6 %) | 25 (5 %) | 20 (4 %) | 6 (1 %) | 19 (3 %) | 7 (1 %) | 3 (0 %) | 8 (1 %) |
Signalements de commentaires | 752 (12 %) | 1 (2 %) | 46 (8 %) | 63 (12 %) | 50 (9 %) | 62 (8 %) | 51 (8 %) | 93 (10 %) | 234 (22 %) | 152 (16 %) |
Plus de la moitié des signalements émis concernent des messages du forum et leur évolution est stable avec environ 450 signalements chaque année depuis 2015. Les signalements de profils et de commentaires ont eux nettement augmentés ces trois dernières années.
Analyse des signalements émis par l'antispam des biographies
Signalements émis… | depuis la création du site | en 2019 | en 2020 | en 2021 | en 2022 | en 2023 | en 2024 |
---|---|---|---|---|---|---|---|
Signalements de profils | 1242 (100 %) | 50 (100 %) | 91 (100 %) | 70 (100 %) | 355 (100 %) | 323 (100 %) | 351 (100 %) |
Signalements de profils par l’antispam des biographies | 783 (63 %) | 0 (0 %) | 0 (0 %) | 0 (0 %) | 252 (71 %) | 261 (81 %) | 268 (76 %) |
Signalements de profils par l’antispam des biographies suivi d’un bannissement | 775 (62 %) | 0 (0 %) | 0 (0 %) | 0 (0 %) | 247 (70 %) | 260 (80 %) | 266 (76 %) |
Depuis juin 2020, l’antispam des biographies essaie de détecter le spam dans les biographies des comptes créés récemment et notifie les modérateurs lorsqu’une biographie lui semble suspecte. Au début, la notification se faisait par message privé à un groupe de modérateurs, mais depuis janvier 2022 le script utilise la fonction "Signaler" des profils. S’il est difficile d’estimer le nombre de faux négatifs, il n’y a en tous cas que très peu de faux positifs !
Code source du script Python qui a extrait ces statistiques de la base de données
from datetime import timedelta
from django.conf import settings
from django.contrib.auth.models import User, Group
from django.db.models import ExpressionWrapper, F, DurationField
from social_django.models import UserSocialAuth
from zds.member.models import Profile, Ban
from zds.utils.models import Alert
groupe_des_robots = Group.objects.get(name=settings.ZDS_APP["member"]["bot_group"])
antispam_user = User.objects.get(username="Antispam des biographies")
def print_to_file(text):
with open("/opt/zds/table_output.md", "a") as f:
f.write(text)
def print_table(base, abscisses, ordonnées, coin=" ", titre=None):
table = "\n" + " | ".join([coin] + list(abscisses.keys())) + "\n" + "|".join(["---"]*(len(abscisses)+1)) + "\n"
for index_y, (titre_y, filtre_y) in enumerate(ordonnées.items()):
table += titre_y
if index_y == 0: # première ligne
valeurs_max = []
for index_x, filtre_x in enumerate(abscisses.values()):
valeur = filtre_y(filtre_x(base)).count()
if index_y == 0: # première ligne
valeurs_max += [valeur]
try:
pourcentage = round(valeur / valeurs_max[index_x] * 100)
except ZeroDivisionError:
pourcentage = 0
table += f" | {valeur} ({pourcentage} %)"
table += "\n"
if titre:
table += f"Table: {titre}\n"
table += "\n\n"
print_to_file(table)
### Analyse des comptes utilisateurs
base = Profile.objects.exclude(user__groups__in=[groupe_des_robots])
abscisses = {
"depuis la création du site": lambda x: x
} | {
f"en {année}": lambda x, année=année: x.filter(user__date_joined__year=année) for année in range(2014, 2025)
}
ordonnées = {
"Comptes utilisateurs": lambda x: x,
"Comptes utilisateurs activés[^utilisateurs-activés]": lambda x: x.filter(user__is_active=True),
"Comptes utilisateurs activés[^utilisateurs-activés] et bannis": lambda x: x.filter(user__is_active=True).filter(can_read=False).filter(end_ban_read=None),
}
coin = "Comptes créés..."
titre = "Analyse des comptes utilisateurs"
print_table(base, abscisses, ordonnées, coin, titre)
print_to_file("\n[^utilisateurs-activés]: Les comptes utilisateurs ne sont activés qu'une fois que l'adresse de courriel a été vérifiée (donc que l'utilisateur a cliqué sur un lien reçu par courriel).\n")
## Utilisateurs inscrits via les réseaux sociaux
base = UserSocialAuth.objects.filter(user__is_active=True)
abscisses = {
"depuis la création du site": lambda x: x
} | {
f"en {année}": lambda x, année=année: x.filter(user__date_joined__year=année) for année in range(2014, 2025)
}
ordonnées = {
"Utilisateurs inscrits via les réseaux sociaux": lambda x: x,
"Utilisateurs inscrits via les réseaux sociaux et bannis": lambda x: x.filter(user__profile__can_read=False).filter(user__profile__end_ban_read=None),
"Utilisateurs inscrits via Google": lambda x: x.filter(provider="google-oauth2"),
"Utilisateurs inscrits via Facebook": lambda x: x.filter(provider="facebook"),
"Utilisateurs inscrits via Twitter[^twitter]": lambda x: x.filter(provider="twitter"),
}
coin = "Comptes créés..."
titre = "Analyse des utilisateurs inscrits via les réseaux sociaux"
print_table(base, abscisses, ordonnées, coin, titre)
print_to_file("\n[^twitter]: La possibilité de s'inscrire via Twitter a été enlevée en février 2015.\n")
### Analyse des bannissements permanents
base = Ban.objects.annotate(depuis_inscription=ExpressionWrapper(F("pubdate") - F("user__date_joined"), output_field=DurationField())).filter(type__in=("Bannissement illimité", "Ban définitif"))
abscisses = {
"depuis la création du site": lambda x: x
} | {
f"en {année}": lambda x, année=année: x.filter(user__date_joined__year=année) for année in range(2014, 2025)
}
ordonnées = {
"Bannissements permanents": lambda x: x,
"Bannissements permanents pour spam[^spam]": lambda x: x.filter(note__icontains="spam"),
"Bannissements permanents pour spam[^spam] (moins de 7 jours après l'inscription)": lambda x: x.filter(note__icontains="spam").filter(depuis_inscription__lte=timedelta(days=7)),
"Bannissements permanents pour spam[^spam] (moins de 1 jour après l'inscription)": lambda x: x.filter(note__icontains="spam").filter(depuis_inscription__lte=timedelta(days=1)),
"Bannissements permanents pour spam[^spam] (moins de 1 heure après l'inscription)": lambda x: x.filter(note__icontains="spam").filter(depuis_inscription__lte=timedelta(hours=1)),
}
coin = "Bannissements de comptes créés..."
titre = "Analyse des bannissements permanents"
print_table(base, abscisses, ordonnées, coin, titre)
print_to_file("\n[^spam]: Je n'ai gardé ici que les bannissements dont l'explication donnée par le modérateur contient le mot 'spam'.\n")
### Analyse des signalements
base = Alert.objects
abscisses = {
"depuis la création du site": lambda x: x
} | {
f"en {année}": lambda x, année=année: x.filter(pubdate__year=année) for année in range(2016, 2025)
}
ordonnées = {
"Signalements": lambda x: x,
"Signalements de profils": lambda x: x.filter(scope="PROFILE"),
"Signalements de messages sur le forum": lambda x: x.filter(scope="FORUM"),
"Signalements de contenus": lambda x: x.filter(scope="CONTENT"),
"Signalements de commentaires": lambda x: x.filter(scope__in=("TUTORIAL", "ARTICLE", "OPINION")),
}
coin = "Signalements émis..."
titre = "Analyse des signalements"
print_table(base, abscisses, ordonnées, coin, titre)
## Signalements par l'antispam des biographies depuis sa mise en place en juin 2020
base = Alert.objects.filter(scope="PROFILE")
abscisses = {
"depuis la création du site": lambda x: x
} | {
f"en {année}": lambda x, année=année: x.filter(pubdate__year=année) for année in range(2019, 2025)
}
ordonnées = {
"Signalements de profils": lambda x: x,
"Signalements de profils par l'antispam des biographies": lambda x: x.filter(author=antispam_user),
"Signalements de profils par l'antispam des biographies suivi d'un bannissement": lambda x: x.filter(author=antispam_user).filter(profile__can_read=False).filter(profile__end_ban_read=None),
}
coin = "Signalements émis..."
titre = "Analyse des signalements émis par l'antispam des biographiques"
print_table(base, abscisses, ordonnées, coin, titre)
print_to_file("\n[[information]]\n| L'antispam des biographies a été mis en place en juin 2020 mais le signalement des biographies était effectué par message privé à un groupe de modérateurs. Ce n'est que depuis janvier 2022 que l'antispam utilise la fonction 'Signaler' des profils.")