赞
踩
首先要解释一下 Headless Chrome,通俗的讲就是运行一个没有GUI的Chrome,在 Headless Chrome 出现以前有 PhantomJS ,但是自从 Headless Chrome 出现之后 PhantomJS 活跃度下降,所以维护者就宣布 了停止继续开发。那么 Headless Chrome 能够什么呢?自动化测试,网页截图,网络调试,爬虫等等任务。Google 说在可预见的未来会一直维护。
关于 Headless Chrome 官方有两篇教程
在 Headless Chrome 之前使用 Python 可以结合Chrome推出的 chromedriver 来操作 PhantomJS,那么现在有了Chrome的Headless模式怎么来控制Chrome就是这篇文章要讲的内容。
Chrome DevTools Protocol
在此之前几个重要的网址:
Google官方维护了一份协议叫做 Chrome DevTools Protocol,只要是实现了这一份协议就能够编程来控制Chrome,Chrome 自带的开发者工具其实也是这一份协议的实现。
Browser 实例可以拥有多个 Page
Page 拥有至少一个 frame: main frame, 其他的 frame 可能被网页标记 iframe 和 frame 创建
Frame 拥有一个可执行的上下文,在该上下文中 JavaScript 可以被执行,Frame 中也可能有浏览器扩展 extensions 产生的内容被执行
可以用这张图 来显示其结构。
常用API
Browser 浏览器版本,关闭,命令行等等管理操作
Page 该域包含和页面相关接口,页面加载,资源内容,截图,打印等
Network 网络请求,Cookie,缓存,证书等内容
DOM 文档DOM的获取,修改,删除等
Runtime 该域下暴露 JavaScript 相关运行时接口,可以用来执行代码
本地启动 Headless Chrome
本地启动 Headless Chrome 可以参考这篇文章。
如果需要使用 Docker 来启动Headless Chrome 也可以使用
docker run -it --rm --name alpine-chrome -p 9222:9222 einverne/alpine-chrome
更加具体的使用介绍可以查看 GitHub
pychrome
pychrome 是 Chrome Devtools Protocol 的 Python 实现,其他语言的实现,可以查看这个项目。Chrome官方推荐js/nodejs的实现 chrome-remote-interface 还有 node.js 实现的更高层级的 Puppeteer。
这里主要摘录几个常用的操作
截图
使用 Page.captureScreenshot
def screenshot(browser, url, filename='image.png'):
tab = browser.new_tab()
tab.start()
tab.call_method('Page.navigate', url=url, _timeout=5)
tab.wait(10)
# 截取当前Tab屏幕,结果为图片内容Base64
screen_base64 = tab.call_method("Page.captureScreenshot")
image_data = screen_base64.get('data', '')
with open(filename, 'wb') as f:
f.write(image_data.decode('base64'))
tab.stop()
browser.close_tab(tab)
打印PDF
同样使用 Page 下方法
def print_to_pdf(browser, url, filename='file.pdf'):
tab = browser.new_tab()
tab.start()
tab.call_method('Page.navigate', url=url, _timeout=5)
tab.wait(10)
pdf_data = tab.call_method('Page.printToPDF', landscape=False).get('data')
with open(filename, 'wb') as f:
f.write(pdf_data.decode('base64'))
tab.stop()
browser.close_tab(tab)
向下翻页
def perform_input(browser):
"""向下浏览页面"""
tab = browser.new_tab()
tab.start()
tab.Page.enable()
tab.Page.navigate(url='https://www.douban.com/', _timeout=5)
tab.wait(5)
# 更多 keycode 可以参考 https://msdn.microsoft.com/en-us/library/dd375731(VS.85).aspx
keycode = int(0x22)
tab.Input.dispatchKeyEvent(type='keyDown', windowsVirtualKeyCode=keycode, nativeVirtualKeyCode=keycode)
tab.wait(3)
screen_base64 = tab.call_method("Page.captureScreenshot")
image_data = screen_base64.get('data', '')
with open("keyDown.png", 'wb') as f:
f.write(image_data.decode('base64'))
tab.stop()
browser.close_tab(tab)
获取网页HTML
获取动态网页的网页内容,等待JS执行完成后获取HTML
def get_html(browser, url):
tab = browser.new_tab()
tab.start()
tab.call_method('Page.navigate', url=url, _timeout=5)
tab.wait(10)
html = tab.Runtime.evaluate(expression="document.documentElement.outerHTML")
tab.stop()
browser.close_tab(tab)
return html['result']['value']
操纵网页元素注入JS
下面这段代码是访问 baidu,输入关键字,并点击搜索按钮,解析搜索结果并打印
def perform_click(browser):
tab = browser.new_tab()
# def loading_finished(**kwargs):
# print "[loading finished]"
#
# # when HTTP request has finished loading
# tab.set_listener("Network.loadingFinished", loading_finished)
tab.start()
# call method
# tab.Network.enable()
tab.Network.enable()
tab.Page.enable()
tab.Runtime.enable()
def dom_content_event_fired(**kwargs):
print "[content] dom content event fired"
tab.DOM.enable()
root = tab.DOM.getDocument()
root_node_id = root.get('root', {}).get('nodeId', '')
# 找到输入框
input_box = tab.DOM.querySelector(nodeId=root_node_id, selector='#kw')
# tab.DOM.setNodeValue(nodeId=input_box, value='hello')
tab.Runtime.evaluate(expression='document.getElementById("kw").value="Chrome"', )
# 找到搜索按钮
search_btn = tab.DOM.querySelector(nodeId=root_node_id, selector='#su')
remote_node = tab.DOM.resolveNode(nodeId=search_btn.get('nodeId', ''))
# 执行点击
tab.Runtime.callFunctionOn(functionDeclaration='(function() { this.click(); })',
objectId=remote_node.get('object', {}).get('objectId', {}))
tab.wait(3)
# 输出结果
html = tab.Runtime.evaluate(expression="document.documentElement.outerHTML")
html_value = html.get('result', {}).get('value', '').encode('utf-8')
soup = BeautifulSoup(html_value, 'html.parser')
l = soup.select('h3 > a')
for result in l:
print result.text
print result['href']
screen_base64 = tab.call_method("Page.captureScreenshot")
image_data = screen_base64.get('data', '')
with open("test.png", 'wb') as f:
f.write(image_data.decode('base64'))
# tab.DOM.performSearch(query='xpath', includeUserAgentShadowDOM=True)
# stop the tab (stop handle events and stop recv message from chrome)
tab.stop()
# close tab
browser.close_tab(tab)
tab.set_listener("Page.domContentEventFired", dom_content_event_fired)
# tab.call_method("Page.reload", ignoreCache=False)
tab.call_method("Page.navigate", url='https://www.baidu.com', _timeout=5)
tab.wait(20)
完整的代码可以参考 GitHub
其他可学习的网站
这篇文章讲述了使用 nodejs 的库 NickJS来爬取网站的要点。
这篇使用 nodejs 的chrome-remote-interface,来抓取网页。
其他
https://stackoverflow.com/questions/28430479/using-google-chrome-remote-debugging-protocol
https://blog.phantombuster.com/web-scraping-in-2017-headless-chrome-tips-tricks-4d6521d695e8
https://stackoverflow.com/questions/7848878/how-to-use-google-chrome-remote-debugging-protocol-in-http
https://github.com/auchenberg/devtools-remote
https://github.com/auchenberg/chrome-devtools-app
https://intoli.com/blog/running-selenium-with-headless-chrome
https://duo.com/blog/driving-headless-chrome-with-python
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。