Hacking de l’horoscope. C’est quoi ce délire ?

hacking horoscope meme

On aurait pas évolué, un peu ?

 

Toi aussi, Paul-Henry, ça te rend dubitatif les astres, le marc de café et les entrailles de nourrisson comme indicateur de ton futur ? Bienvenue au club.

 

Seulement, voilà.

 

Tu as dû, toi aussi, remarquer l’omniprésence de ce truc, gangrène quotidienne de nos journaux. Ce que tu considères comme un ramassis de débilités pondues par un stagiaire pigiste de 12 ans pour lui donner une tribune dans un torchon. L’horoscope. Putain les mecs, c’est quoi ce délire à encore publier ça ? Du folklore ?

 

Oui, on doit en être encore là.

Mais au delà de toutes ces critiques gratuites et cependant fondées, qui est-ce qui a dégoté comme boulot d’écrire ces conneries tous les matins, devant un bol de café Grand-Mère tiède ?

Dans le mile, Paul-Henry, un programme.

Pas convaincu ? Suis-moi.

 

Récupérer l’horoscope, hacking power

 

Déjà, trouve une cible.

Pas de prise de tête, www.horoscope.fr, ça à l’air de parler du sujet sans détour vu l’adresse du site. Et au final, ce qu’il nous faut, c’est beaucoup d’horoscopes à étudier, sans devoir recopier le 20minutes chaque matin pendant 10 ans.

Tu remarque que sur notre cher site cible, on peut entrer une date et avoir un horoscope de naissance. Bingo, ils doivent avoir une base de donnée contenant tout plein d’horoscopes.

 

Méthode GET, méthode POST

 

Concentre toi un peu Paul-Henry. Tu as déja remarqué que des fois, quand tu tapes quelque chose dans une barre de recherche d’un site, l’URL du site changeait, et se mettait à contenir la requête que tu fais ?

Voilà, comme quand tu recherche sur Google.

C’est ce qu’on apelle la méthode GET. Quand une requête est envoyée au serveur via l’URL. Simple, efficace, mais pas sécurisé.

Quand tu entre ta date de naissance sur ce foutu horoscope.fr, l’URL ne change pas. Pourtant le serveur a bien dû recevoir les infos concernant ton super jour fétiche pour afficher l’horoscope correspondant, NON ?

C’est la méthode POST, qui envoie de manière invisible les data au serveur. C’est sensé être plus sécurisé.

M’ouais.

 

Construction et envoi d’une méthode POST

 

import re
import json
import random
import socket
import urllib
import requests
from collections import Counter
import matplotlib.pyplot as plt

''' Parameters '''
parameters = {
    'i': 0,
    'p': 0,
    # Number of random horoscopes you want to download to make stats on
    'horoscopeNumber': 20000,
    # Print an array of sentence utilized more than 'sentenceUtilization' times
    'arrayCSV': True,
    # Number of times the sequence is used among every horoscopes to save it into the file
    'sentenceUtilization': 1,
    # Print a pieplot about sentence utilization is set on 'True'
    'piePlot': True,
    # Random dates to get horoscope or not (start from 01-01-1950) until it reach 'horoscopeNumber')
    'randomDate': False
}

 

Déjà, importe les libraries dont tu auras besoin et construis un dictionnaire de paramètres. Classique, tu vas commencer à aimer ça.

 

''' Build session & co ''' 
post_URL = 'https://www.horoscope.fr/horoscope_custom/'
# Session
session = requests.Session()
# header
session.headers.update({'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36'})
tokenRequest = session.get(post_URL)
# Keep session Cookie
sessionCookies = tokenRequest.cookies
# Header
headers = {
    'Host': 'www.horoscope.fr',
    'Accept': '*/*',
    'Accept-Language': 'en,en-US;q=0.5',
    'Accept-Encoding': 'gzip, deflate, br',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'X-Requested-With': 'XMLHttpRequest',
    'Referer': 'https://www.horoscope.fr/horoscope/perso',
    'Content-Length': '166',
    'DNT': '1',
    'Connection': 'keep-alive'
}

 

