Web Scraping moderne avec Python et Selenium

Publié: 2022-03-11

Le scraping Web a été utilisé pour extraire des données de sites Web presque depuis la naissance du World Wide Web. Au début, le grattage était principalement effectué sur des pages statiques - celles contenant des éléments, des balises et des données connus.

Plus récemment, cependant, les technologies de pointe en matière de développement Web ont rendu la tâche un peu plus difficile. Dans cet article, nous allons explorer comment nous pourrions procéder pour récupérer des données dans le cas où une nouvelle technologie et d'autres facteurs empêcheraient le scraping standard.

Scraping de données traditionnel

Comme la plupart des sites Web produisent des pages destinées à la lisibilité humaine plutôt qu'à la lecture automatisée, le grattage Web consistait principalement à digérer par programme les données de balisage d'une page Web (pensez au clic droit, afficher la source), puis à détecter des modèles statiques dans ces données qui permettraient au programme pour « lire » diverses informations et les enregistrer dans un fichier ou une base de données.

Récupération de données

Si des données de rapport devaient être trouvées, souvent, les données seraient accessibles en transmettant des variables de formulaire ou des paramètres avec l'URL. Par exemple:

 https://www.myreportdata.com?month=12&year=2004&clientid=24823

Python est devenu l'un des langages de scraping Web les plus populaires, en partie grâce aux diverses bibliothèques Web qui ont été créées pour lui. Une bibliothèque populaire, Beautiful Soup, est conçue pour extraire des données de fichiers HTML et XML en permettant la recherche, la navigation et la modification de balises (c'est-à-dire l'arbre d'analyse).

Scraping basé sur un navigateur

Récemment, j'ai eu un projet de grattage qui semblait assez simple et j'étais tout à fait prêt à utiliser le grattage traditionnel pour le gérer. Mais au fur et à mesure que j'avançais, j'ai trouvé des obstacles qui ne pouvaient pas être surmontés avec les méthodes traditionnelles.

Trois problèmes principaux m'ont empêché d'utiliser mes méthodes de grattage standard :

  1. Certificat. Un certificat devait être installé pour accéder à la partie du site Web où se trouvaient les données. Lors de l'accès à la page initiale, une invite est apparue me demandant de sélectionner le bon certificat de ceux installés sur mon ordinateur, puis de cliquer sur OK.
  2. Iframes. Le site utilisait des iframes, ce qui gâchait mon raclage normal. Oui, je pourrais essayer de trouver toutes les URL iframe, puis créer un plan du site, mais cela semblait difficile à manier.
  3. JavaScript. Les données ont été consultées après avoir rempli un formulaire avec des paramètres (par exemple, ID client, plage de dates, etc.). Normalement, je contournerais le formulaire et transmettrais simplement les variables de formulaire (via une URL ou en tant que variables de formulaire masquées) à la page de résultats et verrais les résultats. Mais dans ce cas, le formulaire contenait du JavaScript, ce qui ne me permettait pas d'accéder normalement aux variables du formulaire.

J'ai donc décidé d'abandonner mes méthodes traditionnelles et d'examiner un outil possible de grattage basé sur un navigateur. Cela fonctionnerait différemment de la normale - au lieu d'aller directement à une page, de télécharger l'arbre d'analyse et d'extraire des éléments de données, j'agirais plutôt comme un humain et utiliserais un navigateur pour accéder à la page dont j'avais besoin, puis gratter le données - contournant ainsi la nécessité de traiter les obstacles mentionnés.

Sélénium

En général, Selenium est bien connu en tant que framework de test open source pour les applications Web - permettant aux spécialistes de l'assurance qualité d'effectuer des tests automatisés, d'exécuter des lectures et de mettre en œuvre des fonctionnalités de contrôle à distance (permettant de nombreuses instances de navigateur pour les tests de charge et plusieurs types de navigateur). Dans mon cas, cela semblait être utile.

