前言

在进行某些爬虫任务的时候,我们经常会遇到仅用Http协议难以攻破的情况,比如协议中带有加密参数,破解需要花费大量时间,那这时候就会用Selenium去模拟浏览器进行页面上的元素抓取

大多数情况下我们用Selenium只是爬取一下页面上可见的元素信息或者做一些模拟人工的操作,但页面可见元素的数据字段毕竟有限,有许多有用的字段隐藏在接口响应中的,但是要如何拿到接口响应内容呢?

在网上搜索Selenium如何获取Chrome中Network数据包响应结果,大多数的文章都是Python或者Java,C#的资源少之又少,虽然知道原理,但每个语言之间SDK代码实现相差很大,C#的SDK真的有点魔改,需要自己慢慢摸索

探索

通过寻找资料,大致就2种方案

方案1:通过Selenium指定本地一个代理去截取所有请求,类似于常见抓包工具的原理,但是C#是没有这种插件,也有可能是我没找到,比如Python和Java有一个叫Browsermob-Proxy的插件,可以和Selenium深度结合实现代理抓包。我利用FiddlerCore做了一个本地代理工具,但是并不好用,不能和Selenium进行深度绑定使用,会导致Selenium爬取过程和请求截取是异步进行的,强行用代码实现同步又很难受,达不到我想要的要求

方案2:Selenium通过chromedriver开启浏览器的性能日志功能,记录类型为Performance的日志,该功能在Selenium中叫做 PerformanceLoggingPreferences。网页加载完成后,可以通过Selenium拿到浏览器Performance Logs摘要信息,再利用Log中的RequestId调用Chrome CDP命令去浏览器端获取日志的完整内容。Selenium封装的CDP,本质上还是Http请求,只是带着驱动窗口的SessionId和Chrome的API做交互

方案1参考资料:

https://blog.csdn.net/qq_32502511/article/details/101536325  (Python + Browsermob-Proxy)

https://blog.csdn.net/fontcolor0/article/details/103297635/  (Java + Browsermob-Proxy)

https://www.cnblogs.com/airoot/articles/14888284.html  (C# + FiddlerCore)

方案2参考资料:

https://chromedevtools.github.io/devtools-protocol/  (Chrome DevTools Protocol 介绍)

https://www.jianshu.com/p/615e3c0140a5  (Python + 开启PerfLoggingPref)

https://blog.csdn.net/weixin_49855251/article/details/112281901  (不同日志类型的介绍)

https://blog.csdn.net/bigcarp/article/details/115065730  (Java + 原生CDP协议获取日志内容)

上手

启用Logging:

C#伪代码示例:

首先需要安装最新的Nuget包:OpenQA.Selenium

  1. var option = new ChromeOptions();
  2. option.SetLoggingPreference("performance",OpenQA.Selenium.LogLevel.Info); //启用performance日志,等级为Info即可
  3. option.PerformanceLoggingPreferences = new ChromiumPerformanceLoggingPreferences() {
  4. IsCollectingNetworkEvents = true //采集网络请求事件
  5. };
  6. using (ChromeDriver driver = new ChromeDriver(driverPath,option,TimeSpan.FromSeconds(5))) {
  7. driver.Navigate().GoToUrl("https://item.m.jd.com/product/10052422060501.html");
  8. Thread.Sleep(3 * 1000); //等待页面加载完成
  9.    var logs = driver.Manage().Logs.GetLog("performance"); //获取所有performance日志
  10. }

顺便放个Python代码做个对比:

  1. caps = {
  2. 'browserName': 'chrome',
  3. 'loggingPrefs': {
  4. 'performance': 'Info', //启用performance日志,等级为Info即可
  5. },
  6. 'goog:chromeOptions': {
  7. 'perfLoggingPrefs': {
  8. 'enableNetwork': True, //采集网络请求事件
  9. },
  10. 'w3c': False,
  11. },
  12. }
  13. driver = webdriver.Chrome(desired_capabilities=caps)
  14. //TODO 获取日志
    //.....

分析Logging:

