利用PIL和Selenium實現頁面元素截圖


預備

照張相片

selenium.webdriver可以實現對顯示頁面的截圖:

from selenium import webdriver

dr = webdriver.Firefox()
dr.get('http://store.steampowered.com/')
dr.save_screenshot('D:\\page.png')

瀏覽器界面

實際瀏覽器界面和截圖結果

可以發現截圖結果是瀏覽器內當前的顯示內容。

讓我想想...那只要讓需要截圖的元素出現在當前頁面上,再從得到的截圖里再把要的元素截取出來不就好啦?

那問題是怎么才能讓當前元素先讓我們看見呢?

讓提線木偶動起來

在js中,頁面可以滾動到特定元素:

item_obj = document.getElementsByClassName('store_capsule_frame');
item = item_obj[0];
item.style.border = '2px solid red';
item.scrollIntoView();

在瀏覽器的控制台執行上述代碼

可以看到頁面會滾動到鑒賞家推薦這一欄,並且我給該元素加了個紅線條的邊框。邊框內的內容就是我們要截圖保存的元素。

scrollIntoView方法的參數是一個布爾值,默認為True,指的是否與頂部對齊。true則頁面從元素頂部開始,false則頁面會與底部對齊。

在selenium中,webdriver有execute_script的方法

可以向webdriver中直接注入任何js代碼,操作頁面,並且可以傳入參數。

我們可以把上面的js代碼注入到webdriver中,實現同樣的效果。(這里稍微改動一下,用webdriver來獲取元素)

item = dr.find_element('class name', 'store_capsule_frame')
dr.execute_script('arguments[0].scrollIntoView();', item)

js代碼中的arguments是execute_script方法后面的參數集合,這里我們將定位的元素傳入,效果如上圖。

現在可以讓想要的元素顯示出來啦,自然也可以截出一張對應的頁面圖片啦。

那么又該如何定位元素在圖片中的位置並裁剪出來呢?

來切相片吧

先畫好位置

webdriver獲取的頁面元素封裝了對裁剪很有用的信息。這里會用到它的locaion屬性。

item.location  # {'x': 247, 'y': 1959}
item.size # {'width': , 'height': }

可以看到元素的location保存在一個字典中,分別是該元素相對整個html的x軸和y軸距離。size屬性亦然。

因為截圖時會以元素頂部為起始線,元素左上頂點在截圖中的位置就是(item.location['x'],0),右下頂點位置就是(item.locaion['x'] + item.size['width'], item.size['height'])

學會剪裁

PIL是python中的圖像處理庫,pillow是其的一個分支,其核心仍然是PIL,但更易用。用pip即可安裝。

來剪切剛剛保存的圖片:

from PIL import Image
img = Image.open("D:\\page.png")
crop_size = (0,0,300,300)
cropped = img.crop(crop_size)
cropped.save('D:\\cropped.png')

先將圖片讀入到內存,存儲到一個Image對象中。

crop方法返回一個長方形的Image對象,需要傳入要剪切的尺寸,類型為一個元組。四個值分別是裁剪出的圖片距離原圖片左邊、上邊、右邊、下邊的距離,說簡單點,其實就是裁剪結果左上頂點的x,y坐標以及右下定點的x,y坐標(以原圖的左上頂點為原點)。

上面的操作會從原圖片的左上角裁剪出一個300*300的正方形。

初步代碼

def get_element_screenshot(driver, by, value, path):
from selenium import webdriver
from PIL import Image ele = driver.find_element(by, value) driver.execute_script('arguments[0].scrollIntoView();', ele) if driver.save_screenshot(path): img = Image.open(path+'_Page.png') size = (ele.location['x'],0,ele.location['x']+ele.size['width'],ele.size['height']) cropped = img.crop(size) cropped.save(path+'Cropped.png') del img, cropped print('Image is cropped successfully.') return True else: print('Image not saved.') return False

 跌落的坑

我在截圖過程中,發現按照頁面元素的實際大小去截取而得的圖片總是要小很多。原先以為是截取時選擇的高和寬不對,畢竟js中height值根據padding和boder是否包含分為了clientHeight、offsetHeight等值。但查找多時,仍是沒有結果,無論怎樣改變,都無法准確截取。去瀏覽器的控制台中選中元素查看,發現元素的高寬和實際的像素值差了一些,實際值是其1.5倍。突然想到是否和分辨率有關,就去看了一下,是1920 * 1080。在控制台中用window.screen.availHeight獲得可用高度,剛好是1.5倍。我以為症結就是這個,就搜索了一下,准備用win32api模塊中的GetSystemMetrics方法獲得屏幕分辨率,在除以可用高/寬度,拿到一個比例。但是執行該方法查詢后發現結果是1280和720。我覺得很奇怪,不是1920 * 1080嗎?又去顯示設置去瞧了一下,才忽然知道應該是這縮放設置惹的禍,我將縮放設置為了150%。調為100%后,再去截取圖片,便沒有任何問題了。

費時不少,雖然錯誤很小,但中間也復習了height的相關知識,也學習了些win32模塊的一些小知識點,算是繞路式學習。

是以為記。

 尚未解決的問題

scrollIntoView方法執行后,已經到頁面底部,但是元素頂部並未與頁面頂部對齊,這時的元素定位方法就要隨之改變,但還未寫,留待之后改進。

最終代碼

 

def get_screenshot(driver, ele, path):
    """
    :param driver: selenium webdriver object
    :param by: method to find element
    :param value: element value
    :param path: path to save picture
    :return:
    """
    from PIL import Image

    # Decide whether a scroll bar exists
    js_ret = driver.execute_script('''
        var bounding_top = arguments[0].getBoundingClientRect();
        if(document.documentElement.scrollHeight > document.documentElement.clientHeight){
            arguments[0].scrollIntoView();  
            var rect_obj = arguments[0].getBoundingClientRect();
            if(rect_obj.top == 0){
                return 'scroll-and-on-the-top';
            }else{
                var size_array = new Array(4);
                size_array[0] = rect_obj.x;
                size_array[1] = rect_obj.y;
                size_array[2] = rect_obj.right;
                size_array[3] = rect_obj.bottom;
                return size_array;
            }
        }else{
            return 'no-scroll';
        }
    ''', ele)
    driver.execute_script('arguments[0].scrollIntoView();', ele)
    if driver.save_screenshot(path):
        img = Image.open(path)
        _x = ele.location['x']
        _y = ele.location['y']
        _h = ele.size['height']
        _w = ele.size['width']
        if js_ret == 'scroll-and-on-the-top':
            size = (_x, 0, _x + _w, _h)
        elif js_ret == 'no-scroll':
            size = (_x, _y, _x + _w, _y + _h)
        else:
            print(js_ret)
            size = tuple(js_ret)
        cropped = img.crop(size)
        cropped.save(path)
        del img, cropped
        print('Image is cropped successfully and saved to %s.' % path)
        return True
    else:
        print('Image not saved due to invalid file path. Screen shot failed.')
        return False
View Code

 

 

參考

關於js中getBoundingClientRect()方法的說明,戳這里:

https://www.cnblogs.com/Songyc/p/4458570.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM