Added a lot to the main doc
This commit is contained in:
+244
-1
@@ -338,6 +338,120 @@ J'ai mis une durée arbitraire de deux jours, mais je ne sais pas vraiment combi
|
||||
|
||||
----
|
||||
|
||||
### Technologies utilisées
|
||||
|
||||
Dans ce projet différents choix ont été faits pour ce qui est des technologies.
|
||||
|
||||
Certaines ont été choisies car elles étaient les plus simples, les plus pratiques, les plus efficaces ou encore les plus connues et donc ayant le meilleur support. Je vais tenter de résumer ici ces choix mais je reviendrai sur la plupart d'entre eux plus tard quand j'explique ce que je fais avec.
|
||||
|
||||
#### Selenium
|
||||
|
||||

|
||||
|
||||
Selenium est une librairie à la base Node JS qui permet d'automatiser des actions sur un navigateur internet. Le but premier et je pense son utilisation première est l'automatisation de tests pour des applications WEB. En effet c'est un super outil pour simuler un user faisant un certain nombre d'actions sans apporter de variabilité ce qui fait de supers test unitaires.
|
||||
|
||||
Cependant je pense que l'autre grande partie des utilisateurs de Selenium l'utilisent pour faire du "Scrapping". Et nous sommes un peu dans cette seconde catégorie. Le "Scrapping" c'est l'acte d'aller récupèrer des informations sur des pages web automatiquement pour alimenter sa propre base de données. En effet, si on arrive à passer les protections anti-bot on peut facilemnt utiliser Selenium pour scraper tous les sites qui nous passent par la tête.
|
||||
|
||||
Le cahier des charges que j'avais en tête en cherchant une technologie de contrôle de navigateur internet était le suivant :
|
||||
|
||||
- Simple
|
||||
- Permettant de contrôler un navigateur Headless (Voir chapitre "Simuler un navigateur ?")
|
||||
- Permettant de contrôler firefox
|
||||
- Ayant un wrapper C#
|
||||
- Permettre de changer certaines choses comme les cookies en Live
|
||||
- Permettre d'interagir avec les éléments d'une page
|
||||
- Fonctionner
|
||||
|
||||
Simple car je ne voulais pas avoir à passer trop de temps dessus (ca n'a pas bien vieillit lol...). Je voulais que l'on puisse utiliser Firefox car il n'implémente pas les mêmes sécuritées que Chrome pour faire simple. J'avais besoin que la lib puisse contrôler un Firefox HEADLESS car je ne voulais pas avoir une page web ouvert sur mon ordi quand je commente car c'est de l'espace utilisé pour rien. J'avais besoin d'un wrapper C# car c'est le language que j'utilise. Pour finir j'avais besoin d'interragir avec les éléments de la page pour naviguer dessus et d'insèrer des cookies pour me connecter sans avoir à passer par le login de la F1TV qui est très bon pour detecter les bots.
|
||||
|
||||
Avec un cahier des charges parreil beaucoup de librairies ont été abandonnées. J'ai pu tester pleins de librairies C# qui arrivaient à contrôler un Chrome et même pas mal qui arrivaient à contrôler un Chrome Headless. Mais le choix est très vite restreind quand on veut pouvoir contrôler Chrome OU Firefox.
|
||||
|
||||
A la base mon choix c'était porté sur Puppeteer Sharp qui est une librairie qui se veut être exactement ce que je veux.
|
||||
|
||||

|
||||
|
||||
Je voulais utiliser cette librairie car il y a des plugins qui sont très orientés scrapping, en effet, ils implémentent de nombreuses techniques pour permettre de mieux passer innapercu par les systèmes de detection de bots.
|
||||
|
||||
Sur le papier c'est la librairie parfaite qui correspond parfaitement au cahier des charges que je m'étais fixé et je pense que si j'utilisais un projet JS elle le serait. Sauf que avec le wrapper C# j'ai eu un certain nombre de problèmes:
|
||||
|
||||
- Toutes les versions de la librairie ne fonctionnaient pas. Il fallait faire des tests avec différentes versions de la librairie et de ses dépendences simplement pour faire démarrer un browser. Et ca c'est quand ca marchait car il y avait des jours ou des machines ou je n'ai simplement pas pu faire fonctionner la librairie.
|
||||
- Même avec les techniques proposées par les plugins "Stealth" je n'arrivais pas à bypass les sécuritées de la page de login de la F1TV. J'ai essayé tout ce que j'ai pu trouver sur internet mais on se fait toujours chopper dès que l'on arrive sur la page.
|
||||
- Et le pire de tout, impossible de faire fonctionner un vidéo. J'ai pu faire tout ce que je voulais faire au final en passant par l'utillisation de cookies pour la connexion. Tout ca pour arriver au moment ou il faut lancer la vidéo, et la, crash. Impossible de faire fonctionner Puppeteer Sharp avec une vidéo. Dès qu'elle se lance c'est un crash assuré sans message d'erreur clair. Et le soucis c'est que le wrapper C# n'est pas vraiment bien supporté et que si c'est un bug de la lib je ne risque pas de voir de fix avant un moment si ce n'est jamais.
|
||||
|
||||
Pour toutes ces raisons, j'ai du abandonner cette librairie ce qui a été très dur car j'avais passé beaucoup de temps dessus à essayer de la faire marcher.
|
||||
|
||||
Ensuite le choix de Selenium était plutôt simple, c'était la seule option restante. A ce jour je ne connait aucune autre librairie que Puppeteer ou Selenium qui puisse contrôler un Firefox Headless en respectant mon cahier des charges et qui soit donc disponible depuis C#.
|
||||
|
||||
Si je n'arrivais pas à faire fonctionner Selenium j'aurai du abandonner l'idée de simuler un navigateur tout simplement. Mais j'ai eu la chance que cette librairie fasse tout ce que je pouvais demander. C'est une super lib et même si la version C# n'est vraiment pas bien documentée, la plupart des documentations de la version JS sont pertinentes pour al version C# même si ca n'est pas la même syntaxe.
|
||||
|
||||
Pour résumer, j'ai choisis Puppeteer car c'était la seule option viable pour mon besoin.
|
||||
|
||||
(Note : Par contre si je trouve la personne chez mozilla ou puppeteer qui a décidé d'hardcoder la résolution maximale du browser Headless que l'on peut override UNIQUEMENT en changeant les variables d'environnement de la machine ET DE NE LE DOCUMENTER QUASI NULLE PART JE JURE QUE CA VA TRES MAL SE PASSER)
|
||||
|
||||
#### CSharp
|
||||
|
||||

|
||||
|
||||
Je pense que c'est le choix le plus simple à expliquer. C# est un language de programmation orienté objet relativement haut niveau qui a été créé par Microsoft et qui a comme cible le developpement d'applications pour Windows. (On peut evidemment trouver des adaptations pour le faire tourner sur Linux mais ce n'est pas vraiment le but du language)
|
||||
|
||||
En plus d'être un superbe language de programmation, c'est le language que l'on apprend au CFPT informatique. C'est donc un language avec lequel je suis beaucoup plus à l'aise que pour d'autres languages comme le Python ou le JS.
|
||||
|
||||
Mon but n'était pas de faire une application Web et je travaille sous Windows. Je savais que mon projet allait demander un minimum de programmation orientée objet. J'ai donc immédiattement pensé à utliser C#.
|
||||
|
||||
Cependant j'aurais très bien pu utiliser un language comme python qui m'aurait clairement facilité la tâche avec des librairies bien plus fournies et plus souvent mises à jour. Mais comme je ne suis pas du tout aussi à l'aise avec je pense que le C# était la meilleure option.
|
||||
|
||||
Mes seuls regrets après coup sont que je trouve les Windows Forms très moches et qu'il est particulièrement difficile de les rendres plus jolies et que les librairies disponibles en C# pour des scénarios très précis ne sont pas au niveau de celles pour JS et pour Python.
|
||||
|
||||
Cependant si j'avais à refaire le projet je reprendrais C# je pense.
|
||||
|
||||
#### Python?
|
||||
|
||||

|
||||
|
||||
Alors ce choix la est plus commpliqué à comprendre.
|
||||
|
||||
Pour tout le projet j'ai tenté de garder le C# comme language et de ne pas utiliser autre chose. Cependant j'ai du utiliser une seule fois le Python dans un cas très précis.
|
||||
|
||||
Je n'aime vraiment pas coder en python de base et clairement j'aurais préfèré ne pas l'utiliser mais je n'avais pas le choix.
|
||||
|
||||
Le besoin dans le cas du python était le suivant :
|
||||
|
||||
J'avais besoin d'un moyen de récupèrer des strings et les décoder avec une clé encodée avec le système propriétaire de windows d'encodage.
|
||||
|
||||
Le soucis que j'avais avec le C# c'est que les methodes de decryption ne fonctionnent pas pareil que en python et tous les exemples que je pouvais trouver étaient en python. J'ai essayé pendant un sacré moment de faire fonctionner la decryption en C# mais sans succès.
|
||||
|
||||
J'ai donc directement utilisé le python comme faisait toutes les personnes que je pouvais voir sur internet et je pense que ca n'est pas une mauvaise idée. En effet, cela veut dire que si à un moment Chrome est mis à jour, je n'aurai pas besoin d'aller ouvrir tout le code source de mon projet pour tout recompiler, j'aurai simplement besoin de changer ce script.
|
||||
|
||||
Un des avantages du Python est quand même qu'il y beaucoup plus de gens qui codent dessus, et pour ce genre d'utilisation très spécifiques c'est plutôt pratique.
|
||||
|
||||
Le seul problème c'est que cela oblige l'utilisateur à avoir python installé sur sa machine et que sa version doit être compatible... (les joies de python)
|
||||
|
||||
#### Firefox
|
||||
|
||||

|
||||
|
||||
J'en parle deja plus bas, mais le choix de navigateur est super important.
|
||||
|
||||
Deja tous les navigateurs n'ont pas un mode Headless(sans tête, mieux expliqué dans la rubrique "Simuler un naviguateur ?"). Par exemple, même si Edge est maintenant basé sur chromium, il n'existe pas de moyen de le faire tourner en Headless pour le moment.
|
||||
|
||||
Autre soucis, les librairies d'automatisation ne supportent pas tous les navigateurs. Par exemple, beaucoup supportent chrome mais très peu supportent Firefox ou Edge.
|
||||
|
||||
Donc il me fallait un navigateur qui puisse opèrer en Headless et qui soit supporté par plusieurs librairies d'automatisation.
|
||||
|
||||
Il n'y a que Firefox et Chrome qui sont conforme à ces exigeances (Je n'ai pas verifié pour TOUS les navigateurs. Peut-être que les Opera GX ont aussi un mode headless super, mais je me suis concentré sur les navigateurs plus grand public).
|
||||
|
||||
Chrome est supporté par plus de lib, mais le soucis c'est que la F1TV utilise un lecteur de vidéo avec DRM (Plus d'infos la dessus dans la partie "Simuler un navigateur?") et donc le choix était simple. Il ne restait que Firefox.
|
||||
|
||||
#### Tesseract
|
||||
|
||||
Je pense que le choix le plus simple après le C# fut l'utilisation de Tesseract.
|
||||
|
||||
C'est tout simplement l'outil le plus utilisé pour faire de l'OCR. A la base c'est une lib Python (Ah tiens encore ?) qui peut être redoutablement efficace avec le bon dataset.
|
||||
|
||||
Il existe d'autres outils mais j'ai décidé de prendre celui la à cause de son support juste incroyable et de son omniprésence dans la documentation OCR.
|
||||
|
||||
En plus il est facile à utiliser et je ne pense pas encore avoir fait le tour de tout son potentiel dans ce projet.
|
||||
|
||||
### Récupération des images
|
||||
|
||||
Voici la première grande étape du projet.
|
||||
@@ -1535,7 +1649,136 @@ Dans ce projet le but n'est pas simplement de trouver les données et les affich
|
||||
|
||||
Le vrai soucis de la F1TV c'est justement que l'on ne peut pas facilement voir les évolutions. On ne peut voir que des "photos" de la situation actuelle de la course.
|
||||
|
||||
Il faut donc garder en mémoire les différentes choses qui se sont passées. Techniquement on pourrait stocker ces données dans de bêtes listes C#.
|
||||
Il faut donc garder en mémoire les différentes choses qui se sont passées. Techniquement on pourrait stocker ces données dans de bêtes listes C#. Mais le soucis avec ca c'est que même si des outils comme LinQ existent, ca n'est pas le plus pratique quand on veut faire des recherches complexes.
|
||||
|
||||
Il faut aussi voir que si le projet dans sa forme actuelle aurait peut-être pu se satisfaire de listes simples, le but est d'ensuite pouvoir construire sur ces bases pour faire des predictions et des insertions de stats beaucoup plus intéressantes qui demandent de faire des requêtes complexe rapidement.
|
||||
|
||||
Je me suis dit que la meilleure methode serait d'avoir une base de donnée dans laquelle je peux faire des requètes SQL. Mais commme je n'ai pas besoin de toutes les features de SQl et que je ne veut pas avoir à gèrer un serveur de base de donnée et tout ce qui va avec je me suis dit qu'une bonne option serait d'utiliser SQLITE.
|
||||
|
||||
;
|
||||
|
||||
SQLITE est vraiment pratique car cela me permet d'avoir une DB sans avoir de serveur donc pas vraiment complexe ou quoi que ce soit mais qui conserve les aventages de rapidité et d'utilisation de requetes SQl.
|
||||
|
||||
J'ai créé trois tables dans cette base de donnée SQLITE que voici :
|
||||
|
||||
#### Base de donnée
|
||||
|
||||
Drivers
|
||||
|
||||
| Colonne | Type de Data | Description | Tag |
|
||||
|---------------|---------------|---------------|-----------|
|
||||
| ID | INTEGER | ID du pilote | PRIMARY |
|
||||
| Name | VARCHAR | Nom du pilote | NOT NULL |
|
||||
|
||||
Pitstops
|
||||
|
||||
| Colonne | Type de Data | Description | Tag |
|
||||
|-------------|-----------------|---------------------------------------------|-----------|
|
||||
| Lap | INTEGER | Tour durant lequel le Pitstop a été effectué| PRIMARY |
|
||||
| DriverID | INTEGER | Pilote qui a effectué le Pitstop | PRIMARY |
|
||||
| Tyre | VARCHAR | Pneu chaussé par le pilote | NOT NULL |
|
||||
|
||||
Stats
|
||||
|
||||
| Colonne | Type de Data | Description | Tag |
|
||||
|---------------|-----------------|---------------------------------------------|-----------|
|
||||
| Lap | INTEGER | Tour durant lequel le Pitstop a été effectué| PRIMARY |
|
||||
| DriverID | INTEGER | Pilote qui concerné | PRIMARY |
|
||||
| Tyre | VARCHAR | Pneu chaussé par le pilote | NOT NULL |
|
||||
| LapTime | INTEGER | Temps au tour (MS) | NOT NULL |
|
||||
| Sector1 | INTEGER | Temps du secteur 1 (MS) | NOT NULL |
|
||||
| Sector2 | INTEGER | Temps du secteur 2 (MS) | NOT NULL |
|
||||
| Sector3 | INTEGER | Temps du secteur 3 (MS) | NOT NULL |
|
||||
| GapToLeader | INTEGER | Ecart avec le leader (MS) | NOT NULL |
|
||||
| Position | INTEGER | Position pilote | NOT NULL |
|
||||
|
||||
La table Drivers sert juste à stocker les différents noms de pilote pour qu'ils soient utilisés dans le reste de la DB
|
||||
|
||||
La table Pitstops n'est pas vraiment utilisée dans l'état actuel du projet. Mais le but était de la remplir dès que le programme détectais un arrêt aux stands. Le but est ensuite de pouvoir construire un classement pondèré en fonction des arrêts des différents pilotes et d'afficher la stats tout le temps sur l'affichage principal.
|
||||
Elle n'est pas vraiment utilisée car la detection de pitstop n'a pas pu être commplêtée. De par la nature des données récupèrées des pneus et des positions, c'est très difficile de detecter avec prescision un arrêt aux stands.
|
||||
|
||||
La table Stats est la plus importante car elle contient toutes les informations concernant les pilotes à chaque tour. L'idée est qu'elle soit remplie à chaque tour. Les infos ne sont pas scensée être les infos live mais plutôt juste une photo à chaque tour de la situation de chaque pilote pour ensuite pouvoir faire des commparaisons tourspar tours. Des données comme le GapToLeader peuvent évoluer pendant le tour mais on s'en fiche. Ce qui compte vraiment c'est le temps au tour et les secteurs ainsi que les pneus.
|
||||
|
||||
#### Quand remplir la base ?
|
||||
|
||||
Dans ce projet il y a deux type d'information. Les informations live qui sont stockées dans des listes et les informations long terme qui sont stockées dans la DB.
|
||||
|
||||
A chaque itération de l'OCR, les données récupèrées sont stockées dans une liste de DRIVERDATA.
|
||||
|
||||
Les DRIVERDATA sont des structures de données qui contiennent toutes les infos d'un pilote à un instant T. Elles peuvent être incomplêtes et sont juste la pour faire de petits calculs et determiner quand insèrer des données permanentes.
|
||||
|
||||
Ce qui nous amène au moment intéressant. Comment on détermine quand il est intéressant d'insèrer des informations dans la base de données.
|
||||
|
||||
Il y a deux cas de figure ou on pourrait vouloir insèrer des infos :
|
||||
|
||||
##### Quand un pilote a finit un tour
|
||||
|
||||
En effet, j'ai estimé que les seuls moments ou on veut garder une photo de la situation du pilote c'est quand il passe d'un tour à l'autre.
|
||||
Le raisonnement est le suivant :
|
||||
On ne veut pas conserver TOUTES les données car si on prend une phot toutes les trois secondes, la majorité des informations seront redondantes avec les précédentes.
|
||||
Mais en même temps il ne faut pas rater des changements importants de données.
|
||||
Les seules données qui changent entre deux passages de l'OCR sont les écarts entre les pilotes et de temps en temps un nouveau secteur s'affiche. Alors que d'un tour à l'autre presque toutes les informations changent. Et on ne perd que les légères fluctuations des écarts entre les pilotes.
|
||||
|
||||
J'ai donc décidé de conserver une photo par tour. Mais c'est bien joli sauf qu'il reste une difficulté : Comment savoir qu'un pilote a fait son tour ?
|
||||
|
||||
Cela peut paraître simple comme question mais elle est plus difficile qu'il n'y parait. Il faut savoir que en F1 un pilote peut être dans son 26ème tour pendant qu'un autre en est à son 24ème. Chaque pilote a sa propre course et au fur et à mesure que les écarts se creusent il peut y avoir un tour voir plusieurs d'écart entre la queue de course et les premiers pilotes.
|
||||
|
||||
Ensuite il faut savoir qu'il n'est pas marqué sur la f1TV dans quel tour chaque pilote est. Il faut donc le déduire en fonction des Data.
|
||||
|
||||
Voici le code le if qui détecte un nouveau tour
|
||||
|
||||
```Csharp
|
||||
if (DriverDataLogs[i][DriverDataLogs[i].Count - 1].Sector3 != 0
|
||||
&& DriverDataLogs[i][DriverDataLogs[i].Count - 2].Sector3 == 0
|
||||
&& DriverDataLogs[i][DriverDataLogs[i].Count - 2].Position != -1
|
||||
&& DriverDataLogs[i][DriverDataLogs[i].Count - 1].Position != -1)
|
||||
{
|
||||
//Do stuff
|
||||
}
|
||||
```
|
||||
|
||||
DriverDataLogs est une liste de liste d'informations de pilotes.
|
||||
|
||||
Chaque DriverDataLogs représente les 20 photos des données des pilotes.
|
||||
|
||||
Cela veut dire que `DriverDataLogs[3]` représente toutes les infos des pilotes dans le tour 4 et que `DriverDataLogs[3][0]` représente toutes les infos du premier pilote dans le tour 3.
|
||||
|
||||
Si on analyse un peu ce qui est écrit avec ces informations, on peut voir que je détermine qu'un nouveau tour se définis comme une photo ou le troisième secteur a été complêté et ou il ne l'était pas juste avant. Cela fait sens car quand un pilote complête son troisième secteur c'est la que son dernier temps au tour se met à jour.
|
||||
|
||||
Le reste des tests est juste la pour éviter les faux positifs dans le cas ou un des deux `DriverDataLogs[x][x]` soit corrompu et que donc la valeur n'aie pas de sens. Cela veut dire que dans des conditions très spécifiques je pourrais potentiellement rater un tour mais il faudrait vraiment que l'OCR me joue un vilain tour.
|
||||
|
||||
##### Quand un pilote a fait un arrêt aux stands
|
||||
|
||||
Et la on touche le plus difficile. Pourtant un arrêt aau stand ne devrait pas être compliqué à detecter. C'est quand un pilote change de pneu. Alors il peut changer de pneu en gardant le même type de pneu et donc tout repose sur le nombre de tour qu'un pneu fait.
|
||||
|
||||
Sauf que il faut ajouter à cette reflexion qu'un pneu peut être chaussé sans qu'il soit neuf. Ce qui veut dire que l'on ne peut pas simplement choisir qu'un pilote a changé de pneus quand ses pneus sont à 1 tour. Il peut très bien reprendre des pneus de qualif qui peuvent avoir 10 tours dans les pattes.
|
||||
|
||||
En plus quand le pilote change de pneus il y a un phenomene assez pénible qui fait que les deux premiers tours faits avec ne sont pass vraiment déchiffrables car ils sont un peu cachés derrière la lettre qui indique le nouveau pneu chaussé.
|
||||
|
||||
Exemple :
|
||||
|
||||

|
||||
|
||||
Voici le code que j'avais écrit pour tenter de trouver quand un pilote avait fait un pitstop :
|
||||
|
||||
```Csharp
|
||||
if (data.CurrentTyre.Coumpound != Tyre.Type.Undefined
|
||||
&& data.CurrentTyre.NumberOfLaps == 0
|
||||
&& DriverDataLogs[i][DriverDataLogs[i].Count - 2].CurrentTyre.NumberOfLaps != 0)
|
||||
{
|
||||
//Do stuff
|
||||
}
|
||||
```
|
||||
|
||||
On peut voir que j'essaie de detecter quand le pneu est à 0 tours (ce qui est le moment ou il y a une lettre à la place d'un numéro de tour) et que la photo d'avant montrait un pneu normal.
|
||||
|
||||
On vérifie aussi que le pneu a bien été detecté en verifiant que le pneu n'est pas de type undefined.
|
||||
|
||||
Le soucis c'est que ce n'est pas rare que l'OCR nous retourne qu'un pneu est vieux de 0 tours et donc il est absolument impossible de faire confiance à cette metric.
|
||||
|
||||
Si on veut utiliser cette methode pour trouver les Pitstop il va falloir avant tout améliorer l'OCR sur ce point.
|
||||
|
||||
Ce soucis mets en lumière un principe assez important de l'informatique "Ggarbage in, Garbage out". Si les données que je recoit ne sont pas géniales, le résultat ne sera pas génial non plus. Ce qui est frustrant c'est que la detection des pneus n'est pas si mal mais entre les chiffres qui se chevauchent ce qui nous fait lire 0 alors que c'est juste un un '1' derrière un 'H' ou le 1% du temps ou le programme se trompe, on ne peut pour l'instant tout simplement rien faire de mieux.
|
||||
|
||||
### Affichage des données
|
||||
|
||||
|
||||
Reference in New Issue
Block a user