我们从返回的日志列表里随便挑一个看看原始内容:

  1. {[2022-08-03T08:47:31Z] [Info] {"message":{"method":"Network.responseReceived","params":{"frameId":"78D9BC6F0CBE162DA6779F410AA1500C","hasExtraInfo":true,"loaderId":"33814E6BD702343CF0A2A38C976C772F","requestId":"33814E6BD702343CF0A2A38C976C772F","response":{"connectionId":242,"connectionReused":false,"encodedDataLength":546,"fromDiskCache":false,"fromPrefetchCache":false,"fromServiceWorker":false,"headers":{"access-control-allow-credentials":"true","access-control-allow-headers":"Origin, X-Requested-With, Content-Type, multipart/form-data, Accept, Authorization","access-control-allow-methods":"POST, GET, PATCH, DELETE, PUT, OPTIONS","access-control-allow-origin":"*","access-control-max-age":"3600","cache-control":"no-cache,no-store","content-encoding":"gzip","content-language":"zh-CN","content-type":"text/html;charset=UTF-8","date":"Wed, 03 Aug 2022 08:47:33 GMT","hit":"bj-9153118133147527","server":"jfe","strict-transport-security":"max-age=86400","vary":"Accept-Encoding"},"mimeType":"text/html","protocol":"h2","remoteIPAddress":"106.39.169.120","remotePort":443,"responseTime":1.659516451658764e+12,"securityDetails":{"certificateId":0,"certificateTransparencyCompliance":"compliant","cipher":"AES_256_GCM","issuer":"GlobalSign RSA OV SSL CA 2018","keyExchange":"","keyExchangeGroup":"X25519","protocol":"TLS 1.3","sanList":["*.jd.com","*.360buy.com","*.360buyimg.com","*.3.cn","*.7fresh.com","*.baitiao.com","*.chinabank.com.cn","*.e.jd.com","*.jd.co.th","*.jddglobal.com","*.jd.hk","*.jd.id","*.jdpay.com","*.jd.ru","*.jdworldwide.com","*.jdx.com","*.joybuy.com","*.joybuy.es","*.jr.jd.com","*.k.jd.com","*.m.jd.com","*.m.yhd.com","*.shop.jd.com","*.wangyin.com","*.yhd.com","*.yiyaojd.com","360buy.com","360buyimg.com","3.cn","7fresh.com","baitiao.com","chinabank.com.cn","jd.co.th","jddglobal.com","jd.hk","jd.id","jdpay.com","jd.ru","jdworldwide.com","jdx.com","joybuy.com","joybuy.es","wangyin.com","yhd.com","yiyaojd.com","jd.com"],"signedCertificateTimestampList":[{"hashAlgorithm":"SHA-256","logDescription":"Sectigo 'Mammoth' CT log","logId":"6F5376AC31F03119D89900A45115FF77151C11D902C10029068DB2089A37D913","origin":"Embedded in certificate","signatureAlgorithm":"ECDSA","signatureData":"3045022017A2AC492303F50786758D0B4B63EEB8D031850832031FC5A43139C0CDA5EB4F0221009063F884220327718857A6897B87ED5D9F785FFC97F23BD45C84975A11DE721E","status":"Verified","timestamp":1.634109209345e+12},{"hashAlgorithm":"SHA-256","logDescription":"Google 'Argon2022' log","logId":"2979BEF09E393921F056739F63A577E5BE577D9C600AF8F94D5D265C255DC784","origin":"Embedded in certificate","signatureAlgorithm":"ECDSA","signatureData":"3045022071D6D51A59CAA1764E478598AA8EE34B628C8F856B09CDB8382E090CF5D6D97D022100A8FE17E028BB6D439387721974ED3629B9CC44C90AC524B505B9B2375C75CBB9","status":"Verified","timestamp":1.634109210136e+12},{"hashAlgorithm":"SHA-256","logDescription":"Sectigo 'Sabre' CT log","logId":"5581D4C2169036014AEA0B9B573C53F0C0E43878702508172FA3AA1D0713D30C","origin":"Embedded in certificate","signatureAlgorithm":"ECDSA","signatureData":"3045022015D94080264A3FCA83C0F3DE2B85A703384BB678FEBBE5408B4FF7D30BD40900022100DFBAB7992EE99420724CA35A5C2C252298B750994B0EAA98ACB22992F97D545E","status":"Verified","timestamp":1.634109209393e+12}],"subjectName":"*.jd.com","validFrom":1634109205,"validTo":1668410005},"securityState":"secure","status":200,"statusText":"","timing":{"connectEnd":96.834,"connectStart":19.521,"dnsEnd":19.521,"dnsStart":0,"proxyEnd":-1,"proxyStart":-1,"pushEnd":0,"pushStart":0,"receiveHeadersEnd":217.707,"requestTime":2272960.91089,"sendEnd":97.25,"sendStart":97.03,"sslEnd":96.829,"sslStart":55.726,"workerFetchStart":-1,"workerReady":-1,"workerRespondWithSettled":-1,"workerStart":-1},"url":"https://item.m.jd.com/product/10052422060501.html"},"timestamp":2272961.1294,"type":"Document"}},"webview":"78D9BC6F0CBE162DA6779F410AA1500C"}}