Mon langage de prédilection pour le scraping Web est Python, car il possède des bibliothèques bien intégrées qui peuvent généralement gérer toutes les fonctionnalités requises. Et bien sûr, une bibliothèque Selenium existe pour Python. Cela me permettrait d'instancier un "navigateur" - Chrome, Firefox, IE, etc. - puis de prétendre que j'utilisais le navigateur moi-même pour accéder aux données que je cherchais. Et si je ne voulais pas que le navigateur apparaisse réellement, je pourrais créer le navigateur en mode "sans tête", le rendant invisible à tout utilisateur.

Configuration du projet

Pour commencer à expérimenter, j'avais besoin de mettre en place mon projet et d'obtenir tout ce dont j'avais besoin. J'ai utilisé une machine Windows 10 et je me suis assuré d'avoir une version Python relativement mise à jour (c'était la v. 3.7.3). J'ai créé un script Python vierge, puis j'ai chargé les bibliothèques que je pensais être nécessaires, en utilisant PIP (programme d'installation de package pour Python) si je n'avais pas déjà chargé la bibliothèque. Voici les principales bibliothèques avec lesquelles j'ai commencé :

  1. Requêtes (pour faire des requêtes HTTP)
  2. URLLib3 (gestion des URL)
  3. Belle soupe (au cas où Selenium ne pourrait pas tout gérer)
  4. Selenium (pour la navigation par navigateur)

J'ai également ajouté des paramètres d'appel au script (à l'aide de la bibliothèque argparse) afin de pouvoir jouer avec divers ensembles de données, en appelant le script à partir de la ligne de commande avec différentes options. Ceux-ci comprenaient l'ID client, du mois/année et du mois/année au mois.

Problème 1 - Le certificat

Le premier choix que je devais faire était le navigateur que j'allais dire à Selenium d'utiliser. Comme j'utilise généralement Chrome et qu'il est basé sur le projet open source Chromium (également utilisé par les navigateurs Edge, Opera et Amazon Silk), j'ai pensé que j'essaierais d'abord.

J'ai pu démarrer Chrome dans le script en ajoutant les composants de bibliothèque dont j'avais besoin, puis en émettant quelques commandes simples :

 # Load selenium components from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait, Select from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException # Establish chrome driver and go to report site URL url = "https://reportdata.mytestsite.com/transactionSearch.jsp" driver = webdriver.Chrome() driver.get(url)

Comme je n'ai pas lancé le navigateur en mode sans tête, le navigateur est apparu et j'ai pu voir ce qu'il faisait. Il m'a immédiatement demandé de sélectionner un certificat (que j'avais installé plus tôt).

Le premier problème à résoudre était le certificat. Comment sélectionner le bon et l'accepter afin d'accéder au site ? Lors de mon premier test du script, j'ai reçu cette invite:

Récupération de données

Ce n'était pas bon. Je ne voulais pas cliquer manuellement sur le bouton OK à chaque fois que j'exécutais mon script.

En fin de compte, j'ai pu trouver une solution de contournement pour cela - sans programmation. Alors que j'avais espéré que Chrome avait la capacité de transmettre un nom de certificat au démarrage, cette fonctionnalité n'existait pas. Cependant, Chrome a la possibilité de sélectionner automatiquement un certificat si une certaine entrée existe dans votre registre Windows. Vous pouvez le configurer pour sélectionner le premier certificat qu'il voit, ou bien être plus précis. Comme je n'avais qu'un seul certificat chargé, j'ai utilisé le format générique.

Récupération de données

Ainsi, avec cet ensemble, lorsque j'ai dit à Selenium de lancer Chrome et qu'une invite de certificat est apparue, Chrome "sélectionnait automatiquement" le certificat et continuait.

Problème 2 – Iframes

Bon, alors maintenant j'étais sur le site et un formulaire est apparu, m'invitant à saisir l'ID client et la plage de dates du rapport.

Récupération de données

En examinant le formulaire dans les outils de développement (F12), j'ai remarqué que le formulaire était présenté dans une iframe. Donc, avant de pouvoir commencer à remplir le formulaire, je devais "basculer" vers l'iframe approprié où le formulaire existait. Pour ce faire, j'ai invoqué la fonctionnalité de basculement de Selenium, comme suit :

 # Switch to iframe where form is frame_ref = driver.find_elements_by_tag_name("iframe")[0] iframe = driver.switch_to.frame(frame_ref)

Bon, alors maintenant dans le bon cadre, j'ai pu déterminer les composants, remplir le champ ID client et sélectionner les listes déroulantes de date :

 # Find the Customer ID field and populate it element = driver.find_element_by_name("custId") element.send_keys(custId) # send a test id # Find and select the date drop-downs select = Select(driver.find_element_by_name("fromMonth")) select.select_by_visible_text(from_month) select = Select(driver.find_element_by_name("fromYear")) select.select_by_visible_text(from_year) select = Select(driver.find_element_by_name("toMonth")) select.select_by_visible_text(to_month) select = Select(driver.find_element_by_name("toYear")) select.select_by_visible_text(to_year)

Problème 3 – JavaScript

La seule chose qui restait sur le formulaire était de "cliquer" sur le bouton Rechercher pour lancer la recherche. C'était un peu délicat car le bouton Rechercher semblait être contrôlé par JavaScript et n'était pas un bouton de type "Soumettre" normal. En l'inspectant dans les outils de développement, j'ai trouvé l'image du bouton et j'ai pu en obtenir le XPath en cliquant avec le bouton droit de la souris.

Récupération de données

Puis, armé de cette information, j'ai trouvé l'élément sur la page, puis j'ai cliqué dessus.

 # Find the 'Find' button, then click it driver.find_element_by_xpath("/html/body/table/tbody/tr[2]/td[1]/table[3]/tbody/tr[2]/td[2]/input").click()

Et voilà, le formulaire a été soumis et les données sont apparues ! Maintenant, je pourrais simplement gratter toutes les données sur la page de résultats et les enregistrer au besoin. Ou pourrais-je?

Obtenir les données

D'abord, j'ai dû gérer le cas où la recherche n'a rien trouvé. C'était assez simple. Il afficherait un message sur le formulaire de recherche sans le quitter, quelque chose comme "Aucun enregistrement trouvé". J'ai simplement cherché cette chaîne et je me suis arrêté là si je l'ai trouvée.

Mais si les résultats arrivaient, les données étaient présentées en divs avec un signe plus (+) pour ouvrir une transaction et afficher tous ses détails. Une transaction ouverte affichait un signe moins (-) qui, une fois cliqué, fermait la div. Cliquer sur un signe plus appellerait une URL pour ouvrir sa div et fermer toute ouverture.

Récupération de données

Ainsi, il était nécessaire de trouver tous les signes plus sur la page, de rassembler l'URL à côté de chacun, puis de parcourir chacun d'eux pour obtenir toutes les données de chaque transaction.

 # Loop through transactions and count links = driver.find_elements_by_tag_name('a') link_urls = [link.get_attribute('href') for link in links] thisCount = 0 isFirst = 1 for url in link_urls: if (url.find("GetXas.do?processId") >= 0): # URL to link to transactions if isFirst == 1: # already expanded + isFirst = 0 else: driver.get(url) # collapsed +, so expand # Find closest element to URL element with correct class to get tran type tran_type=driver.find_element_by_xpath("//*[contains(@href,'/retail/transaction/results/GetXas.do?processId=-1')]/following::td[@class='txt_75b_lmnw_T1R10B1']").text # Get transaction status status = driver.find_element_by_class_name('txt_70b_lmnw_t1r10b1').text # Add to count if transaction found if (tran_type in ['Move In','Move Out','Switch']) and (status == "Complete"): thisCount += 1

Dans le code ci-dessus, les champs que j'ai récupérés étaient le type de transaction et le statut, puis ajoutés à un décompte pour déterminer le nombre de transactions conformes aux règles spécifiées. Cependant, j'aurais pu récupérer d'autres champs dans les détails de la transaction, comme la date et l'heure, le sous-type, etc.

Pour ce projet, le décompte a été renvoyé à une application appelante. Cependant, ces données et d'autres données récupérées auraient également pu être stockées dans un fichier plat ou une base de données.

Autres obstacles et solutions possibles

De nombreux autres obstacles peuvent être présentés lors du grattage de sites Web modernes avec votre propre instance de navigateur, mais la plupart peuvent être résolus. Voici quelques-uns:

  • Essayer de trouver quelque chose avant qu'il n'apparaisse

    En naviguant sur vous-même, à quelle fréquence constatez-vous que vous attendez qu'une page s'affiche, parfois pendant plusieurs secondes ? Eh bien, la même chose peut se produire lors de la navigation par programmation. Vous cherchez une classe ou un autre élément – ​​et il n'y est pas !

    Heureusement, Selenium a la capacité d'attendre jusqu'à ce qu'il voie un certain élément, et peut expirer si l'élément n'apparaît pas, comme ceci :

 element = WebDriverWait(driver, 10). until(EC.presence_of_element_located((By.ID, "theFirstLabel")))


  • Traverser un Captcha

    Certains sites utilisent Captcha ou similaire pour empêcher les robots indésirables (qu'ils pourraient vous considérer). Cela peut freiner le raclage Web et le ralentir considérablement.

Pour les invites simples (comme « qu'est-ce que 2 + 3 ? »), celles-ci peuvent généralement être lues et comprises facilement. Cependant, pour les barrières plus avancées, il existe des bibliothèques qui peuvent aider à essayer de le casser. Quelques exemples sont 2Captcha, Death by Captcha et Bypass Captcha.

  • Modifications structurelles du site Web

    Les sites Web sont censés changer - et ils le font souvent. C'est pourquoi, lors de l'écriture d'un script de scraping, il est préférable de garder cela à l'esprit. Vous devrez réfléchir aux méthodes que vous utiliserez pour trouver les données et à celles à ne pas utiliser. Envisagez des techniques de correspondance partielle, plutôt que d'essayer de faire correspondre une phrase entière. Par exemple, un site Web peut changer un message de "Aucun enregistrement trouvé" à "Aucun enregistrement localisé" - mais si votre correspondance est sur "Aucun enregistrement", tout devrait bien se passer. Considérez également s'il faut faire correspondre XPATH, ID, nom, texte de lien, nom de balise ou de classe, ou sélecteur CSS - et lequel est le moins susceptible de changer.

Résumé : Python et Selenium

Il s'agissait d'une brève démonstration pour montrer que presque tous les sites Web peuvent être supprimés, quelles que soient les technologies utilisées et les complexités impliquées. Fondamentalement, si vous pouvez parcourir le site vous-même, il peut généralement être gratté.

Maintenant, en guise de mise en garde, cela ne signifie pas que chaque site Web doit être gratté. Certains ont mis en place des restrictions légitimes et de nombreuses affaires judiciaires ont statué sur la légalité de la suppression de certains sites. D'un autre côté, certains sites accueillent et encouragent la récupération de données sur leur site Web et, dans certains cas, fournissent une API pour faciliter les choses.

Quoi qu'il en soit, il est préférable de vérifier les termes et conditions avant de commencer tout projet. Mais si vous allez de l'avant, soyez assuré que vous pouvez faire le travail.

Ressources recommandées pour le scraping Web complexe :

  • Scraping Web Python avancé : meilleures pratiques et solutions de contournement
  • Scraping évolutif à faire soi-même : comment construire et faire fonctionner des scrapers à grande échelle