I’m a (script driven) robot

Vous l’avez sans doute compris, j’aime beaucoup le scraping.  Pour des cas compliqués, l’émulation d’un véritable browser devient de plus difficile. Certaines protections vont tellement loin dans construction des requêtes successives pour obtenir un résultat que cela devient pénible et aussi instable.

Je suis donc parti, pour ces cas, dans l’approche Selenium, c’est à dire un véritable navigateur sans tête piloté par script. 

Au final je suis parti parti la sous-branche qui procède à cette construction en utilisant google-chrome (c’est fallacieux et amusant à la fois, sachant que je n’utilise jamais ce navigateur).

Il y a des exemples dans beaucoup de langages, mais finalement assez peu sur les bindings en perl.

Grâce à cet article, https://www.perl.com/article/spidering-websites-with-headless-chrome-and-selenium/ la lumière m’est apparue et j’ai gagné beaucoup. It’s more fun to compute en perl. https://en.perlzemi.com/blog/20211119124656.html est aussi très sympa.

J’ai suivi sa procédure, avec quelques adaptations pour mon Ubuntu server. J’ai pris une version plus récente du chrome driver, installation chrome et module Selenium::Remote::Driver sans problèmes. J’avais sur mon système une version de java (pour mon solr) pas compatible semble t’il avec le serveur selenium standalone. J’ai donc mis un openjdk 11 et tout s’est bien emboité.

C’est assez tranquille mais j’ai pas mal erré pour ce qui est de cacher le caractère headless de mon chrome en écrasant la chaîne user-agent d’origine car sinon c’est pas du jeu. Pour une chaîne user-agent dans une variable $ua, je me retrouve avec le constructeur suivant, avec un peu de random en plus dans la taille de la fenêtre virtuelle:

$sx = sprintf("%d",1600 + ((rand() - rand()) * 303 ));
$sy = sprintf("%d",900 + ((rand() - rand()) * 256 ));
my $driver;
eval {
$driver = Selenium::Remote::Driver->new(
  browser_name => $ua,
  extra_capabilities => {
    chromeSwitches => [ "--user-agent= '$ua'" ],
    chromeOptions => {
      args => [
        'window-size='.$sx.','.$sy,
        'headless',
        'user-agent='.$ua,
      ],
    },
  },
  );
};

A partir de là, j’ai pu réellement jouer. Le driver Selenium permet pas mal de choses, mais, pour le moment, je récupère surtout la source de la page que je passe ensuite à une XML::LibXML comme à mes habitudes des dernières années. Il est possible de jouer avec des XPaths, des screenshots, des evals de javascript donc plein de choses très amusantes en perspective. Cela permet notamment d’obtenir le DOM interprété plutôt que la source de la page $driver->execute_script("return document.documentElement.outerHTML");

Ne pas oublier à la fin de faire un $driver->quit() pour libérer de la mémoire

Linkedin’s code tag

Mon user script puis mon prototype d’add-on avait de manière récurrente des problèmes avec Linkedin.

Après moultes débugs, j’ai fini par comprendre qu’ils font en fait un usage très spécial de la balise <code>

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code

Pour linkedin, code se retrouve être une balise cachée dans laquelle ils embarquent des données.

Mon content script évitait déjà les balises script et autres styles mais de la à penser que le contenu des balises code doivent être ignorés, il faut vraiment avoir l’esprit tordu.

Avant ou après le rachat par MSFT ?

https://stackoverflow.com/questions/42530879/why-does-lindkedin-use-hidden-code-tags-in-their-updated-website

Samesite cookies & rails & clueless monkey patch

Une vieille application Ruby on Rails 3. C’était par tourisme de dev et j’aime bien, malgré les particularités de ruby (typage et scope) . Des browsers qui commencent à se plaindre du mauvais voir du manque d’attribut same site sur les cookies (dans mon cas des cookies de session).

https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Set-Cookie/SameSite

Je commence par bien flipper. Entre toutes les versions de rails, des heures de recherche, j’étais presque résolu à monkey-patcher les libraires mais, étant bien incapable de localiser où frapper, je commençais à désespérer.