我们格式化一下,可以发现日志里已经包含了请求的大部分描述信息,我们需要遍历过滤出method为Network.responseReceived的日志,当然你也可以再根据其他的一些参数过滤出你要的请求

每个日志都有一个requestId,我们再通过该值调CDP命令[Network.getResponseBody]换取具体内容

  1. {
  2. "message": {
  3. "method": "Network.responseReceived",
  4. "params": {
  5. "frameId": "78D9BC6F0CBE162DA6779F410AA1500C",
  6. "hasExtraInfo": true,
  7. "loaderId": "33814E6BD702343CF0A2A38C976C772F",
  8. "requestId": "33814E6BD702343CF0A2A38C976C772F",
  9. "response": {
  10. "connectionId": 242,
  11. "connectionReused": false,
  12. "encodedDataLength": 546,
  13. "fromDiskCache": false,
  14. "fromPrefetchCache": false,
  15. "fromServiceWorker": false,
  16. "headers": {
  17. "access-control-allow-credentials": "true",
  18. "access-control-allow-headers": "Origin, X-Requested-With, Content-Type, multipart/form-data, Accept, Authorization",
  19. "access-control-allow-methods": "POST, GET, PATCH, DELETE, PUT, OPTIONS",
  20. "access-control-allow-origin": "*",
  21. "access-control-max-age": "3600",
  22. "cache-control": "no-cache,no-store",
  23. "content-encoding": "gzip",
  24. "content-language": "zh-CN",
  25. "content-type": "text/html;charset=UTF-8",
  26. "date": "Wed, 03 Aug 2022 08:47:33 GMT",
  27. "hit": "bj-9153118133147527",
  28. "server": "jfe",
  29. "strict-transport-security": "max-age=86400",
  30. "vary": "Accept-Encoding"
  31. },
  32. "mimeType": "text/html",
  33. "protocol": "h2",
  34. "remoteIPAddress": "106.39.169.120",
  35. "remotePort": 443,
  36. "responseTime": 1.659516451658764e+12,
  37. "securityDetails": {
  38. "certificateId": 0,
  39. "certificateTransparencyCompliance": "compliant",
  40. "cipher": "AES_256_GCM",
  41. "issuer": "GlobalSign RSA OV SSL CA 2018",
  42. "keyExchange": "",
  43. "keyExchangeGroup": "X25519",
  44. "protocol": "TLS 1.3",
  45. "sanList": ["*.jd.com", "*.360buy.com", "*.360buyimg.com", "*.3.cn", "*.7fresh.com", "*.baitiao.com", "*.chinabank.com.cn", "*.e.jd.com", "*.jd.co.th", "*.jddglobal.com", "*.jd.hk", "*.jd.id", "*.jdpay.com", "*.jd.ru", "*.jdworldwide.com", "*.jdx.com", "*.joybuy.com", "*.joybuy.es", "*.jr.jd.com", "*.k.jd.com", "*.m.jd.com", "*.m.yhd.com", "*.shop.jd.com", "*.wangyin.com", "*.yhd.com", "*.yiyaojd.com", "360buy.com", "360buyimg.com", "3.cn", "7fresh.com", "baitiao.com", "chinabank.com.cn", "jd.co.th", "jddglobal.com", "jd.hk", "jd.id", "jdpay.com", "jd.ru", "jdworldwide.com", "jdx.com", "joybuy.com", "joybuy.es", "wangyin.com", "yhd.com", "yiyaojd.com", "jd.com"],
  46. "signedCertificateTimestampList": [{
  47. "hashAlgorithm": "SHA-256",
  48. "logDescription": "Sectigo 'Mammoth' CT log",
  49. "logId": "6F5376AC31F03119D89900A45115FF77151C11D902C10029068DB2089A37D913",
  50. "origin": "Embedded in certificate",
  51. "signatureAlgorithm": "ECDSA",
  52. "signatureData": "3045022017A2AC492303F50786758D0B4B63EEB8D031850832031FC5A43139C0CDA5EB4F0221009063F884220327718857A6897B87ED5D9F785FFC97F23BD45C84975A11DE721E",
  53. "status": "Verified",
  54. "timestamp": 1.634109209345e+12
  55. }, {
  56. "hashAlgorithm": "SHA-256",
  57. "logDescription": "Google 'Argon2022' log",
  58. "logId": "2979BEF09E393921F056739F63A577E5BE577D9C600AF8F94D5D265C255DC784",
  59. "origin": "Embedded in certificate",
  60. "signatureAlgorithm": "ECDSA",
  61. "signatureData": "3045022071D6D51A59CAA1764E478598AA8EE34B628C8F856B09CDB8382E090CF5D6D97D022100A8FE17E028BB6D439387721974ED3629B9CC44C90AC524B505B9B2375C75CBB9",
  62. "status": "Verified",
  63. "timestamp": 1.634109210136e+12
  64. }, {
  65. "hashAlgorithm": "SHA-256",
  66. "logDescription": "Sectigo 'Sabre' CT log",
  67. "logId": "5581D4C2169036014AEA0B9B573C53F0C0E43878702508172FA3AA1D0713D30C",
  68. "origin": "Embedded in certificate",
  69. "signatureAlgorithm": "ECDSA",
  70. "signatureData": "3045022015D94080264A3FCA83C0F3DE2B85A703384BB678FEBBE5408B4FF7D30BD40900022100DFBAB7992EE99420724CA35A5C2C252298B750994B0EAA98ACB22992F97D545E",
  71. "status": "Verified",
  72. "timestamp": 1.634109209393e+12
  73. }],
  74. "subjectName": "*.jd.com",
  75. "validFrom": 1634109205,
  76. "validTo": 1668410005
  77. },
  78. "securityState": "secure",
  79. "status": 200,
  80. "statusText": "",
  81. "timing": {
  82. "connectEnd": 96.834,
  83. "connectStart": 19.521,
  84. "dnsEnd": 19.521,
  85. "dnsStart": 0,
  86. "proxyEnd": -1,
  87. "proxyStart": -1,
  88. "pushEnd": 0,
  89. "pushStart": 0,
  90. "receiveHeadersEnd": 217.707,
  91. "requestTime": 2272960.91089,
  92. "sendEnd": 97.25,
  93. "sendStart": 97.03,
  94. "sslEnd": 96.829,
  95. "sslStart": 55.726,
  96. "workerFetchStart": -1,
  97. "workerReady": -1,
  98. "workerRespondWithSettled": -1,
  99. "workerStart": -1
  100. },
  101. "url": "https://item.m.jd.com/product/10052422060501.html"
  102. },
  103. "timestamp": 2272961.1294,
  104. "type": "Document"
  105. }
  106. },
  107. "webview": "78D9BC6F0CBE162DA6779F410AA1500C"
  108. }