Tu as déja entendu parler des cookies ? Ces petits enfoirés qui savent ce que tu recherches sur Amazon, ou qui te permettent de rester connecté à Facebook entre deux fermetures de ton navigateur.

Et bien, une requête POST à besoin d’un cookie.

Va sur la page des horoscopes > clic droit > inspecter. Dans la console qui s’ouvre, sélectionne l’onglet « réseau« .

Puis, entre ta date de naissance sur le site et envoie la requête.

La liste des trucs qui s’affichent dans la console, c’est ce que lon navigateur envoie au serveur pour récupérer des infos (images, texte .. le contenu de la page à afficher, quoi).

Tu remarques que beaucoup de requêtes sont en GET, mais approches toi un peu .. Oui, il y a bien une POST.

Ça doit être notre horoscope.

Fais un clic droit dessus et copie l’URL. C’est l’adresse post_URL de destination de notre requête future.

 

Revenons à notre programme. Tu vas donc ouvrir une session de connection avec la librairie requests, puis faire une requête GET vide à notre URL copiée précédemment.

Tu lui passe un header au pif, il en existe plein sur internet, c’est la « signature » de ton navigateur et de ton PC, recherche « browser header ». Puis envoie la requete GET à la fameuse adresse.

On récupère le cookie de cette GET, vu qu’il ne changera pas au sein d’une même session. C’est ça qui nous permet de le réutiliser.

 

Pour compléter le header de la session, tu retournes sur la console d’inspection, re clic droit sur la requête POST et « copier l’en-tête de la requête« . Tu vires la ligne qui parle de cookie dedans (on a le notre, perso, au chaud) et HOP. La requête est prête a être balancée.

 

''' Get horoscope '''
def getHoroscope(year, month, day, date, horoscopeList, textList):
    # Data to send
    payload = {
        'birth_day':str(day),
        'birth_month':str(month),
        'birth_year':str(year),
        'birthdate':date,
        'birth_hour':'',
        'birth_min':'',
        'birthtime':'',
        'unknown_birthtime':'1',
        'birth_city_id':'2988507',
        'birth_city':'Paris+(France)'
    }
    # Encode it
    raw = urllib.parse.urlencode(payload)
    # POST requests
    r = session.post(post_URL, data=raw, cookies=sessionCookies, headers=headers)
    # Dictionary from results	
    dailyHoroscope = {}
    dailyHoroscope['BAD_GOOD'] = r.json()['response']['result'][0]['text']['bien']
    dailyHoroscope['BAD_BAD'] = r.json()['response']['result'][0]['text']['pasbien']
    dailyHoroscope['GOOD_GOOD'] = r.json()['response']['result'][1]['text']['bien']
    dailyHoroscope['GOOD_BAD'] = r.json()['response']['result'][1]['text']['pasbien']
    # Store every parts of the horoscope in a big list
    textList.append(dailyHoroscope['BAD_GOOD'])
    textList.append(dailyHoroscope['BAD_BAD'])
    textList.append(dailyHoroscope['GOOD_GOOD'])
    textList.append(dailyHoroscope['GOOD_BAD'])
    # Second dictionary with date as ID
    results = {}
    results[date] = dailyHoroscope
    horoscopeList.append(results)
    # Update the big list

 

Tu définis ensuite une fonction a qui on fournit une année, un mois, un jour et qui va créer un dictionnaire avec ça. Tu vois la requête POST avec session.post. Puis tu extrais les données du json de réponse du site, comme lors du data mining sur data.gouv.fr.

La liste textList va contenir tous les horoscopes.

 

''' Parse years '''
def parseYear(parsedYear):
    for year in range(1950, 2017):
            for month in range(1, 12):
                for day in range(1, 29):
                    date = str(year) + '-' + str(month) + '-' + str(day)
                    # If not in 'parsedYear' list
                    if date not in parsedYear:
                        parsedYear.append(date)
                        return year, month, day, date

 

Cette petite fonctionation va te renvoyer les dates, une par une, depuis 1950.

 

