web-dev-qa-db-fra.com

Enregistrer la page Web complète (y compris css, images) en utilisant python / sélénium

J'utilise Python/Selenium pour soumettre des séquences génétiques à une base de données en ligne et je souhaite enregistrer la page complète des résultats que je récupère. Voici le code qui m'amène aux résultats que je veux:

from Selenium import webdriver

URL = 'https://blast.ncbi.nlm.nih.gov/Blast.cgi?PROGRAM=blastx&PAGE_TYPE=BlastSearch&LINK_LOC=blasthome'
SEQUENCE = 'CCTAAACTATAGAAGGACAGCTCAAACACAAAGTTACCTAAACTATAGAAGGACAGCTCAAACACAAAGTTACCTAAACTATAGAAGGACAGCTCAAACACAAAGTTACCTAAACTATAGAAGGACAGCTCAAACACAAAGTTACCTAAACTATAGAAGGACA' #'GAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGA'
CHROME_WEBDRIVER_LOCATION = '/home/max/Downloads/chromedriver' # update this for your machine

# open page with Selenium
# (first need to download Chrome webdriver, or a firefox webdriver, etc)
driver = webdriver.Chrome(executable_path=CHROME_WEBDRIVER_LOCATION)
driver.get(URL)
time.sleep(5)

# enter sequence into the query field and hit 'blast' button to search
seq_query_field = driver.find_element_by_id("seq")
seq_query_field.send_keys(SEQUENCE)

blast_button = driver.find_element_by_id("b1")
blast_button.click()
time.sleep(60)

À ce stade, j'ai une page sur laquelle je peux cliquer manuellement sur "enregistrer sous" et obtenir un fichier local (avec un dossier correspondant d'actifs image/js) qui me permet de visualiser la page renvoyée en entier localement (moins le contenu qui est généré dynamiquement à partir de faire défiler la page, ce qui est bien). J'ai supposé qu'il y aurait un moyen simple d'imiter cette fonction "enregistrer sous" en python/Selenium, mais je n'en ai pas trouvé. Le code pour enregistrer la page ci-dessous enregistre simplement du HTML, et ne me laisse pas avec un fichier local qui ressemble à celui du navigateur Web, avec des images, etc.

content = driver.page_source
with open('webpage.html', 'w') as f:
    f.write(content)

J'ai également trouvé cette question/réponse sur SO , mais la réponse acceptée fait simplement apparaître la boîte "Enregistrer sous" et ne fournit pas un moyen de cliquer dessus (comme le soulignent deux commentateurs)

Existe-t-il un moyen simple de "sauvegarder [la page entière] sous" en utilisant python? Idéalement, je préférerais une réponse en utilisant Selenium car Selenium rend la partie d'exploration si simple, mais je suis ouvert à l'utilisation d'une autre bibliothèque s'il existe un meilleur outil pour ce travail. Ou peut-être ai-je juste besoin de spécifier toutes les images/tableaux que je veux télécharger dans le code, et il n'y a pas de raccourci pour émuler la fonctionnalité "Enregistrer sous" du clic droit?

MISE À JOUR - Question de suivi pour la réponse de James J'ai donc exécuté le code de James pour générer un page.html (et les fichiers associés) et je l'ai comparé au fichier html que j'ai obtenu en cliquant manuellement sur Enregistrer sous. Le page.html enregistré via le script de James est génial et a tout ce dont j'ai besoin, mais lorsqu'il est ouvert dans un navigateur, il affiche également beaucoup de texte de mise en forme supplémentaire qui est masqué dans la page enregistrée manuellement. Voir la capture d'écran ci-jointe (page enregistrée manuellement à gauche, page enregistrée avec un script avec un texte de mise en forme supplémentaire affiché à droite). enter image description here

Ceci est particulièrement surprenant pour moi car le code HTML brut de la page enregistrée par le script de James semble indiquer que ces champs doivent toujours être masqués. Voir par exemple le html ci-dessous, qui apparaît de la même manière dans les deux fichiers, mais le texte en question n'apparaît que dans la page rendue par le navigateur sur celle enregistrée par le script de James:

<p class="helpbox ui-ncbitoggler-slave ui-ncbitoggler" id="hlp1" aria-hidden="true">
These options control formatting of alignments in results pages. The
default is HTML, but other formats (including plain text) are available.
PSSM and PssmWithParameters are representations of Position Specific Scoring Matrices and are only available for PSI-BLAST. 
The Advanced view option allows the database descriptions to be sorted by various indices in a table.
</p>

Une idée pourquoi cela se produit?

16
Max Power

Comme vous l'avez noté, Selenium ne peut pas interagir avec le menu contextuel du navigateur pour utiliser Save as..., donc à la place, vous pouvez utiliser une bibliothèque d'automatisation externe comme pyautogui .

pyautogui.hotkey('ctrl', 's')
time.sleep(1)
pyautogui.typewrite(SEQUENCE + '.html')
pyautogui.hotkey('enter')