完整代码:

  1. var option = new ChromeOptions();
  2. option.SetLoggingPreference("performance",OpenQA.Selenium.LogLevel.Info); //启用performance日志,等级为Info即可
  3. option.PerformanceLoggingPreferences = new ChromiumPerformanceLoggingPreferences() {
  4. IsCollectingNetworkEvents = true //采集网络请求事件
  5. };
  6. using (ChromeDriver driver = new ChromeDriver(driverPath,option,TimeSpan.FromSeconds(5))) {
  7. driver.Navigate().GoToUrl("https://item.m.jd.com/product/10052422060501.html");
  8. Thread.Sleep(3 * 1000); //等待页面加载完成
  9. var logs = driver.Manage().Logs.GetLog("performance").Where(o => o.Message.Contains("\"Network.responseReceived\""));//获取所有performance日志,并过滤出所有类型为Network.responseReceived的日志
  10.    foreach (var log in logs) {
  11.       //日志找不到就会抛出异常,必须要捕获异常
  12.       try {
  13.         var json = JObject.Parse(log.Message);
  14.         var url = json["message"]["params"]["response"]["url"].ToString(); //请求url,可通过url过滤出你要的请求
  15.         var requestId = json["message"]["params"]["requestId"].ToString();
  16.         //利用RequestId做为参数,执行CDP命令获取日志详细内容。 踩坑警告:返回的是一个字典,需要转换为Dictionary<string,object>
  17.         var response = driver.ExecuteCdpCommand("Network.getResponseBody",new Dictionary<string,object>() {{ "requestId",requestId }}) as Dictionary<string,object>;
  18.         if (response.TryGetValue("body",out object? bodyObj) && bodyObj != null) {
  19.           string body = bodyObj.ToString();
  20.           Console.WriteLine($"输出Body内容:{body}");
  21.         }
  22.       } catch(Exception ex){
  23.         //记录错误日志
  24.       }
  25.    }
  26. }

