# 本文观点仅为个人见解,若有不足之处,还请多多指教
selenium是一个自动化web测试工具,几乎可以完美模拟人来操作浏览器进行各种请求网页,它的基本原理是以webdriver驱动为桥梁,向浏览器发送遵循JsonWireProtocol规则的命令,进而操作浏览器。该规则是所有浏览器都遵循的,所以只要有对应浏览器的webdriver,我们就可以操作浏览器。后文中均以chrome为例。
chromedriver下载地址:http://chromedriver.storage.googleapis.com/index.html,下载对应浏览器版本的chromedriver
1、向浏览器发起请求
本质上,selenium对浏览器的每一次操作都是发起一次请求,与http类似,http请求是向网络服务器发送请求,然后服务器按照你的请求内容进行响应。而在selenium中的请求对象是本地浏览器,而且它需要一个中间桥梁来实现,就是webdriver,chromedriver只能对应chrome浏览器。selenium中使用subprocess来创建chromedriver子进程,使用urllib3模块来发起请求。subprocess在请求的核心是封装在selenium.webdriver.remote.remote_connection模块中的RemoteConnection的_request方法:
def _request(self, method, url, body=None):
"""
Send an HTTP request to the remote server.
:Args:
- method - A string for the HTTP method to send the request with.
- url - A string for the URL to send the request to.
- body - A string for request body. Ignored unless method is POST or PUT.
:Returns:
A dictionary with the server's parsed JSON response.
"""
LOGGER.debug('%s %s %s' % (method, url, body))
parsed_url = parse.urlparse(url)
headers = self.get_remote_connection_headers(parsed_url, self.keep_alive)
resp = None
if self.keep_alive:
if body and method != 'POST' and method != 'PUT':
body = None
resp = self._conn.request(method, url, body=body, headers=headers)
statuscode = resp.status
else:
http = urllib3.PoolManager()
resp = http.request(method, url, body=body, headers=headers)
statuscode = resp.status
if not hasattr(resp, 'getheader'):
if hasattr(resp.headers, 'getheader'):
resp.getheader = lambda x: resp.headers.getheader(x)
elif hasattr(resp.headers, 'get'):
resp.getheader = lambda x: resp.headers.get(x)
data = resp.data.decode('UTF-8')
。。。
_request方法需要3个参数的,method、url和body,返回结果是data。使用urllib3模块构建请求,并将response赋值给resp。举个栗子,来看看运行时的情况:
我们创建如下代码,创建实例时的executable_path是自己的chromedriver的路径,logging等级设置为debug是为了获得更多程序运行的细节。
import logging
logging.basicConfig(level=logging.DEBUG)
from selenium import webdriver
driver = webdriver.Chrome(executable_path=r'/Volumes/D/pythoncode/webdriver/chromedriver')
#driver.get('https://www.baidu.com')
我们运行代码,可以发现,一个空的浏览器窗口被打开,并且console对话框中结果如图:
可见在创建实例时,客户端发起了一次post请求,请求地址是http://127.0.0.1:52690/session,body是后面一长串的东西。事实上,selenium支持的所有功能都在RemoteConnection的_commands属性中体现了:
self._commands = {
Command.STATUS: ('GET', '/status'),
Command.NEW_SESSION: ('POST', '/session'),
Command.GET_ALL_SESSIONS: ('GET', '/sessions'),
Command.QUIT: ('DELETE', '/session/$sessionId'),
Command.GET_CURRENT_WINDOW_HANDLE:
('GET', '/session/$sessionId/window_handle'),
Command.W3C_GET_CURRENT_WINDOW_HANDLE:
('GET', '/session/$sessionId/window'),
Command.GET_WINDOW_HANDLES:
('GET', '/session/$sessionId/window_handles'),
Command.W3C_GET_WINDOW_HANDLES:
('GET', '/session/$sessionId/window/handles'),
Command.GET: ('POST', '/session/$sessionId/url'),
...
}
_commands属性构造了请求的method与url,而body则是由method的值以及客户端传入的参数所决定。这个属性的值是一个元组,元组中的第一个元素是请求方法,即method。第二个元素则是请求的url的后缀,例如我们使用get方法访问指定的网页时,按照_commands属性中Command.GET的值来构建,method=‘POST',url='http://www.127.0.0.1:<port>'+'/session/$sessionId/url',body则在其他组件中构建,传入了要打开的网页地址及sessionId。如下图所示:
由此,当我们创建selenium中的WebDriver实例时,我们已经向浏览器发起了一次请求,对照_commands,调用的是Command.NEW_SESSION。这次请求创建一个新的session,建立了一个由客户端、webdriver、浏览器所组成的通道。至此,selenium的准备工作完成,我们可以操作这个实例来控制浏览器。