遇見一篇有意思的文章,或者是在購物時發(fā)現(xiàn)了好東西木人,需要分享信柿。
不想發(fā)送鏈接,也許搞一個長截圖是個很好的選擇醒第。
Selenium
Selenium是一種自動化測試工具渔嚷,它支持各種瀏覽器,包括 Chrome稠曼,Safari形病,F(xiàn)irefox 等主流界面式瀏覽器,如果你在這些瀏覽器里面安裝一個 Selenium 的插件霞幅,那么便可以方便地實(shí)現(xiàn)Web界面的測試漠吻。換句話說叫 Selenium 支持這些瀏覽器驅(qū)動。Selenium支持多種語言開發(fā)司恳,比如 Java途乃,C,Ruby, Python等等抵赢。
在這個案例中欺劳,我們使用基于Python3的運(yùn)行環(huán)境進(jìn)行演示。
使用pip3安裝selenium
$ pip3 install selenium
獲取chrome-driver
在淘寶npm鏡像中可以找到Chrome版本對應(yīng)的驅(qū)動: http://npm.taobao.org/mirrors/chromedriver/
初始化瀏覽器
def init_browser():
chrome_options = Options()
# --headless參數(shù)表示铅鲤,Chrome將不會有一個可視化的圖形界面
# chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--disable-web-security")
# 以iPhone 6的屏幕寬度作為基準(zhǔn)
mobile_emulation = { "deviceName": "iPhone 6" }
chrome_options.add_experimental_option("mobileEmulation", mobile_emulation)
return webdriver.Chrome("./chromedriver",
chrome_options=chrome_options)
browser = inti_browser()
調(diào)用Webdriver提供的API, 獲取網(wǎng)頁基本信息
url = "https://code.evink.me"
# browser是我們剛剛初始化的瀏覽器實(shí)例
# 設(shè)置頁面渲染超時
browser.set_page_load_timeout(30)
# 設(shè)置腳本執(zhí)行超時
browser.set_script_timeout(100)
# 獲取網(wǎng)頁資源
browser.get(url)
# 給瀏覽器設(shè)置一個默認(rèn)的初始寬高(非必要)
browser.set_window_size(375, 1000)
Chrome在iPhone 6的模擬環(huán)境下進(jìn)行網(wǎng)頁渲染划提,其寬是一定的,所以可以通過調(diào)用運(yùn)行于webdriver內(nèi)部的javascript代碼邢享,獲得我們想要的內(nèi)容鹏往。
webdrive提供了豐富的接口供我們?nèi)轿徊倏剡@個小小的瀏覽器。
# browser是我們剛剛初始化的瀏覽器實(shí)例
body_height = browser.body.get_attribute("clientHeight")
或者骇塘,這里還有一個更加可操作的方法伊履,直接在webdriver里跑js代碼。Selenium提供了execute_script(str)接口款违,可以讓用戶通過自己更為熟悉的方式唐瀑,得到自己想要的內(nèi)容。
# browser是我們剛剛初始化的瀏覽器實(shí)例
body_height = browser.execute_script(get_script("body_height")))
# get_script(str) -> str
def get_script(_type):
if _type == "body_height":
return """
// get body_height
return document.getElementsByTagName("body")[0].clientHeight;
"""
滾屏截圖
webdriver提供了一個保存當(dāng)前瀏覽器窗口截圖的接口save_screenshot(path)
我們只需要讓網(wǎng)頁沿著一個預(yù)設(shè)定的高度滾動就好插爹。
這里哄辣,這個高度是667(基于我們以iPhone 6的虛擬環(huán)境初始化的瀏覽器)。設(shè)定大于667的高度赠尾,每次也并不會截取到更多的頁面內(nèi)容力穗,而小于667的高度,會讓你最后進(jìn)行圖片處理的時候非常頭疼气嫁。
dir_path = "tmp_screenshots"
filename = "evink" + int(datetime.now().timestamp())
paging_list = []
def take_screenshot():
# loop_times由網(wǎng)頁高度和單屏高度計(jì)算而來
for i in range(loop_times):
browser.execute_script(get_script("scroll_window") % (i + 1))
# 截取屏幕
path = "%s/%s_%s.png"%(dir_path, filename, i)
browser.save_screenshot(path)
paging_list.append(path)
return paging_list
def get_script(_type):
if _type == "scroll_window":
return """
// 滾動的次數(shù)
var m = %s;
// 屏幕range
var begin = 0;
var end = 667;
// 滾屏
window.scrollTo(begin, end * m);
"""
if _type == "scroll_y":
return """
return window.scrollY;
"""
合成圖片
上一步結(jié)束之后当窗,我們在/tmp_screenshots目錄下會發(fā)現(xiàn)若干png格式的圖片,利用Python中的PIL Image庫寸宵,可以很方便的對圖片做處理崖面。
def paste_imgs(self):
# loop_times由網(wǎng)頁高度和單屏高度計(jì)算而來
for i in range(loop_times):
path = "%s/%s_%s.png"%(dir_path, filename, i)
if os.path.exists(path):
continue
else:
# 記錄存在的最大圖片張數(shù)
max_screen = i
# 根據(jù)總圖片張數(shù)計(jì)算合成的單張圖片高度
page_total_height = 667 * 2 * loop_times
# 聲明一張空白的圖片實(shí)例
image = Image.new(
'RGB', (375 * 2, page_total_height), (255, 255, 255))
for i in range(loop_times):
path = "%s/%s_%s.png"%(dir_path, filename, i)
from_img = Image.open(path)
# 粘貼圖片
image.paste(from_img, (0, 1334 * ( i + 1 )))
path = "%s/%s_part_%s.jpg"%(dir_path, filename, part+1)
# 保存圖片 以JPEG格式元咙,60質(zhì)量
image.save(path,format='JPEG', quality=60)
return "%s/%s.jpg"%(dir_path, filename)
處理細(xì)節(jié)
上一步結(jié)束之后,我們獲得了一張完整的圖片嘶朱,但是蛾坯,你一定會發(fā)現(xiàn)很多小細(xì)節(jié)沒有處理光酣。
圖片未被加載
如果你要截取的網(wǎng)頁采用了圖片懶加載模式(可以提升訪問速度)疏遏,你會發(fā)現(xiàn)所截取的網(wǎng)頁的圖片都被灰色的色塊所替代。
我們可以分析網(wǎng)頁中救军,未被加載的圖片是否有什么共同點(diǎn)财异。比如說,是否含有特定的class唱遭,是否有自定義的屬性值戳寸。
# 假設(shè)圖片未加載時,此網(wǎng)頁圖片的class會有 "img_loading"
# 獲取所有的圖片元素
imgs = browser.find_elements_by_tag_name("img")
img_load_start = datetime.now().timestamp()
while True:
# 已加載的圖片數(shù)
img_loadeds = 0
for img in imgs:
# 拿到class屬性
clz = img.get_attribute("class")
if clz.find("img_loading") == -1:
img_loadeds += 1
print("已經(jīng)加載完畢的圖像數(shù):%s"%img_loadeds)
if img_loadeds == len(wait_load_imgs):
print("all imgs loaded")
break
# 給他設(shè)置一個超時
if datetime.now().timestamp() - img_load_start > 60:
print("imgs loading timeout")
break
sleep(0.1)
圖片底部 顯示不完全 / 留有大量的空白 / 拼接不完美
造成這個原因拷泽,無非是高度和留余問題疫鹊。
網(wǎng)頁高度
某些網(wǎng)頁上,在滾屏完畢(即所有圖片都被加載后)的高度和網(wǎng)頁初始化后的高度并不一致司致。
高度的錯誤會導(dǎo)致生成圖片時拋出異常
# 媽媽讓我再滾一次
def scroll_window(need_renew_height=False):
for pre_scroll in range(loop_times + 1):
self.browser.execute_script(self.get_script("scroll_window")%pre_scroll)
sleep(0.5)
print("-- 已經(jīng)滾完 --")
if need_renew_height:
# 重新錄入高度
body = browser.find_element_by_id('activity-detail')
body_height = int(body.get_attribute("clientHeight"))
loop_times = math.ceil(body_height / height)
def get_script(self, _type):
if _type == "scroll_window":
return """
// 滾動的次數(shù)
var m = %s;
// 取出所有圖片
var imgs = document.querySelectorAll('img');
// 屏幕range
var begin = 0;
var end = 667;
// 滾屏
window.scrollTo(begin, end * m);
// 圖片的相對距離
for(var i = 0;i < imgs.length;i++){
var y = imgs[i].getBoundingClientRect()["y"];
if(y >= begin && y <= end){
imgs[i].setAttribute("type", "wait_load");
}
}
"""
處理好收尾工作
在生成滾屏?xí)r拆吆,最后一張圖片含有兩種狀態(tài)。
- 完美占據(jù)一屏的空間 (375 * 667)
- 只占據(jù)部分空間脂矫,底部含有留白
處理好最后一屏圖片和倒數(shù)第二屏的關(guān)系枣耀,就可以避免出現(xiàn)圖片拼接不完美的情況。
圖片 黑屏 / 損壞
圖片過長庭再,嘗試按照固定的屏幕數(shù)捞奕,將一張圖切成幾張小圖。
原文地址: https://code.evink.me/2018/07/post/python-use-chrome-to-make-a-long-page-screenshot/