封装

为了以后方便复用,我封装了一个类

其中过滤条件的入参,可以根据实际情况自行修改,如果不需要对body内容进行过滤可以去掉这个参数,这样就不用每次等待拿到结果再过滤,性能会好很多

  1.    /// <summary>
  2. /// Selenium网络请求日志帮助类
  3. /// </summary>
  4. public class NetworkLoggingHelper
  5. {
  6. static readonly Logger logger = NlogProvider.GetLogger();
  7.  
  8. /// <summary>
  9. /// 开启网络请求日志
  10. /// </summary>
  11. /// <param name="option"></param>
  12. public static void OpenNetworkPerformanceLogging(ref ChromeOptions option)
  13. {
  14. option.SetLoggingPreference("performance",OpenQA.Selenium.LogLevel.Info);
  15. option.PerformanceLoggingPreferences = new ChromiumPerformanceLoggingPreferences() {
  16. IsCollectingNetworkEvents = true
  17. };
  18. }
  19.  
  20. /// <summary>
  21. /// 获取网络请求数据
  22. /// </summary>
  23. /// <param name="driver"></param>
  24. /// <param name="filter">过滤条件 入参1:请求的url | 入参2:请求的mimeType | 入参3:请求的body</param>
  25. /// <returns></returns>
  26. public static Dictionary<string,string> GetNetworkApiDatas(ChromeDriver driver,Func<string,string,string,bool> filter)
  27. {
  28. Dictionary<string,string> datas = new Dictionary<string,string>();
  29. try {
  30. var logs = driver.Manage().Logs.GetLog("performance")?.Where(o => o.Message.Contains("\"Network.responseReceived\""));
  31. foreach (var log in logs) {
  32. try {
  33. var json = JObject.Parse(log.Message);
  34. if (json["message"]["params"] == null || json["message"]["params"]["response"] == null) {
  35. continue;
  36. }
  37. var url = json["message"]["params"]["response"]["url"].ToString();
  38. var mimeType = json["message"]["params"]["response"]["mimeType"].ToString();
  39. var requestId = json["message"]["params"]["requestId"].ToString();
  40. var response = driver.ExecuteCdpCommand("Network.getResponseBody",new Dictionary<string,object>() {
  41. { "requestId",requestId }
  42. }) as Dictionary<string,object>;
  43. if (response != null && response.Count > 0) {
  44. //是否base64编码
  45. var isBase64Encode = false;
  46. if (response.TryGetValue("base64Encoded",out object? base64Encoded) && base64Encoded != null) {
  47. isBase64Encode = (bool)base64Encoded;
  48. }
  49. //获取响应内容
  50. string body = string.Empty;
  51. if (response.TryGetValue("body",out object? bodyObj) && bodyObj != null) {
  52. body = bodyObj.ToString();
  53. if (isBase64Encode) {
  54. body = body.DecodeBase64(Encoding.UTF8);
  55. }
  56. }
  57. //根据条件过滤,如果不需要body内容参与过滤条件判断,这个if语句可以移到获取response的上面,性能会好很多
  58. if (filter.Invoke(url,mimeType,body)) {
  59. datas.Add(url,body);
  60. }
  61. }
  62. } catch (Exception ex) {
  63. logger.Error(ex.Message);
  64. }
  65. }
  66. } catch (Exception ex) {
  67. logger.Error($"获取日志失败:{ex.Message}");
  68. }
  69. return datas;
  70. }
  71. }

