1.å¦ä½å©ç¨CEF3å建ä¸ä¸ªç®åçåºç¨ç¨åº
2.浅析Selenium-WebDriver
3.使用CEF(四)— 在QT中集成CEF(1)基本集成
4.cef是面源码什么文件夹
5.JavaScript 获取整个网页的完整内容,包括 html 标签。面源码
6.chromium+embedded+framework能卸载吗?
å¦ä½å©ç¨CEF3å建ä¸ä¸ªç®åçåºç¨ç¨åº
cefsimple项ç®ä¸é»è®¤å è½½çURLæ¯ google.comï¼å½ç¶ï¼ä½ ä¹å¯ä»¥ç¨èªå®ä¹ç URL å»æ¿ä»£å®ï¼ææ¹ä¾¿çå°±æ¯éè¿å½ä»¤è¡æå®ã
# Load the local file âc:\example\example.htmlâ
cefsimple.exe --url=file://c:/example/example.html
é¤äºå½ä»¤è¡çæ¹æ³ï¼ä¹å¯ä»¥éè¿ç´æ¥ä¿®æ¹å¨ cefsimple/simple.cpp æ件ä¸ç代ç ï¼è¾¾å°ä½ çç®çã
# Load the local file âc:\example\example.htmlâ
â¦
if (url.empty())
url = file://c:/example/example.html;
åºç¨ç¨åºç»æ
ææç CEF åºç¨ç¨åºé½æä¸ä¸ä¸»è¦ç»æé¨åï¼
CEF çå¨æé¾æ¥åº ã(å¨ Windows å¹³å°ä¸å°±æ¯ libcef.dll)
æ¯æåºãï¼ICU,面源码 FFMPEGçï¼
èµæºã(html/js/css, stringsç)
客æ·ç«¯æ§è¡æ件ã(æ¬æç¨ä¸å°±æ¯ cefsimple.exe.)
è¦ç¹ï¼å¿ çï¼
CEF 使ç¨çæ¯å¤è¿ç¨ãåºç¨ç¨åºä¸»è¿ç¨æ¯æµè§å¨è¿ç¨ï¼èå ¶ä»åè¿ç¨æ¯ç± renderer, plugins, GPUçå建ã
å¨ Windows å Linux å¹³å°ä¸çæ§è¡æ件å¯ä»¥è¢«ä¸»è¿ç¨ååè¿ç¨ä½¿ç¨ã
CEF ä¸ææè¿ç¨é½å¯ä»¥æ¯å¤çº¿ç¨çãCEFæä¾äºè®¸å¤åè½åæ¥å£å¨ä¸åç线ç¨ä¸ä¼ éä»»å¡ã
ä¸äºåè°æ¹æ³åå½æ°åªè½å¨ç¹å®çè¿ç¨æè 线ç¨ä¸ä½¿ç¨ãå¨ä½ 第ä¸æ¬¡ä½¿ç¨æ°çåè°æ¹æ³æè å½æ°ä¹åï¼è¯·ç¡®ä¿ä½ å·²ç»é è¯»äº API 头æ件ä¸æºç ï¼ç使ç¨è¦æ±ã
æµç¨åæ
cefsimple åºç¨ç¨åºé¦å åå§åCEFï¼ç¶åå建äºä¸ä¸ªç®åçå¼¹åºæµè§å¨çªå£ãå½å ³éäºææçæµè§å¨çªå£ï¼åºç¨ç¨åºå°±ä¼ç»æãç¨åºæ§è¡æµç¨å¦ä¸ï¼
ç³»ç»æ§è¡å ¥å£ç¹å½æ°(main or wWinMain)ï¼å¹¶å建æµè§å¨è¿ç¨ã
å ¥å£ç¹å½æ°ï¼
å建è½å¤å¤çè¿ç¨çº§å«çåè°æ¹æ³ç SimpleApp å®ä¾ã
åå§å CEFï¼è¿å ¥ CEF æ¶æ¯å¾ªç¯ã
åå§å CEF ä¹åï¼è°ç¨ SimpleApp::OnContextInitialized() ãè¿ä¸ªæ¹æ³ä¸ï¼
å建åä¾ç SimpleHandler ã
ç± CefBrowserHost::CreateBrowsersync() æ¹æ³å建ä¸ä¸ªæµè§å¨çªå£ã
ææçæµè§å¨å ±äº« SimpleHandler å®ä¾ï¼ æ¤å®ä¾è½å®å¶æµè§å¨è¡ä¸ºãå¤çæµè§å¨ç¸å ³åè°æ¹æ³(life span, loading state, title displayç)ã
å½ä¸ä¸ªæµè§å¨çªå£å ³éçæ¶åï¼è°ç¨ SimpleHandler::OnBeforeClose() ãå½ææçæµè§å¨çªå£å ¨é¨å ³éæ¶ï¼OnBeforeClose() å½æ°å°±ä¼æ§è¡è·³åº CEF æ¶æ¯å¾ªç¯çè¡ä¸ºï¼éåºåºç¨ç¨åºã
å ¥å£ç¹å½æ°
ç¨åºçè¿è¡å¼å§äºæµè§å¨è¿ç¨ä¸çå ¥å£ç¹å½æ°ãè¿ä¸ªå½æ°ä¼åå§å CEF 以åææè·æä½ç³»ç»æå ³ç对象ã
Windows
#include <windows.h>
#include "cefsimple/simple_app.h"
#include "include/cef_sandbox_win.h"
// Set to 0 to disable sandbox support.
#define CEF_ENABLE_SANDBOX 1
#if CEF_ENABLE_SANDBOX
// The cef_sandbox.lib static library is currently built with VS. It may not
// link successfully with other VS versions.
#pragma comment(lib, "cef_sandbox.lib")
#endif
// Entry point function for all processes.
int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow) {
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
void* sandbox_info = NULL;
#if CEF_ENABLE_SANDBOX
// Manage the life span of the sandbox information object. This is necessary
// for sandbox support on Windows. See cef_sandbox_win.h for complete details.
CefScopedSandboxInfo scoped_sandbox;
sandbox_info = scoped_sandbox.sandbox_info();
#endif
// Provide CEF with command-line arguments.
CefMainArgs main_args(hInstance);
// SimpleApp implements application-level callbacks. It will create the first
// browser instance in OnContextInitialized() after CEF has initialized.
CefRefPtr<SimpleApp> app(new SimpleApp);
// CEF applications have multiple sub-processes (render, plugin, GPU, etc)
// that share the same executable. This function checks the command-line and,
// if this is a sub-process, executes the appropriate logic.
int exit_code = CefExecuteProcess(main_args, app.get(), sandbox_info);
if (exit_code >= 0) {
// The sub-process has completed so return here.
return exit_code;
}
// Specify CEF global settings here.
CefSettings settings;
#if !CEF_ENABLE_SANDBOX
settings.no_sandbox = true;
#endif
// Initialize CEF.
CefInitialize(main_args, settings, app.get(), sandbox_info);
// Run the CEF message loop. This will block until CefQuitMessageLoop() is
// called.
CefRunMessageLoop();
// Shut down CEF.
CefShutdown();
return 0;
}
SimpleApp
SimpleApp è´è´£å¤çè¿ç¨çº§å«çåè°æ¹æ³ãå®ä¼æé²åºä¸äºå¨å¤è¿ç¨ä¸å ±äº«æè 被ç¹å®è¿ç¨ä½¿ç¨çæ¥å£åæ¹æ³ãCefBrowserProcessHandler æ¥å£ï¼å¨æµè§å¨è¿ç¨ä¸è°ç¨ãè¿æä¸ä¸ªè¢«åç¦»åº CefBrowserProcessHandler æ¥å£ï¼ä¾å项ç®æ²¡æå±ç¤ºï¼åªä¼å¨æ¸²æè¿ç¨ä¸è¢«è°ç¨ãç±äº CefBrowserProcessHandler ä¸å å®ç°äº CefApp, åæ¶è¿æ CefBrowserProcessHandlerï¼æ以å®çè¿åå¼å¿ é¡»æ¯[this]ã
// simple_app.h
#include "include/cef_app.h"
class SimpleApp : public CefApp,
public CefBrowserProcessHandler {
public:
SimpleApp();
// CefApp methods:
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler()
OVERRIDE { return this; }
// CefBrowserProcessHandler methods:
virtual void OnContextInitialized() OVERRIDE;
private:
// Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(SimpleApp);
};
// simple_app.cpp
#include "cefsimple/simple_app.h"
#include <string>
#include "cefsimple/simple_handler.h"
#include "cefsimple/util.h"
#include "include/cef_browser.h"
#include "include/cef_command_line.h"
SimpleApp::SimpleApp() {
}
void SimpleApp::OnContextInitialized() {
REQUIRE_UI_THREAD();
// Information used when creating the native window.
CefWindowInfo window_info;
#if defined(OS_WIN)
// On Windows we need to specify certain flags that will be passed to
// CreateWindowEx().
window_info.SetAsPopup(NULL, "cefsimple");
#endif
// SimpleHandler implements browser-level callbacks.
CefRefPtr<SimpleHandler> handler(new SimpleHandler());
// Specify CEF browser settings here.
CefBrowserSettings browser_settings;
std::string url;
// Check if a "--url=" value was provided via the command-line. If so, use
// that instead of the default URL.
CefRefPtr<CefCommandLine> command_line =
CefCommandLine::GetGlobalCommandLine();
url = command_line->GetSwitchValue("url");
if (url.empty())
url = "xxx";
// Create the first browser window.
CefBrowserHost::CreateBrowserSync(window_info, handler.get(), url,
browser_settings, NULL);
}
SimpleHandler
SimpleHandler è´è´£å¤çæµè§å¨çº§å«çåè°æ¹æ³ãè¿äºåè°æ¹æ³ä¼å¨æµè§å¨è¿ç¨ä¸æ§è¡ãå¨è¿ä¸ªé¡¹ç®ä¸ï¼é对ææçæµè§å¨ä½¿ç¨ç¸åç CefClient å®ä¾ï¼ä½æ¯å¦æä½ æ¿æï¼å¯ä»¥å¨èªå·±çåºç¨ç¨åºä¸ä½¿ç¨ä¸åç CefClientå®ä¾çã
// simple_handler.h
#include "include/cef_client.h"
#include <list>
class SimpleHandler : public CefClient,
public CefDisplayHandler,
public CefLifeSpanHandler,
public CefLoadHandler {
public:
SimpleHandler();
~SimpleHandler();
// Provide access to the single global instance of this object.
static SimpleHandler* GetInstance();
// CefClient methods:
virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() OVERRIDE {
return this;
}
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE {
return this;
}
virtual CefRefPtr<CefLoadHandler> GetLoadHandler() OVERRIDE {
return this;
}
// CefDisplayHandler methods:
virtual void OnTitleChange(CefRefPtr<CefBrowser> browser,
const CefString& title) OVERRIDE;
// CefLifeSpanHandler methods:
virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE;
virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
// CefLoadHandler methods:
virtual void OnLoadError(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
ErrorCode errorCode,
const CefString& errorText,
const CefString& failedUrl) OVERRIDE;
// Request that all existing browser windows close.
void CloseAllBrowsers(bool force_close);
private:
// List of existing browser windows. Only accessed on the CEF UI thread.
typedef std::list<CefRefPtr<CefBrowser> > BrowserList;
BrowserList browser_list_;
// Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(SimpleHandler);
};
// simple_handler.cpp
#include "cefsimple/simple_handler.h"
#include <sstream>
#include <string>
#include "cefsimple/util.h"
#include "include/cef_app.h"
#include "include/cef_runnable.h"
namespace {
SimpleHandler* g_instance = NULL;
} // namespace
SimpleHandler::SimpleHandler() {
ASSERT(!g_instance);
g_instance = this;
}
SimpleHandler::~SimpleHandler() {
g_instance = NULL;
}
// static
SimpleHandler* SimpleHandler::GetInstance() {
return g_instance;
}
void SimpleHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
REQUIRE_UI_THREAD();
// Add to the list of existing browsers.
browser_list_.push_back(browser);
}
void SimpleHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
REQUIRE_UI_THREAD();
// Remove from the list of existing browsers.
BrowserList::iterator bit = browser_list_.begin();
for (; bit != browser_list_.end(); ++bit) {
if ((*bit)->IsSame(browser)) {
browser_list_.erase(bit);
break;
}
}
if (browser_list_.empty()) {
// All browser windows have closed. Quit the application message loop.
CefQuitMessageLoop();
}
}
void SimpleHandler::OnLoadError(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
ErrorCode errorCode,
const CefString& errorText,
const CefString& failedUrl) {
REQUIRE_UI_THREAD();
// Don't display an error for downloaded files.
if (errorCode == ERR_ABORTED)
return;
// Display a load error message.
std::stringstream ss;
ss << "<html><body bgcolor=\"white\">"
"<h2>Failed to load URL " << std::string(failedUrl) <<
" with error " << std::string(errorText) << " (" << errorCode <<
").</h2></body></html>";
frame->LoadString(ss.str(), failedUrl);
}
浅析Selenium-WebDriver
年,当时在ThoughtWorks工作的面源码Jason Huggins开发了Selenium(Selenium RC)的第一版。年,面源码Google工程师基于Selenium开发了WebDriver。面源码源码研习社年,面源码Selenium和WebDriver合并,面源码形成了Selenium2(Selenium WebDriver)。面源码目前,面源码Selenium WebDriver的面源码模式已经升级到Selenium4,并有一个支线项目Selenium-Grid,面源码能够与Selenium配合进行多任务运行(主要针对分布式执行,面源码对于当前业务现状,面源码使用到的面源码可能性很小,本文不展开讲解)。
使用现状:虽然无法直接统计出每个公司的使用现状,但我们可以通过搜索趋势来侧面验证。通过Google Trends查询的结果显示,Selenium WebDriver主导的方案占据主流地位,而Selenium RC的方案正在逐步被淘汰。
Selenium RC:
组成部分:Selenium RC主要由客户端和服务器两部分组成。
工作原理:Selenium RC通过发送HTTP请求与服务器进行通信,服务器再将请求转发给浏览器执行。
缺点:Selenium RC的执行速度较慢,且需要各个浏览器厂商提供支持。
Selenium WebDriver:
组成部分:WebDriver主要由WebDriver接口和对应的浏览器驱动程序组成。
Web Driver:WebDriver提供了另一种与浏览器交互的浏览器打开网页源码乱码方式,即利用浏览器原生的API,封装成一套面向对象的Selenium WebDriver API,直接操作浏览器页面里的元素,甚至操作浏览器本身(截屏、窗口大小、启动、关闭、安装插件、配置证书等)。由于使用的是浏览器的原生API,速度大大提高,但缺点是需要各个浏览器厂商各自提供。
各种编程语言编写的客户端:向remote server发起请求。
工作原理:底层通信包含以下两个过程:
Selenium -> ChromeDriver server:这个通信过程是基于HTTP协议。
例如,我们要打开一个浏览器页面,并访问www.google.com,先看下Selenium源码是怎么实现这个过程的。
首次建立连接的过程:
选择一个空闲的端口启动chromedriver。
具体发请求的接口:
最终的收口就是_request,发起一个blogs.com/uncleyong/p...
[8] cloud.tencent.com/devel...
[9] einverne.gitbook.io/sel...
使用CEF(四)— 在QT中集成CEF(1)基本集成
在QT,一个强大的跨平台C++开发框架,上构建基于CEF(Chrome Embedded Framework)的应用程序,我们之前的文章已经展示了基础的样例,但这些示例主要依赖原生或功能有限的CEF视图。本文将重新开始,通过VS,程序员为什么要读源码实现一个结合QT和原生窗体的集成项目。环境搭建
本文不依赖QtCreator,而是使用VS和Qt VS Tools来构建环境,前提是你已安装并熟悉QT。安装Qt VS Tools插件
在VS扩展中搜索并安装相应的QT插件,安装后别忘了重启VS。配置Qt环境
在Extensions - Qt VS Tools - Options中,配置Qt-VS编译设置。创建Qt项目
在配置完成后,通过VS创建项目,Qt模板将自动出现。我们创建名为QtCefDemo的项目,Qt创建向导随即启动。项目结构
创建完成后,项目的文件结构如下,展示了一个基本的VS IDE下的QT项目。集成CEF环境
为了集成CEF,我们首先需要编译并准备相应的头文件、库文件和资源文件,包括libcef_dll_wrapper.lib(MDd和MD版本)。项目目录结构
按照步骤,你会看到项目的文件夹结构,包括CefFiles文件夹及其内部的头文件、库文件和资源。配置manifest文件和VS设置
为CEF项目配置manifest文件,以及头文件和库文件的路径,这里会根据Debug和Release环境自动调整。可导出源码的h5工具集成完成后的问题与解决方案
在集成过程中,可能会遇到运行时的库文件问题。需要手动或通过脚本将相关文件复制到输出目录。初始运行可能存在问题,但后续文章将深入探讨解决方法。源码和资源
项目源码可以在我的GitHub仓库找到,链接在此省略。此外,需要下载CefFiles文件夹,可以从网盘获取,链接同样省略。cef是什么文件夹
CEF是Chromium Embedded Framework的缩写,是基于Google Chrominum项目开发的一个web浏览器控件。
CEF是开源的,可以下载源码,去掉自己不需要的模块,然后自己编译,也可以直接使用CEF提供的库,CEF提供的库是libcef.dll,很多程序都有libcef.dll这个动态库,比如qq和evernote,但是大小都不一样,估计是自己裁剪过然后编译的。
JavaScript 获取整个网页的完整内容,包括 html 标签。
在探索网页内容的世界里,JavaScript 提供了强大的益学堂主力资金线指标源码工具来抓取我们需要的信息。首先,让我们聚焦于获取 body 标签内的核心内容,这是通过简单的 document.body.innerHTML</操作实现的,它能直接揭示页面的基础结构。 然而,如果你追求的是更全面的网页解析,那么仅仅触及 body 标签是不够的。这时候,document.documentElement.outerHTML</这一命令就显得尤为重要。它能帮你获取到整个 HTML 的结构,包括 head 和 html 标签,是抓取网页全貌的关键。 然而,CefSharp 的使用场景稍有不同,它基于 ChromiumWebBrowser 类型的 GetSourceAsync 方法,为我们提供了获取源代码的途径。这个方法返回的字符串确实包含了 HTML 标签,但动态生成的内容却未被囊括其中。如果你的目标是包括那些动态生成的部分,比如在数据采集中,那就需要采取更为灵活的策略。 为此,我们设计了一个实用的静态方法 GetDocumentHtml</,它巧妙地结合了 CefSharp 的功能。这个方法首先尝试通过 browser.EvaluateScriptAsync</执行 JavaScript 代码,试图获取动态渲染的 HTML,如果成功,就返回解析的结果;如果遇到动态内容的壁垒,它会退而求其次,利用 GetSourceAsync 方法获取完整的源代码,确保你得到的是网页的完整视图。通过这段代码,无论是静态还是动态的网页内容,都能在 JavaScript 的指引下,被准确无误地捕获,满足你对网页数据深度挖掘的需求。
chromium+embedded+framework能卸载吗?
Chromium Embedded Framework(CEF)本身是一个开源的框架,可以通过GitHub等开源平台获取其源代码。由于其是一个框架,因此不是一个独立的应用程序,而是被其他应用程序(例如Web浏览器)所使用。因此,CEF本身无法被卸载。
如果您想卸载使用CEF构建的应用程序,可以通过以下方式进行:
在操作系统中找到该应用程序的卸载程序,一般可以在“控制面板”或者应用程序的安装目录中找到。运行卸载程序后,该应用程序及其使用的CEF框架将被卸载。
如果无法通过卸载程序进行卸载,可以尝试使用第三方卸载工具,例如Revo Uninstaller等。
需要注意的是,在卸载使用CEF构建的应用程序之前,请确保该应用程序中的所有数据已经备份或者转移至其他地方,以免造成数据丢失。
使用CEF(二)— 基于VS编写一个简单CEF样例
在这一章节中,我们将基于VS构建一个简单的CEF(Chromium Embedded Framework)示例。首先,确保你已经阅读过《使用CEF(1)— 起步》,或了解如何获取libcef库以及libcef_dll_wrapper静态库。
接下来,我们将在Debug模式下进行代码开发。若选择Debug或Release模式,确保与libcef库和libcef_dll_wrapper完全一致。创建名为cef的文件夹,将所有相关文件放入其中。之后,创建一个名为simple-cef的C++Windows Desktop Application项目。
删除模板生成的代码,得到一个空白的应用程序。接下来,我们需要准备几个关键步骤:添加头文件,配置项目依赖,以及编写代码。
引入正确的头文件是关键。将cef文件夹包含在项目中,通过在VS中设置Additional Include Directories为$(ProjectDir)cef/include,确保头文件正确加载。添加libcef_dll_wrapper.lib、libcef.lib、cef_sandbox.lib等静态库到Additional Dependencies中。
现在,项目已完成编译和链接配置。接下来,通过cefsimple代码解释如何使用CEF,并适当添加细节。
在simple_app.h和simple_app.cpp中,实现CefApp和CefClient概念,并通过CefWindowDelegate和CefBrowserViewDelegate提供图形视图框架。同时,编写simple_client.cpp和simple_client_os_win.cpp以处理不同平台的具体细节。
最后,编写main.cpp作为入口代码。确保代码结构清晰,包括编译和运行步骤。在运行过程中,可能遇到编译和链接的不匹配问题,通过调整项目的Runtime Library设置为MTd以匹配libcef_dll_wrapper.lib。
在遇到运行问题时,如白屏和错误信息时,创建simple-cef.manifest文件,并将其添加到项目的Additional Manifest Files中。这将帮助解决跨平台版本兼容性问题,最终使页面正常加载。
通过这个示例,我们不仅实现了CEF应用的基本功能,还深入理解了CefApp、CefClient、CefWindowDelegate、CefBrowserViewDelegate等关键组件及其作用。CEF跨平台的实现策略、框架的内部工作原理等问题仍然值得进一步探索和研究。
源代码请自行编译并根据指定方式添加依赖文件。
使用CEF(三)— 从CEF官方Demo源码入手解析CEF架构与CefApp、CefClient对象
在《使用CEF(2)— 基于VS编写一个简单CEF样例》中,介绍了使用简单样例进行CEF(Common Engineering Framework)编程,其中涉及到了CefApp、CefClient等类。本文将深入解析这些概念,以及CEF的进程架构。
CEF3使用多进程模型,主进程负责窗口创建、绘制和网络访问,被称为“浏览器”进程。通常与宿主应用程序进程相同,多数应用逻辑在浏览器进程中执行。渲染和JavaScript执行在单独的“渲染”进程中进行。一些应用逻辑,如JavaScript绑定和DOM访问,在渲染进程中执行。默认情况下,每个唯一源(scheme + domain)运行一个新渲染进程。根据需要,还会生成插件进程处理Flash等,以及GPU进程处理加速合成。
浏览器进程(Browser Process)负责创建窗口和渲染HTML内容。渲染进程(Renderer Process)则执行Blink渲染和JavaScript执行。两者分别运行,确保高效率的多任务处理和资源管理。
在官方提供的Demo(cefsimple)中,将从源码解析Cef概念,重点是CefApp和CefClient。CefApp接口提供进程特定回调访问,CefClient接口提供浏览器实例特定回调访问,一个CefApp实例对应一个进程,CefClient实例可以共享多个浏览器实例。
cefsimple示例代码展示了如何创建CefApp和CefClient实例,并通过它们管理浏览器进程和渲染进程之间的交互。CefApp实例通过GetBrowserProcessHandler和GetRendererProcessHandler方法获取相关处理器。CefClient实例则用于处理浏览器实例间的事件回调,如生命周期变化、显示事件等。
通过理解CefApp和CefClient的作用,可以更好地掌握CEF架构及其在实际应用中的应用。官方文档提供了更深入的指南和详细信息,推荐进一步学习。