預備
照張相片
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
參考
關於js中getBoundingClientRect()方法的說明,戳這里:
https://www.cnblogs.com/Songyc/p/4458570.html