使用 python/selenium 保存完整的网页(包括 css、图像)

Posted

技术标签:

【中文标题】使用 python/selenium 保存完整的网页(包括 css、图像)【英文标题】:Save complete web page (incl css, images) using python/selenium 【发布时间】:2019-05-12 17:48:53 【问题描述】:

我正在使用 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)

那时我有一个页面,我可以手动单击“另存为”并获取一个本地文件(带有相应的图像/js 资产文件夹),让我可以在本地查看整个返回的页面(减去内容通过向下滚动页面动态生成,这很好)。我认为有一种简单的方法可以在 python/selenium 中模拟这个“另存为”函数,但还没有找到。保存下面页面的代码只是保存了 html,并没有给我留下一个看起来像在 Web 浏览器中的本地文件、图像等。

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

我也找到了this question/answer on SO,但接受的答案只是打开了“另存为”框,并没有提供点击它的方法(正如两位评论者指出的那样)

有没有一种简单的方法可以使用 python 将[整页]另存为'?理想情况下,我更喜欢使用 selenium 的答案,因为 selenium 使爬行部分如此简单,但如果有更好的工具来完成这项工作,我愿意使用另一个库。或者我只需要在代码中指定我要下载的所有图像/表格,并且没有模拟右键单击“另存为”功能的快捷方式?

更新 - 詹姆斯回答的后续问题 因此,我运行了 James 的代码以生成 page.html(和相关文件)并将其与手动单击另存为获得的 html 文件进行比较。通过 James 的脚本保存的 page.html 非常棒,它拥有我需要的一切,但是在浏览器中打开时,它还会显示许多隐藏在手动保存页面中的额外格式文本。请参阅随附的屏幕截图(左侧是手动保存的页面,右侧显示的是脚本保存的页面以及额外的格式文本)。

这让我特别惊讶,因为 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>

知道为什么会这样吗?

【问题讨论】:

检查这个问题codereview.stackexchange.com/q/78775/179828 感谢这个 Moshe,尽管从描述中它保存“页面的 html 内容(没有 CSS)并且它还查找页面上的所有图像并保存它们”这不是我的样子寻找。此外,对我来说更重要的是,我想利用像 Selenium 或 Scrapy 这样的爬虫工具,因为我需要做一些爬虫才能到达我的结果页面,我不能只提供一个 URL 作为输入。我也更喜欢使用像 Selenium/Scrapy/bs4 这样的工具,而不是像这个答案那样使用正则表达式来解析 html。 您会考虑使用 Firefox 插件的有效解决方案吗?将this answer 翻译成 Python 看起来是一个可行的任务。 感谢托马斯的建议。尽管该答案指的是 firefox 插件的以下死链接 - addons.mozilla.org/de/firefox/addon/scrapbook。谷歌搜索,我找到addons.mozilla.org/en-US/firefox/addon/web-scrapbook,但它有以下粗体警告文本“此插件正在开发中。未来每个功能都可能发生变化。在生产中谨慎使用并确保经常备份。“所以我现在可能不会围绕这个进行构建,但我很感谢你指点我 我在单击另存为按钮时遇到了类似的问题。解决方案是使用 pywin32 单击另存为按钮。***.com/questions/1181464/… 【参考方案1】:

如您所述,Selenium 无法与浏览器的上下文菜单交互以使用 Save as...,因此您可以使用外部自动化库(如 pyautogui)来代替。

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

此代码通过其键盘快捷键CTRL+S 打开Save as... 窗口,然后按Enter 将网页及其资源保存到默认下载位置。此代码还将文件命名为序列,以便为其提供唯一名称,尽管您可以根据您的用例更改此名称。如果需要,您还可以使用选项卡和箭头键通过一些额外的工作来更改下载位置。

在 Ubuntu 18.10 上测试;根据您的操作系统,您可能需要修改发送的组合键。


完整代码,其中我还添加了条件等待以提高速度:

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')

【讨论】:

我认为对于自定义保存位置,我将只保存到默认位置,然后使用subprocess.call('mv [current-location] [new-location]'),而不是通过 gui 依赖预设的选项卡和箭头。 “另存为...”最初在 MacOS 上对我不起作用。但是将pyautogui.hotkey('command','s') 更改为pyautogui.keyDown('command') pyautogui.press('s') 解决了这个问题。效果很好!【参考方案2】:

这不是一个完美的解决方案,但它可以满足您的大部分需求。您可以通过解析 html 并将任何加载的文件(图像、css、js 等)下载到相同的相对路径来复制“另存为完整网页(完整)”的行为。

由于跨源请求阻塞,大多数 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 的文件,其中包含您所追求的内容。

【讨论】:

嘿,詹姆斯,谢谢,这真的很棒,虽然我很困惑为什么它还会显示一堆额外的文本,这些文本在手动保存页面时会被隐藏(请参阅我的问题更新中的更多详细信息在底部)。你明白为什么会这样吗? 这些元素通常被 javascript 隐藏。其中一个下载的 JS 库可能正在调用另一个未下载的库。【参考方案3】:

受上面 FThompson 回答的启发,我想出了以下工具,可以下载给定页面 url 的完整/完整 html(请参阅:https://github.com/markfront/SinglePageFullHtml)

更新 - 跟进 Max 的建议,以下是使用该工具的步骤:

    克隆项目,然后运行 ​​maven 构建:
$> git clone https://github.com/markfront/SinglePageFullHtml.git

$> cd ~/git/SinglePageFullHtml
$> mvn clean compile package

    在目标文件夹中找到生成的jar文件:SinglePageFullHtml-1.0-SNAPSHOT-jar-with-dependencies.jar

    在命令行中运行jar:

$> java -jar .target/SinglePageFullHtml-1.0-SNAPSHOT-jar-with-dependencies.jar <page_url>

    结果文件名将带有前缀“FP”,后跟页面 url 的哈希码,文件扩展名为“.html”。它将在任一文件夹“/tmp”中找到(您可以通过 System. getProperty("java.io.tmp")。如果没有,请尝试在您的主目录或 Java 中的 System.getProperty("user.home") 中找到它。

    结果文件将是一个巨大的自包含 html 文件,其中包含原始 html 源引用的所有内容(css、javascript、图像等)。

【讨论】:

嗨,马克,欢迎来到 Stack Overflow!似乎有人投了你的票,我刚刚投了你的票,因为我认为你构建了一个为此提供解决方案的东西真是太棒了。但总的来说,人们更喜欢这样的答案(主要是对链接的描述)作为评论。对于更多人会投赞成票而不是投反对票的答案,这里的人们更喜欢最小的工作代码示例。因此,我建议您将 github 自述文件中“使用”部分的代码/步骤复制到此处的答案中,或者删除此答案并将链接和说明留在评论中。【参考方案4】:

我建议你试试sikulix,这是一个基于图像的自动化工具,用于在 PC 操作系统中操作任何小部件,它支持 python 语法并使用命令行运行,也许是解决你的问题的最简单方法。 您需要做的只是给它一个屏幕截图,在您的 python 自动化脚本中调用 sikulix 脚本(使用 OS.system("xxxx") 或子进程...)。

【讨论】:

以上是关于使用 python/selenium 保存完整的网页(包括 css、图像)的主要内容,如果未能解决你的问题,请参考以下文章

自动点击下载链接后,使用 Selenium、Firefox、Python 将下载的 EPS 文件保存到磁盘

Python3+Selenium2完整的自动化测试实现之旅:自动化测试框架Python面向对象以及POM设计模型简介

python selenium的在线安装及离线安装

一次完整的自动化登录测试-基于python+selenium进行cnblog的自动化登录测试

一次完整的自动化登录测试-基于python+selenium进行cnblog的自动化登录测试

一次完整的自动化登录测试-基于python+selenium进行cnblog的自动化登录测试