调用示例:

  1. var option = new ChromeOptions();
  2. NetworkLoggingHelper.OpenNetworkPerformanceLogging(ref option); //开启日志
  3. using (ChromeDriver driver = new ChromeDriver(driverPath,option,TimeSpan.FromSeconds(5))) {
  4. driver.Navigate().GoToUrl("https://item.m.jd.com/product/10052422060501.html");
  5. Thread.Sleep(3 * 1000); //等待页面加载完成
  6.  
  7. var datas = NetworkLoggingHelper.GetNetworkApiDatas(driver,(url,mimeType,body) => {
  8. return url.Contains("//item.m.jd.com/product/") || mimeType.Contains("application/json") || body.Contains("windows.itemInfo");
  9. });
  10. Console.WriteLine(datas.Count);
  11. }

Selenium工作原理

叙述一下selenium工作的过程

1.selenium client(python等语言编写的自动化测试脚本)初始化一个service服务,通过Webdriver启动浏览器驱动程序chromedriver.exe

2.通过RemoteWebDriver向浏览器驱动程序发送HTTP请求,浏览器驱动程序解析请求,打开浏览器,并获得sessionid,如果再次对浏览器操作需携带此id

3.打开浏览器,绑定特定的端口,把启动后的浏览器作为webdriver的remote server

3.打开浏览器后,所有的selenium的操作(访问地址,查找元素等)均通过RemoteConnection链接到remote server,然后使用execute方法调用_request方法通过urlib3向remote server发送请求

4.浏览器通过请求的内容执行对应动作

5.浏览器再把执行的动作结果通过浏览器驱动程序返回给测试脚本

6.  webdriver.是 w3c 的标准协议。提供一组接口,用于发现和操作web文档中的DOM元素。

webdriver.是一系列的API.它给测试代码提供了定位和操作 wEB元素的能力。不同的开发语言有相应的wsbdriver.

↑↑↑↑ 该部分内容来自:https://www.cnblogs.com/xrxc/p/14776895.html

作者:Harry

原文出处:https://www.cnblogs.com/simendancer/articles/16546199.html

有些文本描述和图片源自网络,如有侵犯请私信告知