Le site étant service par un apache2 sous mod_passenger, la lumière la plus évidente m’est enfin apparue. J’installe et j’active le mod headers d’apache2 puis je rajoute ce segment dans la section qui vas bien de la config apache du site en question:

<ifmodule mod_headers.c>
Header always edit Set-Cookie (.*) "$1; SameSite=strict"
</ifmodule> 

Je redémarre mon apache et mon firefox ne se plaint plus. Magique !!

NoUTF8MB4

Dans un job d’indexation de données externes, essentiellement RSS mais aussi tweets (cf poste précédent), je me suis retrouvé bien en difficulté d’encodage, collation et stockage dans une base de contenus avec des emojis. J’avais pourtant passé la table d’utf8 à utf8mb4 mais cela ne marchait pas (peut-être un bug dans DBI mais je pense pas car j’ai déjà réussi ailleurs). Après pas mal de recherches, j’ai eu l’idée de ce patch qui a pour objet de garder les emojis sous forme d’entités hautes tout en conservant le texte en bas (les entitiés nommées) de l’unicode en direct.

$s = encode_entities($s);
(@entits) = $s =~ /(&.*?;)/g;
foreach $entit (@entits) {
  $deco = encode('utf-8',decode_entities($entit));
  $s =~ s/$entit/$deco/g unless($entit =~ /&#x/);
}

Plus qu’à décoder pour injection dans solR, ou conserver tel quel lors de l’inclusion dans le HTML. Petit hack qui évite bien des tracas.

Perl Image::ExifTool

Je le mets ici car j’ai un peu trimé pour trouver les infos nécessaires.

ExifTool marche parfaitement mais ne pas oublier d’aller voir sur https://metacpan.org/pod/Image::ExifTool#DESCRIPTION que le type d’accès qu’offre le module en fonction du type fichier que vous visez (par example MP3 en lecture, PDF en compl<et).

#!/usr/bin/perl
use Image::ExifTool;
$exifTool = new Image::ExifTool;
$exifTool->ExtractInfo($ARGV[0]);
if ($ARGV[2]) {
$val = $exifTool->GetValue($ARGV[1],'ValueConv');
if (ref $val eq 'ARRAY') {
@values = split(/,\s*/g,$ARGV[2]);
$exifTool->SetNewValue($ARGV[1] => \@values, { Replace=>1, AddValue => 2 });
} else {
$exifTool->SetNewValue($ARGV[1],[$ARGV[2]]);
}
$exifTool->WriteInfo($ARGV[0]);
}
$exifTool->ExtractInfo($ARGV[0]);
foreach $tag (grep { !($_ =~ /\s(1)$/) } sort $exifTool->GetFoundTags()) {
$tt = $tag;
$tt .= ' (1)' if (grep { $_ eq $tt.' (1)'} $exifTool->GetFoundTags());
printf("%-21s : %s\n",$tag,$exifTool->GetValue($tt));
}

Notes: https://exiftool.org/ExifTool.html

Regex Article

Ici

$ra = ‘\s*(?:(([Aa][Nn][Nn][Ee][Xx][Ee]\s[A-Za-z0-9]+)?|([Tt][Aa][Bb][Ll][Ee][Aa][Uu]\s*[Nn]?°?\s?[A-Z0-9]*\s?)?)|\*?[LRDAO]*\.?\s?\**\.?\s?\d+(\-\d+)*\-?\s?[A-Z]*)(\s?(?:tricies|bis|ter|quater|quinquies|sexies|septies|octies|nonies|unvicies|quinquies|novovicies|tertricies|terdecies|duotricies|untrecies|quatertricies|decies|undecies|duodecies|terdecies|quaterdecies|quindecies|sexdecies|septdecies|octodecies|novodecies|vicies|unvecies|duovicies|tervecies|quatervicies|quinvicies|sexvicies|septvicies|octovicies|tervicies)?\s?[A-Z0-9\-]*\s?(?:bis|ter|quater|quinquies|sexies|septies)?\s?[A-Z0-9\-]*\-?\d?)?’;

Taux de réussite: 99.61 % au jour d’aujourd’hui

Bilan: 140.804 articles qui correspondent sur un total de 141.504

.

On examine ensuite la liste des matchs par ordre de longueur décroissante, on évalue au besoin la distance entre chaque match et le nom du code, et on confronte alors les résultats avec des tables de correspondances numéro:identifiant legifrance, …

Big Code ?

Je crois que j’en ai eu assez d’entendre des politiques et autres commentateurs dire que le code du travail trop gros et que sa modification est urgente. Au delà des arguments politiques, j’aimerais mettre quelques chiffres ici, suite à mon expérience sur ces données (http://codes.droit.org) et bientôt une application iOS et des web-services.

Si je regarde le poids des PDFs que je génère pour chaque codes (droit positif + articles liés), je constate que les plus lourds sont :

  • sante_publique.pdf (35412646)
  • rural_peche_maritime.pdf (30630860)
  • securite_sociale.pdf (30238979)
  • travail.pdf (28924689)
  • impots.pdf (23586049)

En nombre d’articles, c’est différent:

  • sante_publique (11845)
  • travail (1123)
  • rural_peche_maritime (9413)
  • securite_sociale (7538)
  • commerce (6679)

Enfin, si on se contente d’observer le nombre de liens, on obtient:

  • sante_publique 19001
  • securite_sociale 17364
  • rural_peche_maritime 14501
  • travail 13442
  • monetaire_financier 11899

Bref, le code du travail n’est ni le plus lourd, ni le plus long.

Je vous épargne mes commentaires politiques.

Open Game

J’étais à une conférence où il était question entre autre d’Open Access et Démocratie.

J’ai été marqué par plusieurs choses. Par exemple dans l’opposition vis à vis des éditeurs privés (concentrations, prix abusif, opacité, …) plusieurs personnes semblaient rêver d’interventions de l’Etat afin de garantir un accès démocratique. Plusieurs fois l’intervention du secteur public semble vécue comme la panacée. Sur le fond je trouve aussi que sur de telles questions l’intervention de l’Etat est plus que légitime et pourrait aller encore plus loin de par ses prérogatives. Reste que vu l’évolution du paysage politique, je ne pense malheureusement pas que l’intervention de l’Etat soit l’ultime remède car certains prétendants au pouvoir (et pas les plus mal placés (( entretiennent apparemment de gros fantasmes de privatisations.

Le rempart cela semble être la publication de code open source permettant de traiter les données. Si tel service est privatisé ou absorbé par un méchant éditeur du secteur privé, au moins on sauve l’infrastructure logicielle de traitement. Ce raisonnement doit être modéré par les interrogations sur l’Open Source au sens logiciel. Pour ma part je relis très très rarement le code d’autres personnes. Peut-être suis-je une loup des steppes/circuits mais je vois rarement mes confrères proches évoquer ce  type d’activité non productrice à court terme.

Vu de l’extérieur code is code, il sera relu et tout ira bien.

Mon point principal, en tant que programmeur en perl, javascript, xml et/ou swift, c’est que le langage dans lequel sont développés ces projets est un enjeu majeur. Il suffit de peu d’observations pour se rendre compte que le public qui s’exprime en assembleur, C++, python ou dotnet sont très différents. Il y a une part d’idéologie sous-jacent, un constat générationnel et sociologique. C’est pourtant bien occulté au grand public. Pour un certain nombre de personnes plus proches du milieu vas vous tenir le raisonnement que pour tel type de job il n’y a pas à tergiverser quand au choix de l’outil qui doit s’imposer par lui même. Je n’y crois pas. Comme on dit souvent entre perlistes, « there’s more than one way to do it ».

J’y crois d’autant moins que j’ai connu professionnellement des cas où la décision du choix du langage incombe à la hiérarchie non technique. Je suis pour le pluralisme, je pratique certains langages mais j’en ignore bien d’autres. Je n’ai pas envie d’imposer mes choix, au mieux convaincre, et encore. J’aurai pu apprendre php il y a quelques années mais cela me révulse, j’aurai pu me mettre au python mais je trouve le code inélégant, et je suis trop jeune pour avoir un background en assembleur. Paysage d’autant plus compliqué avec l’explosion du nombre de librairies et fameworks, il y en a pour tous les goûts.

Code is not code. Ce n’est pas un tour de babel mais une ville avec ses quartiers et ses ghettos, sa politique et sa sociologie.