Coding session 5 – un web crawler avec Puppeteer

Date de la session : 21/08/2021 15h00-18h00

Date de publication : 22/03/2021

Intro

Analyser comment crawler digicamdb.com.

Description

Un fichier JSON contenant la liste des noms des caméras est facilement récupérable depuis la console de développement.

D’ailleurs, on peut remarquer que le nombre d’éléments dans le JSON ne correspond pas au nombre de camera répertoriés sur le site. Le Fujifilm X-S10 est sorti en fin 2020 et est 4028e élément.

cs5 number-cameras-in-digicamdb-edited.gif

Au début, je pensais qu’avec cette liste, j’aurais pu facilement déterminer les URLs.

En partant du principe que le premier espace rencontré serait à remplacer par un _. Et les espaces suivant sont à remplacer par un -.

Example :

Fujifilm X-S10 ⇒ fujifilm_x-s10

Bien que cette règle fonctionne dans la plupart des cas, j’ai quand même trouvé certains cas où elle ne fonctionne pas.

cs5 inconsistence-url-edited.gif

Deuxième solution plus fiable, utiliser le formulaire qui sert de navigation en haut de la page.

Il suffira de copier chaque valeur du tableau dans le champ du formulaire, et de cliquer sur le bouton de recherche. Attendre le chargement de la page. Récupérer les informations. Recommencer avec la valeur suivante ceci plus de 4 000 fois.

cs5 form navigation-edited.gif

Objectifs

  • Crawler digicamdb.com
  • Récupérer la liste des URLs des pages
  • Définir une méthodologie pour naviguer sur les 4 000 + pages

Code

Le script crawler digicamdb sera créé en s’inspirant des scripts précédant.

Difficultés rencontrées

Rien à voir, mais j’ai décidé de faire des enregistrements d’écrans. Et les transformer en GIF pour les inclure dans les articles coding sessions. Ça rajoute une perte de temps dans la progression vers la finalisation de l’application.

Il faut enregistrer l’écran, éditer la vidéo retirer les moments ennuyeux. Parfois transformer en zoomant, croppant des parties de la vidéo. Après l’export convertir en GIF. Une étape supplémentaire en plus de rédiger l’article 😆.

Points positifs

Pas de point de blocage pour l’instant, c’est encourageant.

Nouvelles problématiques

Coder, écrire un article coding session, et dans un même temps capturer, éditer ma progression sous forme de vidéo/GIF.

Prochaines étapes

Exécuter le script crawler digicamdb.

Coding session 4 – un web crawler avec Puppeteer

Date de la session : 19/08/2021 18h30-00h00++

Date de publication : 22/09/2021

Intro

Exécution du deuxième script.

deuxième script crawler allphotolenses
deuxième script crawler allphotolenses

Description

Parfois, ça plante.

Mais grâce aux logs, il suffit de récupérer le nombre de l’itération qui a planté, et replacer la boucle for à l’endroit ou ça à planté.

script faillure-edited.gif
script failure

Lors de ma recherche de base de données, j’ai enfin trouvé le bon terme de recherche pour Google. « digital camera database ».

Je suis tombé sur ce site. teoalida.com/database/digitalcameras

C’est apparemment quelqu’un qui c’est spécialisé dans le scraping de site web. Il propose les données du site digicamdb.com. Contre un paiement de $75.32, marrant, c’est ma prochaine cible.

teoalida-digital-camera-database.PNG

Site très intéressant, je m’arrête là pour ce soir, il faut que visite plus en profondeur ce fameux site WordPress teoalida.com

Objectifs

  • Faire fonctionner le 2ième Crawler

Code

2ième script.

Récupère les informations sur l’objectif.

  • ‘Name’, ‘Focal length’, ‘Max. aperture’, ‘Min. aperture’, ‘Blades’, ‘Min. Focus (m.)’, ‘Filter Ø (mm.)’, ‘Weight (gr/oz)’, ‘Length (mm/in)’, pictures
import { chromium } from 'playwright';import * as fs from "fs";
const pages = require("./paths.json");
(async () => {
    const browser = await chromium.launch();
    const page = await browser.newPage();
    // START
    let results: any[][] = [
        [
            'Name',
            'Focal length',
            'Max. aperture',
            'Min. aperture',
            'Blades',
            'Min. Focus (m.)',
            'Filter Ø (mm.)',
            'Weight (gr/oz)',
            'Length (mm/in)'
        ]
    ];
    let fileNumber = 0;
    console.time('time_elapsed')
    for (let i = 526; i < pages.length; i++) {
        await page.goto(pages[i], { waitUntil: 'networkidle', timeout:40000});
        console.log("iteration ", i, pages[i])
        let infos: any[] = await page.$eval('strong', elementStrong => {
            let values: string[] = [];
            //let parentElement = elementStrong.find(el => el.textContent === 'Specifications:')
            // let elementTh = elementStrong.filter(el => el.textContent === 'Specifications:')[0].parentElement.querySelectorAll('th');
            // headers = Array.from(elementTh).map(th => th.textContent)
            let elementTd = elementStrong.filter(el => el.textContent === 'Specifications:')[0].parentElement.querySelectorAll('td');
            values = Array.from(elementTd).map(r => r.textContent)
            return values;
        })
        let name: string = await page.$eval('h1', h1 => {
            return h1.textContent;
        })
        let pictures: string[] = await page.$eval('strong', elementStrong => {
            let values: string[] = [];
            let elementTd = elementStrong.filter(el => el.textContent === 'Pictures')[0].parentElement.querySelectorAll('a');
            values = Array.from(elementTd).map(r => '<http://allphotolenses.com>' + r.getAttribute('href'))
            return values;
        })
        let pics = {
            pictures: pictures
        };
        infos.push(pics);
        // rotate file
        let r = await page.evaluate(({ results, name, infos, fileNumber }) => {
            if (results.length == 100) {
                fileNumber = fileNumber + 1;
                results = [];
            }
            results.push([name].concat(infos));
            return { fileNumber, results }
        }, { results, name, infos, fileNumber });
        results = r.results;
        fileNumber = r.fileNumber
        console.log("write to file ", r.fileNumber, results.length)
        fs.writeFileSync(`./results/lenses-${r.fileNumber}.json`, JSON.stringify(r.results));
    };
    console.log("results length", results.length);
    console.log("results :", results);
    // END
    console.timeEnd('time_elapsed');
    await browser.close();
})();