C#爬虫之通过Selenium获取浏览器请求响应结果的更多相关文章

  1. VS2008 C++ 利用WinHttp API获取Http请求/响应头部Header

    http://www.cnblogs.com/LCCRNblog/p/3833472.html 这一篇博客中,实现了获取http请求/响应后的html源码,现在需要获取http请求/响应的头部Head ...

  2. Spider-Python爬虫之使用Selenium模拟浏览器行为

    分析 他的代码比较简单,主要有以下的步骤:使用BeautifulSoup库,打开百度贴吧的首页地址,再解析得到id为new_list标签底下的img标签,最后将img标签的图片保存下来. header ...

  3. selenium.获取浏览器大小、设置浏览器位置、最大化浏览器

    此篇博客学习控制浏览器的api,分别有: get_window_size() 获取浏览器大小 set_window_size() 设置浏览器位置 get_window_position() 获取浏览器 ...

  4. LoadRunner 获取接口请求响应信息

    Action() { int nHttpRetCode; // 默认最大长度为256,get请求需注意缓存问题,需要根据content-length进行修改 web_set_max_html_para ...

  5. [转] LoadRunner 获取接口请求响应信息

    Action() { int nHttpRetCode; // 默认最大长度为256,get请求需注意缓存问题,需要根据content-length进行修改 web_set_max_html_para ...

  6. 爬虫模块介绍--selenium (浏览器自动化测试工具,模拟可以调用浏览器模拟人操作浏览器)

    selenium主要的用途就是控制浏览器,模仿真人操作浏览器的行为 模块安装:pip3 install selenium 需要控制的浏览器 from selenium import webdriver ...

  7. 爬虫实战--使用Selenium模拟浏览器抓取淘宝商品美食信息

    from selenium import webdriver from selenium.webdriver.common.by import By from selenium.common.exce ...

  8. [技巧篇]11.JavaScript原生态如何获取浏览器请求地址中的参数

    var getAccessParams = function(){ var i,ilen,strs,keyName,keyValue, params={}, path = window.locatio ...

  9. selenium获取浏览器控制台日志

    public void logsTest(){ WebDriver driver = null; try { System.setProperty("webdriver.chrome.dri ...

随机推荐

  1. Java获取特定区间随机数及产生不重复随机数

    问题 有这样一种需求,在这样一个数组中String[] arr = new String[]{"电商", "互联网", "小程序", &qu ...

  2. 第06组 Alpha冲刺 (2/6)

    目录 1.1 基本情况 1.2 冲刺概况汇报 1.郝雷明 2. 方梓涵 3. 黄少丹 4. 董翔云 5.曾丽莉 6. 詹鑫冰 7.鲍凌函 8.杜筱 9.曹兰英 10. 吴沅静 1.3 冲刺成果展示 1 ...

  3. 测试平台系列(94) 前置条件该怎么支持Python呢

    回顾 上一节我们狠狠操练了一番oss,但我们的任务还很长久,所以我们需要继续打磨我们的功能. 那今天就让我们来思考下,如何在前置条件支持python脚本,多的不说,我们也暂时不考虑其他语言,因为光考虑 ...

  4. Linux查看系统参数配置

    Linux查看系统参数 1.查看内存(以GB为单位) [root@rac1 ~]# free -g total :内存总数,物理内存总数 used :已使用内存 free :空闲的内存数 shared ...

  5. Nginx安装及支持https代理配置和禁用TSLv1.0、TSLv1.1配置

    Linux安装Nginx Nginx安装及支持https代理配置和禁用TSLv1.0.TSLv1.1配置. 下载安装包 [root@localhost ~]# wget http://nginx.or ...

  6. WIN32 API 获取文件版本信息

    CString strVersion; CString strPath(_T("xxxxxxxx.exe")); // 读文件信息 DWORD dwVerHnd = 0; DWOR ...

  7. VMware虚拟机基于contos 7 搭建lnmp环境全过程

    这个环境也整了几次了,由于本人比较懒,没有记住.找资料很麻烦,就自己动手咯 1.下载VMware虚拟机   (有注册码)     地址:http://www.zdfans.com/5928.html ...

  8. C#中的枚举器

    更新记录 本文迁移自Panda666原博客,原发布时间:2021年6月28日. 一.先从可枚举类型讲起 1.1 什么是可枚举类型? 可枚举类型,可以简单的理解为: 有一个类,类中有挺多的数据,用一种统 ...

  9. 高通sensor理解

    .1.高通为什么引入adsp? 2.adsp sensor 是如何工作起来的? 3.adsp 和ap 是如何通信的? 4.adsp 架构组成 解答: 1.高通在msm8960之前sensor 是挂在p ...

  10. C# 读写文件从用户态切到内核态,到底是个什么流程?

    一:背景 1. 一个很好奇的问题 我们在学习 C# 的过程中,总会听到一个词叫做 内核态 ,比如说用 C# 读写文件,会涉及到代码从 用户态 到 内核态 的切换,用 HttpClient 获取远端的数 ...