使用Selenium Python下载图片

59

我想从浏览器获取验证码图片。我已经获得了该图片的URL,但是这张图片每次更新时都会更改(URL不变)。

是否有任何解决方案可以从浏览器中获取图片(例如“保存图像”按钮)?

另一方面,我认为以下方法应该有效:

  1. 获取浏览器的屏幕截图
  2. 获取图片的位置
  3. 使用OpenCV从屏幕截图中裁剪出验证码

动态验证码链接-链接

问题已通过截屏解决:

browser.save_screenshot('screenshot.png')
img = browser.find_element_by_xpath('//*[@id="cryptogram"]')
loc = img.location

image = cv.LoadImage('screenshot.png', True)
out = cv.CreateImage((150,60), image.depth, 3)
cv.SetImageROI(image, (loc['x'],loc['y'],150,60))
cv.Resize(image, out)
cv.SaveImage('out.jpg', out)

感谢


我需要从页面中获取验证码图片。快照是一种获取它的方法。 - user1941407
1
@erm3nda,显然你不知道如何读取逗号后面的内容或理解日期。 - brizz
我不明白的是,为什么要在这里写“嘿,我不知道X”,而不是直接搜索它。你这样做的真正收益是什么?请看我的下一条评论,告诉我你的想法。 - m3nda
"find_element_by_xpath" 是什么?没关系,看一下:find_element_by_xpath。 - m3nda
假设“cv”逗号后面的内容是用户在创建OpenCV实例时使用的单词。同样的情况也适用于创建新的Selenium webdriver对象。 - m3nda
显示剩余3条评论
9个回答

69

这里是一个完整的例子(使用谷歌的reCAPTCHA作为目标):

import urllib
from selenium import webdriver

driver = webdriver.Firefox()
driver.get('http://www.google.com/recaptcha/demo/recaptcha')

# get the image source
img = driver.find_element_by_xpath('//div[@id="recaptcha_image"]/img')
src = img.get_attribute('src')

# download the image
urllib.urlretrieve(src, "captcha.png")

driver.close()

更新:

动态生成的图像问题在于每次请求时都会生成新的图像。在这种情况下,您有几个选项:

  • 截屏

 from selenium import webdriver

 driver = webdriver.Firefox()
 driver.get('https://moscowsg.megafon.ru/ps/scc/php/cryptographp.php?PHPSESSID=mfc540jkbeme81qjvh5t0v0bnjdr7oc6&ref=114&w=150')

 driver.save_screenshot("screenshot.png")

 driver.close()
  • 模拟右键点击+“另存为”。有关更多信息,请参见此线程


  • 1
    Google reCAPTCHA的URL是静态的。链接 - user1941407
    1
    验证码链接 - link - user1941407
    15
    在过去几年中,urllib发生了变化。现在需要使用urllib.request.urlretrieve代替之前的urllib.urlretrieve。请注意,这里只是翻译,不包含任何解释或其他内容。 - zwep
    5
    Selenium本身能否在自动化浏览器的同一会话中下载图像? - Rahul Bali
    5
    如果访问图像需要身份验证,则此方法将 无法 生效。 - ccpizza
    显示剩余5条评论

    33

    可以保存整个页面的截图然后从中裁剪图像,但是您还可以使用“webdriver”中的“find”方法来定位要保存的图像,并像下面这样编写“screenshot_as_png”属性:

    from selenium import webdriver
    driver = webdriver.Firefox()
    driver.get('https://www.webpagetest.org/')
    with open('filename.png', 'wb') as file:
        file.write(driver.find_element_by_xpath('/html/body/div[1]/div[5]/div[2]/table[1]/tbody/tr/td[1]/a/div').screenshot_as_png)
    
    有时由于滚动条的原因可能会出现错误,但根据您的需求,这是获取图像的好方法。

    @ Ramon,这个不行,我正在尝试从这个页面获取你的个人资料图片,并且出现了错误selenium.common.exceptions.WebDriverException: Message: unknown command: session/5734e4b0f8d6171317af42ddf0979562/element/0.9586500909174849-1/screenshot - Jortega
    那么,@Jortega。试着把你的代码发到这里,我可以检查看看是否有相同的错误。 - Ramon
    但是如果这个图片是一个gif文件呢?有没有办法下载那个gif文件? - oeter

    6
    使用save_screenshot的问题在于我们不能保存图像的原始质量,并且无法还原图像中的 alpha 通道。因此,我提出了另一种解决方案。以下是一个完整的示例,使用了 @codam_hsmits 建议的 selenium-wire 库。可以通过ChromeDriver下载图像。
    我定义了以下函数来解析每个请求,并在必要时将请求正文保存到文件中。
    from seleniumwire import webdriver  # Import from seleniumwire
    from urllib.parse import urlparse
    import os
    from mimetypes import guess_extension
    import time
    import datetime
    
    def download_assets(requests,
                       asset_dir="temp",
                       default_fname="unnamed",
                       skip_domains=["facebook", "google", "yahoo", "agkn", "2mdn"],
                       exts=[".png", ".jpeg", ".jpg", ".svg", ".gif", ".pdf", ".bmp", ".webp", ".ico"],
                       append_ext=False):
        asset_list = {}
        for req_idx, request in enumerate(requests):
            # request.headers
            # request.response.body is the raw response body in bytes
            if request is None or request.response is None or request.response.headers is None or 'Content-Type' not in request.response.headers:
                continue
                
            ext = guess_extension(request.response.headers['Content-Type'].split(';')[0].strip())
            if ext is None or ext == "" or ext not in exts:
                #Don't know the file extention, or not in the whitelist
                continue
            parsed_url = urlparse(request.url)
            
            skip = False
            for d in skip_domains:
                if d in parsed_url.netloc:
                    skip = True
                    break
            if skip:
                continue
            
            frelpath = parsed_url.path.strip()
            if frelpath == "":
                timestamp = str(datetime.datetime.now().replace(microsecond=0).isoformat())
                frelpath = f"{default_fname}_{req_idx}_{timestamp}{ext}"
            elif frelpath.endswith("\\") or frelpath.endswith("/"):
                timestamp = str(datetime.datetime.now().replace(microsecond=0).isoformat())
                frelpath = frelpath + f"{default_fname}_{req_idx}_{timestamp}{ext}"
            elif append_ext and not frelpath.endswith(ext):
                frelpath = frelpath + f"_{default_fname}{ext}" #Missing file extension but may not be a problem
            if frelpath.startswith("\\") or frelpath.startswith("/"):
                frelpath = frelpath[1:]
            
            fpath = os.path.join(asset_dir, parsed_url.netloc, frelpath)
            if os.path.isfile(fpath):
                continue
            os.makedirs(os.path.dirname(fpath), exist_ok=True)
            print(f"Downloading {request.url} to {fpath}")
            asset_list[fpath] = request.url
            try:
                with open(fpath, "wb") as file:
                    file.write(request.response.body)
            except:
                print(f"Cannot download {request.url} to {fpath}")
        return asset_list
    

    让我们从谷歌主页下载一些图片到temp文件夹中。

    # Create a new instance of the Chrome/Firefox driver
    driver = webdriver.Chrome()
    
    # Go to the Google home page
    driver.get('https://www.google.com')
    
    # Download content to temp folder
    asset_dir = "temp"
    
    while True:
        # Please browser the internet, it will collect the images for every second
        time.sleep(1)
        download_assets(driver.requests, asset_dir=asset_dir)
    
    driver.close()
    

    请注意,它无法决定哪些图片可以在页面上显示,而不是隐藏在背景中,因此用户应主动单击按钮或链接以触发新的下载请求。

    3

    为了保持与时俱进,这里介绍一个2020年的解决方案,使用seleniumwire库来获取浏览器发出的请求。您可以按照以下方式轻松使用:

    from seleniumwire import webdriver
    
    # Sometimes, selenium randomly crashed when using seleniumwire, these options fixed that.
    # Probably has to do with how it proxies everything.
    options.add_argument('--ignore-certificate-errors')
    options.add_argument('--ignore-ssl-errors')
    
    driver = webdriver.Chrome(chrome_options=options)
    driver.get("https://google.com")
    
    for request in driver.requests:
        # request.path
        # request.method
        # request.headers
        # request.response is the response instance
        # request.response.body is the raw response body in bytes
    
        # if you are using it for a ton of requests, make sure to clear them:
        del driver.requests
    

    现在,为什么需要这个?比如说用于ReCaptcha绕过或绕过像Incapsula这样的东西。请自行决定使用风险。


    2

    您可以使用JS下载图像而不会失去质量:

    from io import BytesIO
    from PIL import Image
    from base64 import b64decode
    
    driver.get(url)
    
    # Create a canvas, set it's width and height equal to image's
    # Write image to canvas, translate to base64
    # Remove metadata prefix
    b64img = driver.execute_script(r'''
    var img = document.getElementsByTagName("img")[0];
    var canvas = document.createElement("canvas");
    canvas.width = img.width;
    canvas.height = img.height;
    var ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0);
    var dataURL = canvas.toDataURL("image/png");
    return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
    ''')
    
    # Decode from base64, translate to bytes and write to PIL image
    img = Image.open(BytesIO(b64decode(b64img)))
    

    这是一个有效的解决方案,但在大多数情况下会失败:它会抛出以下错误 https://dev59.com/pWEh5IYBdhLWcg3wMA0t - titusfx

    1
    请使用以下代码下载图片。
    from selenium import webdriver
    #set chromedriver.exe path
    driver = webdriver.Chrome()
    driver.implicitly_wait(0.5)
    #maximize browser
    driver.maximize_window()
    #launch URL
    driver.get("name of webpage from where you want to download image");
    #open file in write and binary mode
    with open('Logo.png', 'wb') as file:
    #identify image to be captured
       l = driver.find_element_by_xpath('//*[@alt="name in the alt of image"]')
    #write file
       file.write(l.screenshot_as_png)
    #close browser
    driver.quit()
    

    不错的技巧,但它并不真正地“下载”图像,而是将其截屏为PNG格式。这可能会导致质量损失和/或文件体积变大。 - undefined

    0

    如果您需要授权才能下载图像,我发现最好的解决方案是结合selenium和selenium-requests

    1. 使用selenium登录网站
    2. 解析网站以获取图像URL
    3. 使用类似以下代码的selenium-requests获取图像
    response = driver.request("GET", image_url, stream=True)
    response.raise_for_status()
    with open(path, 'wb') as f:
        response.raw.decode_content = True
        shutil.copyfileobj(response.raw, f) 
    

    根据使用requests下载图像的方法

    0

    请参考'disable_encoding': True

    from seleniumwire import webdriver
    
    seleniumwire_options = {
        "disable_encoding": True,
    }
    
    driver = webdriver.Chrome(options=options, seleniumwire_options=seleniumwire_options)
    driver.get(url) # direct url to image
    
    request = driver.last_request
    
    file = url.split("/")[-1]
    with open(file, "wb") as f:
        f.write(request.response.body)
    

    -1

    这里是。

    • 使用Selenium WebDriver打开图像
    • 使用BeautifulSoup提取图像的宽度和高度
    • 使用driver.set_window_size设置正确的当前窗口大小,并使用driver.save_screenshot截屏
    from bs4 import BeautifulSoup
    from selenium import webdriver
     
    import os
    from urllib.parse import urlparse
     
    url = 'https://image.rakuten.co.jp/azu-kobe/cabinet/hair1/hb-30-pp1.jpg'
     
    filename = os.path.basename(urlparse(url).path)
    filename_png = os.path.splitext(filename)[0] + '.png'  # change file extension to .png
     
    opts = webdriver.ChromeOptions()
    opts.headless = True
    driver = webdriver.Chrome(options=opts)
     
    driver.get(url)
     
    # Get the width and height of the image
    soup = BeautifulSoup(driver.page_source, 'lxml')
    width = soup.find('img')['width']
    height = soup.find('img')['height']
     
    driver.set_window_size(width, height) # driver.set_window_size(int(width), int(height))
    driver.save_screenshot(filename_png)
    

    这同样适用于Google的图片格式WebP。

    请参考使用Selenium WebDriver通过屏幕截图下载Google的WebP图片


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接