web-dev-qa-db-ja.com

python / seleniumを使用して完全なWebページ(CSS、画像を含む)を保存する

Python/Seleniumを使用して遺伝子配列をオンラインデータベースに送信し、返された結果の全ページを保存したい。以下は、私が望む結果に導くコードです。

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)

その時点で、[名前を付けて保存]を手動でクリックして、返されたページ全体をローカルで表示できるローカルファイル(image/jsアセットの対応するフォルダー)を取得できるページがありますページを下にスクロールします。これは問題ありません)。 python/Seleniumでこの「名前を付けて保存」機能を模倣する簡単な方法があると思いましたが、見つかりませんでした。以下のページを保存するコードはhtmlを保存するだけで、Webブラウザや画像などのように見えるローカルファイルを残しません。

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

SOに関するこの質問/回答 も見つけましたが、受け入れられた回答は「名前を付けて保存」ボックスを表示し、クリックする方法を提供しません(2人のコメント者が指摘しているように)

Pythonを使用して「[フルページ]として保存」する簡単な方法はありますか? Seleniumはクロール部分を非常に単純にするため、Seleniumを使用した回答が望ましいのですが、このジョブに適したツールがあれば、別のライブラリを使用することもできます。または、コードでダウンロードするすべての画像/テーブルを指定するだけで、右クリックの「名前を付けて保存」機能をエミュレートするショートカットはありませんか?

更新-ジェームズの答えのフォローアップの質問だから、ジェームズのコードを実行してpage.html(および関連ファイル)と、手動で[名前を付けて保存]をクリックして取得したHTMLファイルと比較しました。 page.html Jamesのスクリプトを介して保存されたものは素晴らしく、必要なものはすべて揃っていますが、ブラウザーで開くと、手動で保存されたページに隠されている多くの余分な書式設定テキストも表示されます。添付のスクリーンショットを参照してください(左側に手動で保存されたページ、右側に追加の書式設定テキストが表示されたスクリプト保存ページ)。 enter image description here

Jamesのスクリプトによって保存されたページの生のhtmlは、これらのフィールドがまだ非表示であることを示しているように見えるので、これは特に驚きです。例参照下のhtmlは両方のファイルで同じように表示されますが、問題のテキストは、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>

なぜこれが起こっているのでしょうか?

16
Max Power

ご指摘のとおり、Seleniumはブラウザのコンテキストメニューと対話してSave as...、そうする代わりに、 pyautogui のような外部自動化ライブラリを使用できます。

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

このコードは、Save as...キーボードショートカットのウィンドウCTRL+Sそして、Enterキーを押して、Webページとそのアセットをデフォルトのダウンロード場所に保存します。また、このコードは、ファイルに一意の名前を付けるためにシーケンスとしてファイルに名前を付けますが、使用例に応じてこれを変更することもできます。必要に応じて、タブと矢印キーを使用して追加の作業を行うことで、ダウンロード場所を追加で変更できます。

Ubuntu 18.10でテスト済み。 OSによっては、送信されるキーの組み合わせを変更する必要がある場合があります。


完全なコード。速度を改善するために条件付き待機も追加しました。

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

これは完璧なソリューションではありませんが、必要なもののほとんどを取得できます。 HTMLを解析し、ロードされたファイル(画像、CSS、JSなど)を同じ相対パスにダウンロードすることで、「完全なWebページとして保存(完全)」の動作を再現できます。

ほとんどのjavascriptは、クロスオリジンリクエストブロックのために機能しません。しかし、コンテンツは(ほとんど)同じように見えます。

これは、requestsを使用してロードされたファイルを保存し、lxmlを使用してhtmlを解析し、osを使用してパスレッグワークを保存します。

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)  

pageというフォルダーにpage.htmlというファイルがあり、その中に必要なコンテンツが含まれている必要があります。

4
James