''' Execution '''
# Build a list of date to avoir redonduncy, a JSON list with date and a big oune containing all of the text.
passedDates = []
horoscopeList = []
textList = []
# Random date generation until it reachs parameters['horoscopeNumber']
if parameters['randomDate'] is True :
    while parameters['i'] < parameters['horoscopeNumber']:
        day = random.randint(1, 29)
        month = random.randint(1, 12)
        year = random.randint(1950, 2017)
        date = str(year) + '-' + str(month) + '-' + str(day)
        # If not into list
        if date not in passedDates:
            passedDates.append(date)
            try:
                getHoroscope(year, month, day, date, horoscopeList, textList)
            except:
                continue
        parameters['i'] += 1
        print('{} %\t{}'.format((parameters['i']*100)/parameters['horoscopeNumber'], date))
# From 1950/1/1 until it reachs parameters['horoscopeNumber']			
else:
    parsedYear = []
    while parameters['i'] < parameters['horoscopeNumber']:
        timeDate = parseYear(parsedYear)
        year = timeDate[0]
        month = timeDate[1]
        day = timeDate[2]
        date = timeDate[3]
        # If not into list
        if date not in passedDates:
            passedDates.append(date)
            try:
                getHoroscope(year, month, day, date, horoscopeList, textList)
            except:
                continue
        parameters['i'] += 1
        print('{} %\t{}'.format((parameters['i']*100)/parameters['horoscopeNumber'], date))
    
# End of requests Session
session.close()
# Let's join the list containing all of the text
joinedTextList = ''.join(textList)
splittedSentence = []
# And split it on the '.' to get list of sentences used.
for sentence in joinedTextList.split('. '):
    if sentence != '':
        splittedSentence.append(sentence)
# Count how many times a sentence have been used
counterDict =  Counter(splittedSentence)

 

Tu exécutes ensuite, en fonction de si tu veux des dates aléatoires ou qui se suivent (défini dans les paramètres).

Le code est assez commenté, tu devrais comprendre. Tout repose sur la fonction getHoroscope, de toute façon.

 

''' Results CSV '''
if parameters['arrayCSV'] is True:
    horoscope = open('horoscope.csv', 'w')
    for sentence, count in counterDict.items():
        # Print if more than 1
        if count > parameters['sentenceUtilization']:
            horoscope.write(str(count) + '\t' + sentence + '\n')
    horoscope.close()

''' Results plot '''	
if parameters['piePlot'] is True:	
    counts = []
    # Count how many sentence have been used 1 time, 2 times, 3 times ...
    for count in counterDict.values():
        counts.append(count)
    counOfCounts = Counter(counts)
    # Extract label and counts to plot it
    labels = []
    sizes = []
    for label, count in counOfCounts.items():
        labels.append(label)
        sizes.append(count)
    # Plot results
    plt.pie(sizes, labels=labels, shadow=True, startangle=90, labeldistance=1.2)
    plt.axis('equal')
    plt.show()

 

Et enfin, tu sors un graphe et/ou un tableau récapitulatif de tout ça.

 

Et alors, l’horoscope, ça donne quoi ?

 

Ça donne que tu avais raison Paul-Henry.

Voilà un pie-plot de la fréquence d’utilisation des phrases.

Tu vois donc qu’environ la moitié des phrases sont utilisées plus d’une fois dans 10000 horoscopes au hasard entre janvier 1950 et janvier 2017.

On parle ici de phrases exactes à la virgule près, pas de ressemblance vague.

En statistiques, 50%, ça ne peut pas s’apeller du hasard.

En cadeau, le tableau CSV des résultats. Comme ça, tu peux voir par toi-même quelle phrase est la chouchoute des voyants. Ou plutôt qui a eu une probabilité de sortir du programme plus haute que les autres. Probabilité qui fait tout de même monter le nombre d’utilisation de la phrase « Bien que celles-ci soient de courte durée, vous les vivez comme un fardeau alors qu’elles vont vous permettre de voir plus clair dans votre vie ensuite » à .. 1298 fois.

 

Allez, j’ai un chat noir à aller adopter.

 

 

PS: Le code, sur Github.

 

 

 

 

1 commentaire sur “Hacking de l’horoscope. C’est quoi ce délire ?

Laisser un commentaire