Ce code ouvre le Save as... fenêtre à travers son raccourci clavier CTRL+S, puis enregistre la page Web et ses ressources dans l'emplacement de téléchargement par défaut en appuyant sur Entrée. Ce code nomme également le fichier comme séquence afin de lui donner un nom unique, bien que vous puissiez le changer pour votre cas d'utilisation. Si nécessaire, vous pouvez également modifier l'emplacement de téléchargement grâce à un travail supplémentaire avec les touches de tabulation et de flèche.

Testé sur Ubuntu 18.10; selon votre système d'exploitation, vous devrez peut-être modifier la combinaison de touches envoyée.


Code complet, dans lequel j'ai également ajouté des attentes conditionnelles pour améliorer la vitesse:

import time
from Selenium import webdriver
from Selenium.webdriver.common.by import By
from Selenium.webdriver.support.expected_conditions import visibility_of_element_located
from Selenium.webdriver.support.ui import WebDriverWait
import pyautogui

URL = 'https://blast.ncbi.nlm.nih.gov/Blast.cgi?PROGRAM=blastx&PAGE_TYPE=BlastSearch&LINK_LOC=blasthome'
SEQUENCE = 'CCTAAACTATAGAAGGACAGCTCAAACACAAAGTTACCTAAACTATAGAAGGACAGCTCAAACACAAAGTTACCTAAACTATAGAAGGACAGCTCAAACACAAAGTTACCTAAACTATAGAAGGACAGCTCAAACACAAAGTTACCTAAACTATAGAAGGACA' #'GAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGAGAAGA'

# open page with Selenium
# (first need to download Chrome webdriver, or a firefox webdriver, etc)
driver = webdriver.Chrome()
driver.get(URL)

# enter sequence into the query field and hit 'blast' button to search
seq_query_field = driver.find_element_by_id("seq")
seq_query_field.send_keys(SEQUENCE)

blast_button = driver.find_element_by_id("b1")
blast_button.click()

# wait until results are loaded
WebDriverWait(driver, 60).until(visibility_of_element_located((By.ID, 'grView')))

# open 'Save as...' to save html and assets
pyautogui.hotkey('ctrl', 's')
time.sleep(1)
pyautogui.typewrite(SEQUENCE + '.html')
pyautogui.hotkey('enter')
4
FThompson

Ce n'est pas une solution parfaite, mais vous obtiendrez la plupart de ce dont vous avez besoin. Vous pouvez reproduire le comportement de "enregistrer en tant que page Web complète (complète)" en analysant le code HTML et en téléchargeant tous les fichiers chargés (images, css, js, etc.) vers leur même chemin d'accès relatif.

La plupart des javascript ne fonctionneront pas en raison du blocage croisé des demandes d'origine. Mais le contenu sera (principalement) le même.

Cela utilise requests pour enregistrer les fichiers chargés, lxml pour analyser le code html et os pour le cheminement du chemin.

from Selenium import webdriver
import chromedriver_binary
from lxml import html
import requests
import os

driver = webdriver.Chrome()
URL = 'https://blast.ncbi.nlm.nih.gov/Blast.cgi?PROGRAM=blastx&PAGE_TYPE=BlastSearch&LINK_LOC=blasthome'
SEQUENCE = 'CCTAAACTATAGAAGGACAGCTCAAACACAAAGTTACCTAAACTATAGAAGGACAGCTCAAACACAAAGTTACCTAAACTATAGAAGGACAGCTCAAACACAAAGTTACCTAAACTATAGAAGGACAGCTCAAACACAAAGTTACCTAAACTATAGAAGGACA' 
base = 'https://blast.ncbi.nlm.nih.gov/'

driver.get(URL)
seq_query_field = driver.find_element_by_id("seq")
seq_query_field.send_keys(SEQUENCE)
blast_button = driver.find_element_by_id("b1")
blast_button.click()

content = driver.page_source
# write the page content
os.mkdir('page')
with open('page/page.html', 'w') as fp:
    fp.write(content)

# download the referenced files to the same path as in the html
sess = requests.Session()
sess.get(base)            # sets cookies

# parse html
h = html.fromstring(content)
# get css/js files loaded in the head
for hr in h.xpath('head//@href'):
    if not hr.startswith('http'):
        local_path = 'page/' + hr
        hr = base + hr
    res = sess.get(hr)
    if not os.path.exists(os.path.dirname(local_path)):
        os.makedirs(os.path.dirname(local_path))
    with open(local_path, 'wb') as fp:
        fp.write(res.content)

# get image/js files from the body.  skip anything loaded from outside sources
for src in h.xpath('//@src'):
    if not src or src.startswith('http'):
        continue
    local_path = 'page/' + src
    print(local_path)
    src = base + src
    res = sess.get(hr)
    if not os.path.exists(os.path.dirname(local_path)):
        os.makedirs(os.path.dirname(local_path))
    with open(local_path, 'wb') as fp:
        fp.write(res.content)  

Vous devriez avoir un dossier appelé page avec un fichier appelé page.html avec le contenu que vous recherchez.

4
James