Difficultés rencontrées

  • Trouver la bonne technique pour traverser le DOM
  • Le script plante parfois pour accéder à une page. Lié à une erreur de chargement de la page. Cela induit une opération manuelle pour relancer le crawl. 😑

Points positifs

  • 2ième script exécution ok partielle.

Nouvelles problématiques

Fixer les erreurs de chargement de la page lors de l’exécution du script.

Prochaines étapes

Crawler digicamdb.com.

Coding session 3 – un web crawler avec Puppeteer

Date de la session : 19/08/2021 19h30-02h00++

Date de publication : 22/09/2021

Intro

Idée d’app. Pouvoir calculer le poids de son équipement appareil photo et voir quel stabilisateur serait approprié en fonction du poids supporté. (En écrivant ces lignes me vient une autre idée. Cette app pourrais aussi calculer le poids des équipements style trépieds, lumière, micro, batterie externe, disque dur, mais bon là ça devient fastidieux. Faut d’abord se concentrer sur une feature. Et cette feature doit être bien faite donc je disais…) Pour cette appli il me faut des données. À savoir les appareils photo noms et poids. Et les objectifs noms et poids.

Aucune base de données libre d’accès, n’est disponible ni d’API. En revanche, il existe des sites qui recenses des appareils photos et des objectifs.

Changeons de perspective internet est une base de données et les pages sont des tables.

Je veux récupérer des informations dans ces tables. Je vais devoir les crawler 😎.

Voici quelques sites qui semblent contenir les informations que je recherche.

Description

Pour récupérer les données de dxomark il suffit d’ouvrir la console. Et regarder les appels HTTP.

retrieve data from dxomark
Retrieve data from dxomark

Après analyse, il semblerait que les données de dxomark ne correspondent pas à ce que je recherche. Le poids de l’appareil photo n’est pas présent.

allphotolenses.com/lenses contient des informations sur les objectifs. Le nom et le poids de l’objectif sont présents. Même quelques images de l’objectif parfois 👌🏾.

En revanche, pour récupérer ces données ce n’est pas aussi simple que sur dxomark.

Les données ne sont pas requêtées depuis le client. Les pages sont soit statiques côté serveur ou généré côté serveur.

Du coup, il va falloir récupérer les informations directement dans la page HTML.

La première étape va consister à récupérer toutes les URLs des pages qui m’intéressent.

À savoir 3800+ URLs.

Seulement après avoir écrit et exécuté le 1er script je rendit compte que les URLs aurait pu facilement être deviné.

["Canon/Canon-EF-35mm-F14L-II-USM",
"Canon/Canon-EF-100mm-F2-USM",
"Fujifilm/Fujifilm-FUJINON-XF-200mm-F2-R-LM-OIS-WR"
]

J’aurais pu générer ces URLs avec une boucle sur le nom des objectifs contenu dans le JSON récupéré précédemment. Passer plus de temps sur l’analyse m’aurait évité du temps perdu a développer ce premier script. Mais ce n’est pas plus mal. J’ai pu monter en compétences sur l’utilisation de Puppetteer et de valider mon choix de d’outil.

Objectifs

  • Base de données cameras.
  • Base de données objectifs.
  • Base de données sur Google Sheets
  • API de recherche GET cameras/ & GET lenses/ +  Swagger
  • Crawler Nodejs

Code

Premier script permet de récupérer toutes les pages.

import { chromium } from 'playwright';
import * as fs from "fs";
(async () => {
    const browser = await chromium.launch();
    const page = await browser.newPage();
    // START
    const results = [];
    const pageLenesSize = Array.from({ length: 239 }, (_, index) => index + 1);;
    for (let i = 0; i < pageLenesSize.length; i++) {
        await page.goto('<https://allphotolenses.com/lenses/>' + `p_${i + 1}.html`,
            { waitUntil: 'networkidle' });
        console.log("iteration ", i)
        const trLight = await page.$eval('.light_tr', e => {
            const as = e.map(r => r.querySelector('a').href);
            return as;
        })
        const trDark = await page.$eval('.dark_tr', e => {
            const as = e.map(r => r.querySelector('a').href);
            return as;
        })
        console.log('found', trLight.length, trDark.length)
        console.log(trLight, trDark);
        results.push(...trLight);
        results.push(...trDark);
    };
    console.log("results length", results.length);
    fs.writeFileSync('./paths.json', JSON.stringify(results));
    // END
    await browser.close();
})();

Difficultés rencontrées

Quel Framework choisir ?

Le choix ira pour Apfy Playwright.

Points positifs

JSON to Google Sheets api via Pipedream.

Réalisation d’un premier crawler avec Puppetteer.

Un premier script permet de récupérer les pages à crawler.

Le deuxième script va récupérer les informations sur la page.

Import du JSON généré dans notre API JSON To Google Sheets.

Nouvelles problématiques

Est-ce légal de réutiliser ces données accessibles publiquement dans une autre application ?

Est-ce légal d’exposer ces données via une API REST publique ?