初探selenium3原理
从一个启动浏览器并打开百度网页的代码开始
- from selenium import webdriver
- driver = webdriver.chrome()
- driver.get('https://www.baidu.com')
from selenium import webdriver
这代码表示从selenium导入webdriver。进入selenium, 发现webdriver是一个包,那么导入的其实是webdriver包下的`___init__.py`文件
- from .firefox.webdriver import WebDriver as Firefox # noqa
- from .firefox.firefox_profile import FirefoxProfile # noqa
- from .firefox.options import Options as FirefoxOptions # noqa
#实例化的是.chrome.webdriver里的webDriver- from .chrome.webdriver import WebDriver as Chrome # noqa
- from .chrome.options import Options as ChromeOptions # noqa
- from .ie.webdriver import WebDriver as Ie # noqa
- from .ie.options import Options as IeOptions # noqa
- from .edge.webdriver import WebDriver as Edge # noqa
- from .opera.webdriver import WebDriver as Opera # noqa
- from .safari.webdriver import WebDriver as Safari # noqa
- from .blackberry.webdriver import WebDriver as BlackBerry #noqa
- from .phantomjs.webdriver import WebDriver as PhantomJS # noqa
- from .android.webdriver import WebDriver as Android # noqa
- from .webkitgtk.webdriver import WebDriver as WebKitGTK # noqa
- from .webkitgtk.options import Options as WebKitGTKOptions
- from .remote.webdriver import WebDriver as Remote # noqa
- from .common.desired_capabilities import DesiredCapabilities
- from .common.action_chains import ActionChains # noqa
- from .common.touch_actions import TouchActions # noqa
- from .common.proxy import Proxy # noqa
打开chrome.webdriver文件,下面只展示出相关代码
- #selenium/webdriver/chrome/webdriver.py
- import warnings
- from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
- from .remote_connection import ChromeRemoteConnection
- from .service import Service
- from .options import Options
- class WebDriver(RemoteWebDriver):
- def __init__(self, executable_path="chromedriver", port=0,
- options=None, service_args=None,
- desired_capabilities=None, service_log_path=None,
- chrome_options=None):
- """
- 参数:
- - executable_path - chromedriver的执行路径 默认在环境变里中查找
- - port -http连接的端口号
- - desired_capabilities: 一般浏览器的字典对象
- - options: ChromeOptions的实例
- """
- #………………………………省略…………………………………………
- #第1步 实例化一个Service对象
- self.service = Service(
- executable_path,
- port=port,
- service_args=service_args,
- log_path=service_log_path)
- #第2步 调用了service的start方法
- self.service.start()
- #………………………………省略…………………………………………
WebDriver构造方法中最先实例化Service类,我们实例化chrome() 并没有参数,所以Service 的参数 executable_path="chromedriver" port=0,其余都是None
打开Chrome目录Service文件, 只有以下代码
- #selenium/webdriver/chrome/service.py
- from selenium.webdriver.common import service
- class Service(service.Service):
- """
- 实例化Service对象 管理ChromeDriver的启动和停止
- """
- def __init__(self, executable_path, port=0, service_args=None,
- log_path=None, env=None):
- """
- 参数:
- - service_args : chromedriver 的参数 列表形式
- - log_path : chromedriver的日志路径
- """
- self.service_args = service_args or []
- if log_path:
- self.service_args.append('--log-path=%s' % log_path)
- #第1步 调用复类的构造方法
- service.Service.__init__(self, executable_path, port=port, env=env,
- start_error_message="Please see https://sites.google.com/a/chromium.org/chromedriver/home")
- #重写父类方法 获取命令行的参数
- def command_line_args(self):
- return ["--port=%d" % self.port] + self.service_args
该类继承了selenium.webdriver.common目录下 service 类,并重写了父类的command_line_args方法。构造方法中调用了父类的构造方法。
- #selenium/webdriver/common/service.py
- import errno
- import os
- import platform
- import subprocess
- from subprocess import PIPE
- import time
- from selenium.common.exceptions import WebDriverException
- from selenium.webdriver.common import utils
- try:
- from subprocess import DEVNULL
- _HAS_NATIVE_DEVNULL = True
- except ImportError:
- DEVNULL = -3
- _HAS_NATIVE_DEVNULL = False
- class Service(object):
- def __init__(self, executable, port=0, log_file=DEVNULL, env=None, start_error_message=""):
- self.path = executable
- self.port = port
- #默认自动获取一个端口
- if self.port == 0:
- self.port = utils.free_port()
- if not _HAS_NATIVE_DEVNULL and log_file == DEVNULL:
- log_file = open(os.devnull, 'wb')
- self.start_error_message = start_error_message
- self.log_file = log_file
- #默认获取系统的环境变量
- self.env = env or os.environ
- @property
- def service_url(self):
- """
- Gets the url of the Service
- """
- return "http://%s" % utils.join_host_port('localhost', self.port)
- def command_line_args(self):
- raise NotImplemented("This method needs to be implemented in a sub class")
- def start(self):
- """
- Starts the Service.
- :Exceptions:
- - WebDriverException : Raised either when it can't start the service
- or when it can't connect to the service
- """
- try:
- #启动chromedriver程序 参数为 --port=端口号 输入输出到devnull空设备
- cmd = [self.path]
- cmd.extend(self.command_line_args())
- self.process = subprocess.Popen(cmd, env=self.env,
- close_fds=platform.system() != 'Windows',
- stdout=self.log_file,
- stderr=self.log_file,
- stdin=PIPE)
- except TypeError:
- raise
- except OSError as err:
- if err.errno == errno.ENOENT:
- raise WebDriverException(
- "'%s' executable needs to be in PATH. %s" % (
- os.path.basename(self.path), self.start_error_message)
- )
- elif err.errno == errno.EACCES:
- raise WebDriverException(
- "'%s' executable may have wrong permissions. %s" % (
- os.path.basename(self.path), self.start_error_message)
- )
- else:
- raise
- except Exception as e:
- raise WebDriverException(
- "The executable %s needs to be available in the path. %s\n%s" %
- (os.path.basename(self.path), self.start_error_message, str(e)))
- count = 0
- #检测是否subprocess进程是否还在,不在则抛出异常
- #检测是否http协议是否链接 若无链接等待30秒抛出异常
- while True:
- self.assert_process_still_running()
- if self.is_connectable():
- break
- count += 1
- time.sleep(1)
- if count == 30:
- raise WebDriverException("Can not connect to the Service %s" % self.path)
- def assert_process_still_running(self):
- return_code = self.process.poll()
- if return_code is not None:
- raise WebDriverException(
- 'Service %s unexpectedly exited. Status code was: %s'
- % (self.path, return_code)
- )
#判断是否正在连接,等待30秒后抛出webdriver异常- def is_connectable(self):
- return utils.is_connectable(self.port)
由上代码可知Serivce的实例化 获取一个端口。
然后调用了service对象的start方法。该方法用subprocess启动chromedriver程序 并检测是否正在连接。
现在再来看最开始chrome 的webDriver类, 此类继承了selenium.webdriver.remote下的webdriver并调用了父类的构造方法。
- #selenium/webdriver/remote/webdriver.py
- import warnings
- from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
- from .remote_connection import ChromeRemoteConnection
- from .service import Service
- from .options import Options
- class WebDriver(RemoteWebDriver):
- """
- Controls the ChromeDriver and allows you to drive the browser.
- You will need to download the ChromeDriver executable from
- http://chromedriver.storage.googleapis.com/index.html
- """
- def __init__(self, executable_path="chromedriver", port=0,
- options=None, service_args=None,
- desired_capabilities=None, service_log_path=None,
- chrome_options=None):
- #………………………………省略…………………………………………
- if options is None:
- # desired_capabilities stays as passed in
- if desired_capabilities is None:
- #第1步 创建一个浏览器的字典对象
- desired_capabilities = self.create_options().to_capabilities()
- else:
- if desired_capabilities is None:
- desired_capabilities = options.to_capabilities()
- else:
- desired_capabilities.update(options.to_capabilities())
- #………………………………省略…………………………………………
- #第二步调用 复类的构造方法
- try:
- RemoteWebDriver.__init__(
- self,
- command_executor=ChromeRemoteConnection(
- remote_server_addr=self.service.service_url),
- desired_capabilities=desired_capabilities)
- except Exception:
- self.quit()
- raise
- self._is_remote = False
- def create_options(self):
- return Options()
首先创建一个浏览器的字典对象,然后调用了to_capabilities()方法。
Options的to_capabilities()方法是返回一个caps字典对象
chrome浏览器返回的caps字典对象为:
{
'browserName': 'chrome',
'version': '',
'platform': 'ANY',
'goog:chromeOptions': {'extensions': [], 'args': []}
}
接下来看看 RemoteWebDriver的构造方法
- RemoteWebDriver.__init__(
- self,
- command_executor=ChromeRemoteConnection(
- remote_server_addr=self.service.service_url),
- desired_capabilities=desired_capabilities)
传入了2个参数 一个是 ChromeRemoteConnection类的实例对象, 一个是前面获取到的浏览器字典对象。
来看看ChromeRemoteConnection类。继承了RemoteConnection,调用了父类的构造方法并往self._commands添加里几个command键值对
- #selenium/webdriver/chrome/remote_connection.py
- from selenium.webdriver.remote.remote_connection import RemoteConnection
- class ChromeRemoteConnection(RemoteConnection):
- def __init__(self, remote_server_addr, keep_alive=True):
- RemoteConnection.__init__(self, remote_server_addr, keep_alive)
- self._commands["launchApp"] = ('POST', '/session/$sessionId/chromium/launch_app')
- self._commands["setNetworkConditions"] = ('POST', '/session/$sessionId/chromium/network_conditions')
- self._commands["getNetworkConditions"] = ('GET', '/session/$sessionId/chromium/network_conditions')
- #selenium/webdriver/remote/remote_connection.py
- class RemoteConnection(object):
- """A connection with the Remote WebDriver server.
- Communicates with the server using the WebDriver wire protocol:
- https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol"""
- def __init__(self, remote_server_addr, keep_alive=False, resolve_ip=True):
- # Attempt to resolve the hostname and get an IP address.
- self.keep_alive = keep_alive
- parsed_url = parse.urlparse(remote_server_addr)
- addr = parsed_url.hostname
- if parsed_url.hostname and resolve_ip:
- port = parsed_url.port or None
- if parsed_url.scheme == "https":
- ip = parsed_url.hostname
- elif port and not common_utils.is_connectable(port, parsed_url.hostname):
- ip = None
- LOGGER.info('Could not connect to port {} on host '
- '{}'.format(port, parsed_url.hostname))
- else:
- ip = common_utils.find_connectable_ip(parsed_url.hostname,
- port=port)
- if ip:
- netloc = ip
- addr = netloc
- if parsed_url.port:
- netloc = common_utils.join_host_port(netloc,
- parsed_url.port)
- if parsed_url.username:
- auth = parsed_url.username
- if parsed_url.password:
- auth += ':%s' % parsed_url.password
- netloc = '%s@%s' % (auth, netloc)
- remote_server_addr = parse.urlunparse(
- (parsed_url.scheme, netloc, parsed_url.path,
- parsed_url.params, parsed_url.query, parsed_url.fragment))
- else:
- LOGGER.info('Could not get IP address for host: %s' %
- parsed_url.hostname)
- self._url = remote_server_addr
- if keep_alive:
- self._conn = httplib.HTTPConnection(
- str(addr), str(parsed_url.port), timeout=self._timeout)
- 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'),
- #................省略.................
- }
- #最终发送命令到远程服务器的方法
- def execute(self, command, params):
- command_info = self._commands[command]
- assert command_info is not None, 'Unrecognised command %s' % command
- path = string.Template(command_info[1]).substitute(params)
- if hasattr(self, 'w3c') and self.w3c and isinstance(params, dict) and 'sessionId' in params:
- del params['sessionId']
- data = utils.dump_json(params)
- url = '%s%s' % (self._url, path)
- return self._request(command_info[0], url, body=data)
- #返回带有JSON解析的字典
- 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 body and method != 'POST' and method != 'PUT':
- body = None
- if self.keep_alive:
- resp = self._conn.request(method, url, body=body, headers=headers)
- statuscode = resp.status
- else:
- http = urllib3.PoolManager(timeout=self._timeout)
- 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')
- try:
- if 300 <= statuscode < 304:
- return self._request('GET', resp.getheader('location'))
- if 399 < statuscode <= 500:
- return {'status': statuscode, 'value': data}
- content_type = []
- if resp.getheader('Content-Type') is not None:
- content_type = resp.getheader('Content-Type').split(';')
- if not any([x.startswith('image/png') for x in content_type]):
- try:
- data = utils.load_json(data.strip())
- except ValueError:
- if 199 < statuscode < 300:
- status = ErrorCode.SUCCESS
- else:
- status = ErrorCode.UNKNOWN_ERROR
- return {'status': status, 'value': data.strip()}
- # Some of the drivers incorrectly return a response
- # with no 'value' field when they should return null.
- if 'value' not in data:
- data['value'] = None
- return data
- else:
- data = {'status': 0, 'value': data}
- return data
- finally:
- LOGGER.debug("Finished Request")
- resp.close()
构造方法中主要是把localhost域名换成127.0.0.1,通过urllib.parse.urlparse把要处理的url解析6大部分。
urlparse返回的是一个名字元组对象scheme, netloc, path, params, query, fragment。netloc包括hostname和port。
调用 common_utils.find_connectable_ip()方法获取hostname对应的ip地址,最后urllib.parse.urlunparse()重新组成url并赋值给self._url
初始化里self._commands 字典,value为具体执行的命令的字典。
RemoteConnection类的实例方法execute调用 _request方法最终实现发送命令到远程服务器。
他们是通过wire protocol有线协议 这种协议是点对点方式进行通信的。首先前端将这个点击转换成json格式的字符串,然后通过wire protocl协议传递给服务器
RemoteWebDriver类的构造方法 更新capabilities字典 主要调用start_session传入capabilities字典
start_session方法 根据capabilities字典创建一个新的会话并获取session_id。
另外还实例化了错误处理handle,文件查找file_detector(默认实例化是LocalFileDetector)。一个页面切换的SwitchTo对象。
- #selenium/webdriver/remote/webdriver.py
- class WebDriver(object):
- _web_element_cls = WebElement
- def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
- desired_capabilities=None, browser_profile=None, proxy=None,
- keep_alive=False, file_detector=None, options=None):
- """
- 创建一个driver使用 wire协议发送命令
- 参数:
- - command_executor - 远程服务器的url 'http://127.0.0.1:端口号'
- - desired_capabilities - A dictionary of capabilities to request when
- starting the browser session. 必选参数
- - proxy - 一个selenium.webdriver.common.proxy.Proxy 对象. 可选的
- - file_detector - 自定义文件检测器对象. 默认使用LocalFileDetector()
- - options - options.Options类的实例
- """
- capabilities = {}
- if options is not None:
- capabilities = options.to_capabilities()
- if desired_capabilities is not None:
- if not isinstance(desired_capabilities, dict):
- raise WebDriverException("Desired Capabilities must be a dictionary")
- else:
- #更新capabilities字典
- capabilities.update(desired_capabilities)
- if proxy is not None:
- warnings.warn("Please use FirefoxOptions to set proxy",
- DeprecationWarning)
- proxy.add_to_capabilities(capabilities)
- self.command_executor = command_executor
- if type(self.command_executor) is bytes or isinstance(self.command_executor, str):
- self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive)
- self._is_remote = True
- #控制浏览器会话的字符串id
- self.session_id = None
- self.capabilities = {}
- #errorhandler.ErrorHandler 处理错误的handler
- self.error_handler = ErrorHandler()
- self.start_client()
- if browser_profile is not None:
- warnings.warn("Please use FirefoxOptions to set browser profile",
- DeprecationWarning)
- #核心代码 开始一个会话
- self.start_session(capabilities, browser_profile)
- #实例化页面切换对象
- self._switch_to = SwitchTo(self)
- #app
- self._mobile = Mobile(self)
- #默认实例化LocalFileDetector对象
- self.file_detector = file_detector or LocalFileDetector
- def start_session(self, capabilities, browser_profile=None):
- """
- 根据capabilities字典创建一个新的会话
- browser_profile FirefoxProfile的一个对象 只有火狐浏览器
- """
- if not isinstance(capabilities, dict):
- raise InvalidArgumentException("Capabilities must be a dictionary")
- if browser_profile:
- if "moz:firefoxOptions" in capabilities:
- capabilities["moz:firefoxOptions"]["profile"] = browser_profile.encoded
- else:
- capabilities.update({'firefox_profile': browser_profile.encoded})
- """
- _make_w3c_caps return dict
- {
- "firstMatch": [{}],
- "alwaysMatch": {
- 'browserName': 'chrome',
- 'version': '',
- 'platformName': 'any',
- 'goog:chromeOptions': {'extensions': [], 'args': []}
- }
- }
- """
- w3c_caps = _make_w3c_caps(capabilities)
- parameters = {"capabilities": w3c_caps,
- "desiredCapabilities": capabilities}
- #Command.NEW_SESSION: ('POST', '/session'),
- response = self.execute(Command.NEW_SESSION, parameters)
- if 'sessionId' not in response:
- response = response['value']
- #获取session_id
- self.session_id = response['sessionId']
- self.capabilities = response.get('value')
- # if capabilities is none we are probably speaking to
- # a W3C endpoint
- if self.capabilities is None:
- self.capabilities = response.get('capabilities')
- # Double check to see if we have a W3C Compliant browser
- self.w3c = response.get('status') is None
- self.command_executor.w3c = self.w3c
- def _make_w3c_caps(caps):
- """Makes a W3C alwaysMatch capabilities object.
- Filters out capability names that are not in the W3C spec. Spec-compliant
- drivers will reject requests containing unknown capability names.
- Moves the Firefox profile, if present, from the old location to the new Firefox
- options object.
- :Args:
- - caps - A dictionary of capabilities requested by the caller.
- """
- #深拷贝
- caps = copy.deepcopy(caps)
- #因为浏览器chrome 所以profile为None
- profile = caps.get('firefox_profile')
- always_match = {}
- if caps.get('proxy') and caps['proxy'].get('proxyType'):
- caps['proxy']['proxyType'] = caps['proxy']['proxyType'].lower()
- for k, v in caps.items():
- #如果caps的key 在_OSS_W3C_CONVERSION key中 而且caps的key对应的值不为空
- if v and k in _OSS_W3C_CONVERSION:
- #always_match的key 为_OSS_W3C_CONVERSION字典的值 value是caps字典的值
- always_match[_OSS_W3C_CONVERSION[k]] = v.lower() if k == 'platform' else v
- if k in _W3C_CAPABILITY_NAMES or ':' in k:
- always_match[k] = v
- if profile:
- moz_opts = always_match.get('moz:firefoxOptions', {})
- # If it's already present, assume the caller did that intentionally.
- if 'profile' not in moz_opts:
- # Don't mutate the original capabilities.
- new_opts = copy.deepcopy(moz_opts)
- new_opts['profile'] = profile
- always_match['moz:firefoxOptions'] = new_opts
- return {"firstMatch": [{}], "alwaysMatch": always_match}
- _OSS_W3C_CONVERSION = {
- 'acceptSslCerts': 'acceptInsecureCerts',
- 'version': 'browserVersion',
- 'platform': 'platformName'
- }
- #通过self.command_executor.execute发送cmd命令到远程服务器达到控制浏览器的目标。
- def execute(self, driver_command, params=None):
- """
- 通过command.CommandExecutor执行driver_command命令
- 返回一个字典对象 里面装着JSON response
- """
- if self.session_id is not None:
- if not params:
- params = {'sessionId': self.session_id}
- elif 'sessionId' not in params:
- params['sessionId'] = self.session_id
- #数据封包
- params = self._wrap_value(params)
- #核心代码 执行cmmand_executor实例对象的execute方法
- response = self.command_executor.execute(driver_command, params)
- if response:
- self.error_handler.check_response(response)
- #数据解包
- response['value'] = self._unwrap_value(
- response.get('value', None))
- return response
- # If the server doesn't send a response, assume the command was
- # a success
- return {'success': 0, 'value': None, 'sessionId': self.session_id}
driver.get('https://www.baidu.com')调用的是webdriver/remote/webdriver.py下的get方法
get方法调用了remote_connection.py中execute的方法,remote_connection.py中execute的方法中self.command_executor.execute实际调用的是RemoteConnection.py的execute方法。
实际上是一个HTTP request给监听端口上的Web Service, 在我们的HTTP request的body中,会以WebDriver Wire协议规定的JSON格式的字符串来告诉Selenium我们希望浏览器打开'https://www.baidu.com'页面
- #selenium/webdriver/remote/webdriver.py
- def get(self, url):
- """
- Loads a web page in the current browser session.
- """
- #Command.GET: ('POST', '/session/$sessionId/url'),
- self.execute(Command.GET, {'url': url})
总结一下:
首先是webdriver实例化Service 类调用start()方法用subprocess启动chromedriver(带--port参数)驱动。chromedriver启动之后都会在绑定的端口启动Web Service。
接着实例化RemoteConnection获得 command_executor实例化对象 传入给RemoteWebDriver构造方法。
RemoteWebDriver构造方法 start_session()方法启动session并获得唯一的session_id,通过这个session_id来确定找到对方且在多线程并行的时候彼此之间不会有冲突和干扰)
接下来调用WebDriver的任何API,比如get() 都需要借助一个ComandExecutor(remote_connection类的实例对象)调用execute()发送一个命令(这个命令在ComandExecutor实例化时候生成的一个command字典)。
- #部分
- 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'),
- #.................省略.....................
- }
ComandExecutor中的execute()方法最后返回一个_request()方法,实际上是一个HTTP request给监听端口上的Web Service。
在HTTP request的body中,Wire JSON格式字典来告诉chromedriver接下来做什么事。(通过之前绑定的端口)
实际的执行者是chromedriver驱动,而selenium就相当于一个代理。所以selenium并不是直接操控浏览器而是运行webdriver, 通过webdriver间接操控浏览器。
在现实生活中这类似打出租车,我们告诉司机目的地是哪?走哪条路到达?webdriver就相当于出租车司机。
初探selenium3原理的更多相关文章
- CSS Spritec下载,精灵图,雪碧图,初探之原理、使用
CSS Spritec下载,精灵图,雪碧图,初探之原理.使用 关于CSS Sprite CSSSprites在国内很多人叫css精灵雪碧图,是一种网页图片应用处理方式.它允许你将一个页面涉及到的所有零 ...
- CSS Sprite初探之原理、使用
CSS Sprite简介: 利用CSS Sprites能很好地减少了网页的http请求次数,从而大大的提高了页面的性能,节省时间和带宽.CSS Sprites在国内很多人叫css精灵, 是一种网页图片 ...
- Kubernetes初探:原理及实践应用
总体概览 如下图所示是我初步阅读文档和源代码之后整理的总体概览,基本上可以从如下三个维度来认识Kubernetes. 操作对象 Kubernetes以RESTFul形式开放接口,用户可操作的REST对 ...
- lucene 初探
前言: window文件管理右上角, 有个搜索功能, 可以根据文件名进行搜索. 那如果从文件名上判断不出内容, 我岂不是要一个一个的打开文件, 查看文件的内容, 去判断是否是我要的文件? 几个, 十几 ...
- 初探Windows用户态调试机制
我们在感叹Onlydbg强大与便利的同时,是否考虑过它实现的原理呢? 作为一个技术人员知其然必知其所以然,这才是我们追求的本心. 最近在学习张银奎老师的<软件调试>,获益良多.熟悉Wind ...
- 【云计算】Docker云平台—Docker进阶
Docker云平台系列共三讲,此为第二讲:Docker进阶 参考资料: 五个Docker监控工具的对比:http://www.open-open.com/lib/view/open1433897177 ...
- Flynn初步:基于Docker的PaaS台
Flynn它是一个开源PaaS台,无论要部署的应用程序,你可以建立自己的主动性Docker容器集群的实现,能特性与组件设计大量參考了传统的PaaS平台Heroku.本文旨在从使用动机.基本对象.层次架 ...
- Python源代码剖析笔记3-Python运行原理初探
Python源代码剖析笔记3-Python执行原理初探 本文简书地址:http://www.jianshu.com/p/03af86845c95 之前写了几篇源代码剖析笔记,然而慢慢觉得没有从一个宏观 ...
- JVM初探- 内存分配、GC原理与垃圾收集器
JVM初探- 内存分配.GC原理与垃圾收集器 标签 : JVM JVM内存的分配与回收大致可分为如下4个步骤: 何时分配 -> 怎样分配 -> 何时回收 -> 怎样回收. 除了在概念 ...
随机推荐
- Qt获取当前屏幕大小
1.头文件 #include<QScreen> 2.代码 QScreen *screen = QGuiApplication::primaryScreen (); QRect screen ...
- 后台实战——用户登录之JWT
https://blog.csdn.net/jackcheng_ht/article/details/52670211
- SSM开发健康信息管理系统
Spring+Spring MVC+MyBatis基于MVC架构的个人健康信息管理系统 采用ssm框架,包含 健康档案.健康预警(用户输入数据,系统根据范围自动判断给出不同颜色箭头显示). 健康分析. ...
- linux安装Nginx 以及 keepalived 管理Nginx
linux安装Nginx 1.1将Nginx素材内容上传到/usr/local目录(pcre,zlib,openssl,nginx)(注意:必须登录用对这个文件具有操作权限的) 1.2安装pcre库 ...
- 用idea打包maven项目
利用idea工具打包项目 1.点击图中标记①,idea会自动生成打包命令,这个打包命令会包含单元测试内容,如果单元测试的内容报错,是打包不成功的,这个时候我们需要在打包命令中用 -Dmaven.tes ...
- 【pattern】设计模式(1) - 单例模式
前言 好久没写博客,强迫自己写一篇.只是总结一下自己学习的单例模式. 说明 单例模式的定义,摘自baike: 单例模式最初的定义出现于<设计模式>(艾迪生维斯理, 1994):“保证一个类 ...
- openlayers地图显示点
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
- stun/turn服务器部署
目录: 一.简介 二.安装 三.配置与运行 四.运行检测 五.答疑环节 一.简介 本文通过在服务器上安装coturn这个软件,实现搭建STUN服务器和TURN服务器. coturn 简介:是一个免费的 ...
- H5_0013:CSS特色样式集
按比例变化,同时又限制最大宽高 ".start-wrap {", " width:40%;", " top: 83.21%;", " ...
- ECMAScript基本语法——⑤运算符 void
void阻止返回值的运算符,没有返回值