资讯专栏INFORMATION COLUMN

【譯】Headless Chrome 入門指南

toddmark / 992人阅读

摘要:確切位置因平台而異。如果以編程方式使用,這個頁面也是一個強大的調試工具,能看到所有原始的協議命令通過連線,於瀏覽器進行通信。警告協議可以做很多有趣的事,但作為入門選項他令人沮喪。目前,提供了比協議高級別的。

本文翻譯自:Getting Started with Headless Chrome
原文更新時間:July 28,2017
作者:Eric Bidelman(Engineer @ Google. Working on Chrome & the web.)
譯者:Pandorym

Headless Chrome由 Chrome 59 帶來。這是一種在無界面環境中運行 Chrome 瀏覽器的方法。本質上,就是運行無界面的的 Chrome !他為命令行帶來了 Chromium 和 Blink 渲染引擎提供的所有現代的 Web 平台特性

他有什麼用呢?

用於對於自動化測試和不需要能看到UI外殼的服務器環境,Headless 瀏覽器是一個極好的工具。舉個例子,你可能要對一個真實的 Web 頁面進行一些測試,為它創建一個 PDF ,或者就是檢查噶瀏覽器如何渲染一個 URL 。

提示:Headless模式在Mac和Linux上是Chrome 59可用。Windows的支持將在Chrome 60到來。你可以打開chrome://version檢查當前版本。

開啟 Headless(CLI)

啟動 Headless 模式最簡單的方式四在命令行中打開 Chrome 二進制文件。如果你已經安裝Chrome 59+,攜帶--headless標識啟動Chrome:

chrome 
  --headless                    # 使用 headless 模式.
  --disable-gpu                 # 現在暫時還需要.
  --remote-debugging-port=9222  
  https://www.chromestatus.com   # 將打開的 URL. 默認打開 about:blank.

注意:現在,你需要包含--disable-gpu參數。不過這個參數最終會被丟棄。

chrome需要指向你安裝的 Chrome。確切位置因平台而異。由於我在 Mac 上,我為我安裝的每個版本的 Chrome 都創建了方便的別名。

如果你在Chrome的穩定版頻道不能進行這個測試,我推薦使用chrome-canary

alias chrome="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
alias chrome-canary="/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"
alias chromium="/Applications/Chromium.app/Contents/MacOS/Chromium"

從 這裡 下載 Chrome Canary。

命令行特性

在某些情況下,你可能不需要以編程方式編寫 Headless Chrome。這是一些對執行普通任務有用的命令行標識。

打印 DOM

--dump-dom標識,打印document.body.innerHTML到標準輸出:

chrome --headless --disable-gpu --dump-dom https://www.chromestatus.com/
創建 PDF

--print-to-pdf標識,為頁面創建一個PDF文件:

chrome --headless --disable-gpu --print-to-pdf https://www.chromestatus.com/
取得截屏

要捕獲一個頁面的屏幕截圖,使用--screenshot標識:

# Size of a standard letterhead.
chrome --headless --disable-gpu --screenshot --window-size=1280,1696 https://www.chromestatus.com/

# Nexus 5x
chrome --headless --disable-gpu --screenshot --window-size=412,732 https://www.chromestatus.com/

使用--screenshot標識運行,將在當前工作目錄產生一個命名為screenshot.png的文件。如果你正在尋找完整的頁面截圖,事情會更複雜一些。這有一個 Davil Schnurr 發佈的優秀的Blog,包含你需要的大部分內容。查看:使用 Headless Chrome 作為自動截屏工具。

REPL 模式(read-eval-print loop)

--repl標識使 Headless 運行砸地一個特定的模式。你可以通過命令行,要求在瀏覽器中執行JS表達式:

$ chrome --headless --disable-gpu --repl https://www.chromestatus.com/
[0608/112805.245285:INFO:headless_shell.cc(278)] Type a Javascript expression to evaluate or "quit" to exit.
>>> location.href
{"result":{"type":"string","value":"https://www.chromestatus.com/features"}}
>>> quit
$
脫離瀏覽器界面調試Chrome

當你使用--remote-debug-port=9222運行 Chrome 時,他將啟動一個實例,并激活 DevTools 協議。這個協議用於連接 Chrome ,和驅動 Headless 瀏覽器實例。他也用於使用 Sublime,VS Code 和 Node 等工具遠程調試應用程序。#協同

由於你沒有瀏覽器 UI 查看頁面,在另一個瀏覽器進入http://localhost:9222以檢查他正在工作。你將在視察頁面看到一個列表,在這你可以通過點擊來查看 Headless 的渲染。

在這,你可以使用熟悉的 DevTools 特性像通常那樣去檢查、調試和調整頁面。如果以編程方式使用 Headless,這個頁面也是一個強大的調試工具,能看到所有原始的 DevTools 協議命令通過連線,於瀏覽器進行通信。

使用編程方式(Node) 啟動 Chrome

在之前的章節,我們使用--headless --remote-debugging-port=9222手動啟動Chrome。然後,為了完全自動化測試,你可能想在你的應用程序中生成Chrome。

一個方法是使用child-process

const execFile = require("child_process").execFile;

function launchHeadlessChrome(url, callback) {
  // Assuming MacOSx.
  const CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
  execFile(CHROME, ["--headless", "--disable-gpu", "--remote-debugging-port=9222", url], callback);
}

launchHeadlessChrome("https://www.chromestatus.com", (err, stdout, stderr) => {
  ...
});

但你想要一個跨多平台的解決方案時,事情就變得棘手了。就看看這硬編碼的 Chrome 路徑吧 :(

使用 ChromeLauncher

Lighthouse 是一個用於測試Web應用程序質量的極好的工具。有一個用於啟動 Chrome 的強大的模塊,之前被包含在 Lightouse 內部,但現在可以獨立使用了。chrome-launcher NPM 模塊將尋找 Chrome 的安裝位置、設置一個調試實例、啟動瀏覽器,并在你的應用程序完成時結束他。最優秀的是它跨多平台工作,感謝 Node !

默認的,chrome-launcher將啟動 Chrome Canary (如果安裝了),但你能手動更改選擇使用哪一個 Chrome。要使用他,先從 npm 安裝:

yarn add chrome-launcher

Example - 使用chrome-launcher啟動 Headless

const chromeLauncher = require("chrome-launcher");

// Optional: set logging level of launcher to see its output.
// Install it using: yarn add lighthouse-logger
// const log = require("lighthouse-logger");
// log.setLevel("info");

/**
 * Launches a debugging instance of Chrome.
 * @param {boolean=} headless True (default) launches Chrome in headless mode.
 *     False launches a full version of Chrome.
 * @return {Promise}
 */
function launchChrome(headless=true) {
  return chromeLauncher.launch({
    // port: 9222, // Uncomment to force a specific port of your choice.
    chromeFlags: [
      "--window-size=412,732",
      "--disable-gpu",
      headless ? "--headless" : ""
    ]
  });
}

launchChrome().then(chrome => {
  console.log(`Chrome debuggable on port: ${chrome.port}`);
  ...
  // chrome.kill();
});

運行這個腳本并不會做什麼事,但你應該在任務管理器中看到一個 Chrome 實例啟動,他載入了about:blank。記著,這不會有任何瀏覽器界面,We"re headless.

要控制瀏覽器,我們需要 DevTools 協議!

檢索關於界面的信息

chrome-remote-interface是一個很好的 Node 包,他提供了 DevTools 協議可用的 APIs。你可以使用他操作 Headless Chrome,跳轉到頁面,獲取關於頁面的相關信息。

警告:DevTools 協議可以做很多有趣的事,但作為入門選項他令人沮喪。我建議先花費一點時間瀏覽 DevTools Protocol Viewer,然後移步到chrome-remote-interface的API文檔,看看他是如何包裝原始協議的。

讓我們安裝這個庫:

yarn add chrome-remote-interface
Examples

Example - 打印 user agent

const CDP = require("chrome-remote-interface");

...

launchChrome().then(async chrome => {
  const version = await CDP.Version({port: chrome.port});
  console.log(version["User-Agent"]);
});

返回值大概像這樣:HeadlessChrome/60.0.3082.0

Example - 檢查這個網站是否存在 web app manifest

const CDP = require("chrome-remote-interface");

...

(async function() {

const chrome = await launchChrome();
const protocol = await CDP({port: chrome.port});

// Extract the DevTools protocol domains we need and enable them.
// See API docs: https://chromedevtools.github.io/devtools-protocol/
const {Page} = protocol;
await Page.enable();

Page.navigate({url: "https://www.chromestatus.com/"});

// Wait for window.onload before doing stuff.
Page.loadEventFired(async () => {
  const manifest = await Page.getAppManifest();

  if (manifest.url) {
    console.log("Manifest: " + manifest.url);
    console.log(manifest.data);
  } else {
    console.log("Site has no app manifest");
  }

  protocol.close();
  chrome.kill(); // Kill Chrome.
});

})();

Example - 使用 DOM APIs 取得頁面的</b></p> <pre>const CDP = require("chrome-remote-interface"); ... (async function() { const chrome = await launchChrome(); const protocol = await CDP({port: chrome.port}); // Extract the DevTools protocol domains we need and enable them. // See API docs: https://chromedevtools.github.io/devtools-protocol/ const {Page, Runtime} = protocol; await Promise.all([Page.enable(), Runtime.enable()]); Page.navigate({url: "https://www.chromestatus.com/"}); // Wait for window.onload before doing stuff. Page.loadEventFired(async () => { const js = "document.querySelector("title").textContent"; // Evaluate the JS expression in the page. const result = await Runtime.evaluate({expression: js}); console.log("Title of page: " + result.result.value); protocol.close(); chrome.kill(); // Kill Chrome. }); })(); </pre> <b>使用 Selenium,WebDriver,和ChromeDriver</b> <p>現在,Selenium打開了一個完整的 Chrome 實例。換句話說,這是一個自動化的解決方案,但不是完全 Headless。但是Selenium可以配置運行 Headless Chrome,只需少量配置即可。如果你想要配置的完全指南,我推薦 Running Selenium with Headless Chrome,但我已經在列出了一些例子,讓你可以快速開始。</p> <b>使用 ChromeDriver</b> <p>ChromeDriver 2.3.0 支持 Chrome 59 及之後的版本,並且工作於 Headless Chrome。在某些情況下,你可能需要 Chrome 60 來避免 Bugs。舉個例子,我們已經知道 Chrome 59 的獲取截圖存在問題。</p> <p>安裝:</p> <pre>yarn add selenium-webdriver chromedriver </pre> <p>Example:</p> <pre>const fs = require("fs"); const webdriver = require("selenium-webdriver"); const chromedriver = require("chromedriver"); // This should be the path to your Canary installation. // I"m assuming Mac for the example. const PATH_TO_CANARY = "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"; const chromeCapabilities = webdriver.Capabilities.chrome(); chromeCapabilities.set("chromeOptions", { binary: PATH_TO_CANARY // Screenshots require Chrome 60. Force Canary. "args": [ "--headless", ] }); const driver = new webdriver.Builder() .forBrowser("chrome") .withCapabilities(chromeCapabilities) .build(); // Navigate to google.com, enter a search. driver.get("https://www.google.com/"); driver.findElement({name: "q"}).sendKeys("webdriver"); driver.findElement({name: "btnG"}).click(); driver.wait(webdriver.until.titleIs("webdriver - Google Search"), 1000); // Take screenshot of results page. Save to disk. driver.takeScreenshot().then(base64png => { fs.writeFileSync("screenshot.png", new Buffer(base64png, "base64")); }); driver.quit(); </pre> <b>使用 WebDriverIO</b> <p>WebDriverIO 是一個 Selenium WebDirver 上的的高級別 API。</p> <p>安裝:</p> <pre>yarn add webdriverio chromedriver </pre> <p>Example - 過濾 chromestatus.com 上的 CSS 特性</p> <pre>const webdriverio = require("webdriverio"); const chromedriver = require("chromedriver"); // This should be the path to your Canary installation. // I"m assuming Mac for the example. const PATH_TO_CANARY = "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"; const PORT = 9515; chromedriver.start([ "--url-base=wd/hub", `--port=${PORT}`, "--verbose" ]); (async () => { const opts = { port: PORT, desiredCapabilities: { browserName: "chrome", chromeOptions: { binary: PATH_TO_CANARY // Screenshots require Chrome 60. Force Canary. args: ["--headless"] } } }; const browser = webdriverio.remote(opts).init(); await browser.url("https://www.chromestatus.com/features"); const title = await browser.getTitle(); console.log(`Title: ${title}`); await browser.waitForText(".num-features", 3000); let numFeatures = await browser.getText(".num-features"); console.log(`Chrome has ${numFeatures} total features`); await browser.setValue("input[type="search"]", "CSS"); console.log("Filtering features..."); await browser.pause(1000); numFeatures = await browser.getText(".num-features"); console.log(`Chrome has ${numFeatures} CSS features`); const buffer = await browser.saveScreenshot("screenshot.png"); console.log("Saved screenshot..."); chromedriver.stop(); browser.end(); })(); </pre> <b>更多資源</b> <p>這是一些有用的入門資源:</p> <p>文檔</p> <p><p>DevTools Protocol Viewer - API 參考文檔</p></p> <p>工具</p> <p><p>chrome-remote-interface - node 模塊,封裝了 DevTools 協議</p></p> <p><p>Lighthouse - Web app 質量自動化測試工具; 大量用於協議</p></p> <p><p>chrome-launcher - node 模塊, 啟動 Chrome, 準備自動化</p></p> <p>演示</p> <p><p>The Headless Web - Paul Kinlan 發佈的優秀的博客,關於使用 Headless。api.ai 也是域名。</p></p> <b>FAQ</b> <p><strong>我需要<b>--disable-gpu</b>標識嗎?</strong></p> <p>是的,現在需要。<b>--disable-gpu</b>標識是暫時處理一些 Bug 的必要條件。在未來的 Chrome 版本中不需要這個標識。看 https://crbug.com/546953#c152 和 https://crbug.com/695212 獲得更多信息。</p> <p><strong>所以我仍然需要 Xvfb?</strong></p> <p>不需要。Headless Chrome 不使用窗口,所以不再需要像 Xvfb 這樣的展示服務。你可以拋開它,開心地運行自動化測試。</p> <p>什麼是 Xvfb? Xvfb 是一個類 Unix 系統的內存顯示服務器,可以讓你運行圖形應用程序(如 Chrome),無需附加物理顯示器。許多人使用 Xvfb 運行早期版本的 Chrome 進行 「Headless」測試。</p> <p><strong>我要如何創建一個 Docker 容器運行 Headless Chrome?</strong></p> <p>查看 lighthouse-ci。他有一個例子 Dockerfile,它使用 Ubuntu 為基本映像,並在 APP Engine Flexible 容器中安裝、運行 Lightouse。</p> <p><strong>我可以和 Selenium / WebDriver / ChromeDriver 一起使用嗎?</strong></p> <p>可以,看上文「使用 Selenium,WebDriver,和ChromeDriver」。</p> <p><strong>他和 PhantomJS 有什麼關係?</strong></p> <p>Headless Chrome 是一個於 PhantomJS 相似的工具。兩者都可用於 Headless 環境下的自動化測試。兩者主要的區別在於,Phantom 使用舊版本的 WebKit 作為渲染引擎,Headless Chrome 使用最新版本的 Blink。</p> <p>目前,Phantom 提供了比 DevTools 協議 高級別的 API。</p> <p><strong>在哪提交 bugs?</strong></p> <p>對於 Headless Chrome 的 Bugs,crbug.com。<br>對於 DevTools 協議的 Bugs,github.com/ChromeDevTools/devtools-protocol。</p> </div> <div class="mt-64 tags-seach" > <div class="tags-info"> <a style="width:120px;" title="云服务器" href="https://www.ucloud.cn/site/active/kuaijiesale.html?ytag=seo">云服务器</a> <a style="width:120px;" title="GPU云服务器" href="https://www.ucloud.cn/site/product/gpu.html">GPU云服务器</a> <a style="width:120px;" title="Headless" href="https://www.ucloud.cn/yun/tag/Headless/">Headless</a> <a style="width:120px;" title="Chrome" href="https://www.ucloud.cn/yun/tag/Chrome/">Chrome</a> <a style="width:120px;" title="chrome cdn" href="https://www.ucloud.cn/yun/tag/chrome cdn/">chrome cdn</a> <a style="width:120px;" title="chrome+webrtc" href="https://www.ucloud.cn/yun/tag/chrome+webrtc/">chrome+webrtc</a> </div> </div> <div class="entry-copyright mb-30"> <p class="mb-15"> 文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。</p> <p>转载请注明本文地址:https://www.ucloud.cn/yun/84457.html</p> </div> <ul class="pre-next-page"> <li class="ellipsis"><a class="hpf" href="https://www.ucloud.cn/yun/84456.html">上一篇:babel知多少</a></li> <li class="ellipsis"><a class="hpf" href="https://www.ucloud.cn/yun/84458.html">下一篇:从零实现一个简单的 Promise</a></li> </ul> </div> <div class="about_topicone-mid"> <h3 class="top-com-title mb-0"><span data-id="0">相关文章</span></h3> <ul class="com_white-left-mid atricle-list-box"> <li> <div class="atricle-list-right"> <h2 class="ellipsis2"><a class="hpf" href="https://www.ucloud.cn/yun/84531.html"><b>【<em>譯</em>】<em>chrome</em>-remote-interface</b></a></h2> <p class="ellipsis2 good">摘要:該支持下列事件當到的連接已建立時觸發。取得該調試協議描述符。在關閉請求收到響應后執行,他將獲得下列參數一個對象,指明成功狀態當缺省時,將返回一個對象。當缺省時,將返回一個對象,並且狀態取決于屬性。 本文翻譯自:chrome-remote-interface原文更新時間:July 21,2017譯者:Pandorym Chrome 調試協議 的接口,他提供一個使用 JavaScript ...</p> <div class="com_white-left-info"> <div class="com_white-left-infol"> <a href="https://www.ucloud.cn/yun/u-125.html"><img src="https://www.ucloud.cn/yun/data/avatar/000/00/01/small_000000125.jpg" alt=""><span class="layui-hide64">lentoo</span></a> <time datetime="">2019-08-20 18:27</time> <span><i class="fa fa-commenting"></i>评论0</span> <span><i class="fa fa-star"></i>收藏0</span> </div> </div> </div> </li> <li> <div class="atricle-list-right"> <h2 class="ellipsis2"><a class="hpf" href="https://www.ucloud.cn/yun/84468.html"><b>【<em>譯</em>】<em>Chrome</em> Launcher npm v0.3.2</b></a></h2> <p class="ellipsis2 good">摘要:本文翻譯自原文更新時間譯者從輕鬆啟動。禁用了許多服務,他們對於自動化情景是無用的。自動定位二進制文件的位置進行啟動。每次啟動都使用一個新的,並在中清除它。對於可配置性的細節,提供一些設置選項。然後在中,像這樣使用它 本文翻譯自:Chrome Launcher原文更新時間:July 21,2017譯者:Pandorym 從 Node 輕鬆啟動 Google Chrome。 禁用了許多 ...</p> <div class="com_white-left-info"> <div class="com_white-left-infol"> <a href="https://www.ucloud.cn/yun/u-4.html"><img src="https://www.ucloud.cn/yun/data/avatar/000/00/00/small_000000004.jpg" alt=""><span class="layui-hide64">阿罗</span></a> <time datetime="">2019-08-20 18:25</time> <span><i class="fa fa-commenting"></i>评论0</span> <span><i class="fa fa-star"></i>收藏0</span> </div> </div> </div> </li> <li> <div class="atricle-list-right"> <h2 class="ellipsis2"><a class="hpf" href="https://www.ucloud.cn/yun/81577.html"><b>[<em>譯</em> + 補充] Webpack 2 <em>入門</em></b></a></h2> <p class="ellipsis2 good">摘要:目錄許多開發者會把的目錄命名為但這並不強迫。所有的檔案都會使用從被編譯成。同時有個小小的重點那就是我們可已觀察編譯後的檔案大小。在專案目錄下執行可以觀察截至目前為止的結果。我們的目標是要把編譯封裝到我們的中。 在今時今日,webpack 已經成為前端開發非常重要的工具之一。本質上它是一個 Javascript 模組封裝工具,但透過 loaders 和 plugins 它也可以轉換封裝其...</p> <div class="com_white-left-info"> <div class="com_white-left-infol"> <a href="https://www.ucloud.cn/yun/u-1536.html"><img src="https://www.ucloud.cn/yun/data/avatar/000/00/15/small_000001536.jpg" alt=""><span class="layui-hide64">betacat</span></a> <time datetime="">2019-08-20 14:22</time> <span><i class="fa fa-commenting"></i>评论0</span> <span><i class="fa fa-star"></i>收藏0</span> </div> </div> </div> </li> <li> <div class="atricle-list-right"> <h2 class="ellipsis2"><a class="hpf" href="https://www.ucloud.cn/yun/115060.html"><b>[<em>譯</em>] CSS 載入機制的未來趨勢</b></a></h2> <p class="ellipsis2 good">摘要:載入流程被限制在兩個階段根據上面的模式,內嵌透過隱藏尚未套用樣式的內容,然後非同步得載入之後呈現內容。樣式表本身的載入機制是平行的,但是套用樣式卻是要照順序的。我們需要一點小技巧來避免。 這週閱讀到這篇有意思的文章,於是便動手寫下簡單的翻譯,如果有理解錯誤的地方歡迎指教。 Chrome 正在試圖改變當 寫在 的行為,從blink-dev 的文章並不能很清楚的知道其優點。所以這篇文章...</p> <div class="com_white-left-info"> <div class="com_white-left-infol"> <a href="https://www.ucloud.cn/yun/u-1302.html"><img src="https://www.ucloud.cn/yun/data/avatar/000/00/13/small_000001302.jpg" alt=""><span class="layui-hide64">Astrian</span></a> <time datetime="">2019-08-30 12:47</time> <span><i class="fa fa-commenting"></i>评论0</span> <span><i class="fa fa-star"></i>收藏0</span> </div> </div> </div> </li> <li> <div class="atricle-list-right"> <h2 class="ellipsis2"><a class="hpf" href="https://www.ucloud.cn/yun/87432.html"><b>前端每周清单第 29 期:Web 现状分析与优化策略、Vue 单元测试、<em>Headless</em> Chrom</b></a></h2> <p class="ellipsis2 good">摘要:前端每周清单第期现状分析与优化策略单元测试爬虫作者王下邀月熊编辑徐川前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点分为新闻热点开发教程工程实践深度阅读开源项目巅峰人生等栏目。 showImg(https://segmentfault.com/img/remote/1460000011008022); 前端每周清单第 29 期:Web 现状分析与优化策略...</p> <div class="com_white-left-info"> <div class="com_white-left-infol"> <a href="https://www.ucloud.cn/yun/u-449.html"><img src="https://www.ucloud.cn/yun/data/avatar/000/00/04/small_000000449.jpg" alt=""><span class="layui-hide64">HackerShell</span></a> <time datetime="">2019-08-21 12:05</time> <span><i class="fa fa-commenting"></i>评论0</span> <span><i class="fa fa-star"></i>收藏0</span> </div> </div> </div> </li> </ul> </div> <div class="topicone-box-wangeditor"> <h3 class="top-com-title mb-64"><span>发表评论</span></h3> <div class="xcp-publish-main flex_box_zd"> <div class="unlogin-pinglun-box"> <a href="javascript:login()" class="grad">登陆后可评论</a> </div> </div> </div> <div class="site-box-content"> <div class="site-content-title"> <h3 class="top-com-title mb-64"><span>0条评论</span></h3> </div> <div class="pages"></ul></div> </div> </div> <div class="layui-col-md4 layui-col-lg3 com_white-right site-wrap-right"> <div class=""> <div class="com_layuiright-box user-msgbox"> <a href="https://www.ucloud.cn/yun/u-156.html"><img src="https://www.ucloud.cn/yun/data/avatar/000/00/01/small_000000156.jpg" alt=""></a> <h3><a href="https://www.ucloud.cn/yun/u-156.html" rel="nofollow">toddmark</a></h3> <h6>男<span>|</span>高级讲师</h6> <div class="flex_box_zd user-msgbox-atten"> <a href="javascript:attentto_user(156)" id="attenttouser_156" class="grad follow-btn notfollow attention">我要关注</a> <a href="javascript:login()" title="发私信" >我要私信</a> </div> <div class="user-msgbox-list flex_box_zd"> <h3 class="hpf">TA的文章</h3> <a href="https://www.ucloud.cn/yun/ut-156.html" class="box_hxjz">阅读更多</a> </div> <ul class="user-msgbox-ul"> <li><h3 class="ellipsis"><a href="https://www.ucloud.cn/yun/124397.html">OpenCV通道的分离和合并</a></h3> <p>阅读 3208<span>·</span>2021-11-23 09:51</p></li> <li><h3 class="ellipsis"><a href="https://www.ucloud.cn/yun/120534.html">基于域名的虚拟主机是什么-什么是虚拟主机?</a></h3> <p>阅读 3667<span>·</span>2021-09-22 15:35</p></li> <li><h3 class="ellipsis"><a href="https://www.ucloud.cn/yun/120005.html">【机器学习实战 Task1】 (KNN)k近邻算法的应用</a></h3> <p>阅读 3644<span>·</span>2021-09-22 10:02</p></li> <li><h3 class="ellipsis"><a href="https://www.ucloud.cn/yun/118585.html">搬瓦工,DC2机房CN2 VPS汇总,电信双程cn2 gt,联通/移动双程直连</a></h3> <p>阅读 2954<span>·</span>2021-08-30 09:49</p></li> <li><h3 class="ellipsis"><a href="https://www.ucloud.cn/yun/117989.html">FxTransit:$16/月/2核/1GB内存/10GB SSD硬盘/2TB流量/500Mbps端</a></h3> <p>阅读 508<span>·</span>2021-08-05 10:01</p></li> <li><h3 class="ellipsis"><a href="https://www.ucloud.cn/yun/116838.html">微信小程序隐藏客服按钮,用图片替代&增加提示卡片可随时关闭。</a></h3> <p>阅读 3374<span>·</span>2019-08-30 15:54</p></li> <li><h3 class="ellipsis"><a href="https://www.ucloud.cn/yun/116794.html">CSS3渐变效果工具</a></h3> <p>阅读 1631<span>·</span>2019-08-30 15:53</p></li> <li><h3 class="ellipsis"><a href="https://www.ucloud.cn/yun/113756.html">前端每日实战:111# 视频演示如何用纯 CSS 创作一只艺术的鸭子</a></h3> <p>阅读 3557<span>·</span>2019-08-29 16:27</p></li> </ul> </div> <!-- 文章详情右侧广告--> <div class="com_layuiright-box"> <h6 class="top-com-title"><span>最新活动</span></h6> <div class="com_adbox"> <div class="layui-carousel" id="right-item"> <div carousel-item> <div> <a href="https://www.ucloud.cn/site/active/kuaijiesale.html?ytag=seo" rel="nofollow"> <img src="https://www.ucloud.cn/yun/data/attach/240625/2rTjEHmi.png" alt="云服务器"> </a> </div> <div> <a href="https://www.ucloud.cn/site/product/gpu.html" rel="nofollow"> <img src="https://www.ucloud.cn/yun/data/attach/240807/7NjZjdrd.png" alt="GPU云服务器"> </a> </div> </div> </div> </div> <!-- banner结束 --> <div class="adhtml"> </div> <script> $(function(){ $.ajax({ type: "GET", url:"https://www.ucloud.cn/yun/ad/getad/1.html", cache: false, success: function(text){ $(".adhtml").html(text); } }); }) </script> </div> </div> </div> </div> </div> </section> <!-- wap拉出按钮 --> <div class="site-tree-mobile layui-hide"> <i class="layui-icon layui-icon-spread-left"></i> </div> <!-- wap遮罩层 --> <div class="site-mobile-shade"></div> <!--付费阅读 --> <div id="payread"> <div class="layui-form-item">阅读需要支付1元查看</div> <div class="layui-form-item"><button class="btn-right">支付并查看</button></div> </div> <script> var prei=0; $(".site-seo-depict pre").each(function(){ var html=$(this).html().replace("<code>","").replace("</code>","").replace('<code class="javascript hljs" codemark="1">',''); $(this).attr('data-clipboard-text',html).attr("id","pre"+prei); $(this).html("").append("<code>"+html+"</code>"); prei++; }) $(".site-seo-depict img").each(function(){ if($(this).attr("src").indexOf('data:image/svg+xml')!= -1){ $(this).remove(); } }) $("LINK[href*='style-49037e4d27.css']").remove(); $("LINK[href*='markdown_views-d7a94ec6ab.css']").remove(); layui.use(['jquery', 'layer','code'], function(){ $("pre").attr("class","layui-code"); $("pre").attr("lay-title",""); $("pre").attr("lay-skin",""); layui.code(); $(".layui-code-h3 a").attr("class","copycode").html("复制代码 ").attr("onclick","copycode(this)"); }); function copycode(target){ var id=$(target).parent().parent().attr("id"); var clipboard = new ClipboardJS("#"+id); clipboard.on('success', function(e) { e.clearSelection(); alert("复制成功") }); clipboard.on('error', function(e) { alert("复制失败") }); } //$(".site-seo-depict").html($(".site-seo-depict").html().slice(0, -5)); </script> <link rel="stylesheet" type="text/css" href="https://www.ucloud.cn/yun/static/js/neweditor/code/styles/tomorrow-night-eighties.css"> <script src="https://www.ucloud.cn/yun/static/js/neweditor/code/highlight.pack.js" type="text/javascript"></script> <script src="https://www.ucloud.cn/yun/static/js/clipboard.js"></script> <script>hljs.initHighlightingOnLoad();</script> <script> function setcode(){ var _html=''; document.querySelectorAll('pre code').forEach((block) => { var _tmptext=$.trim($(block).text()); if(_tmptext!=''){ _html=_html+_tmptext; console.log(_html); } }); } </script> <script> function payread(){ layer.open({ type: 1, title:"付费阅读", shadeClose: true, content: $('#payread') }); } // 举报 function jupao_tip(){ layer.open({ type: 1, title:false, shadeClose: true, content: $('#jubao') }); } $(".getcommentlist").click(function(){ var _id=$(this).attr("dataid"); var _tid=$(this).attr("datatid"); $("#articlecommentlist"+_id).toggleClass("hide"); var flag=$("#articlecommentlist"+_id).attr("dataflag"); if(flag==1){ flag=0; }else{ flag=1; //加载评论 loadarticlecommentlist(_id,_tid); } $("#articlecommentlist"+_id).attr("dataflag",flag); }) $(".add-comment-btn").click(function(){ var _id=$(this).attr("dataid"); $(".formcomment"+_id).toggleClass("hide"); }) $(".btn-sendartcomment").click(function(){ var _aid=$(this).attr("dataid"); var _tid=$(this).attr("datatid"); var _content=$.trim($(".commenttext"+_aid).val()); if(_content==''){ alert("评论内容不能为空"); return false; } var touid=$("#btnsendcomment"+_aid).attr("touid"); if(touid==null){ touid=0; } addarticlecomment(_tid,_aid,_content,touid); }) $(".button_agree").click(function(){ var supportobj = $(this); var tid = $(this).attr("id"); $.ajax({ type: "GET", url:"https://www.ucloud.cn/yun/index.php?topic/ajaxhassupport/" + tid, cache: false, success: function(hassupport){ if (hassupport != '1'){ $.ajax({ type: "GET", cache:false, url: "https://www.ucloud.cn/yun/index.php?topic/ajaxaddsupport/" + tid, success: function(comments) { supportobj.find("span").html(comments+"人赞"); } }); }else{ alert("您已经赞过"); } } }); }); function attenquestion(_tid,_rs){ $.ajax({ //提交数据的类型 POST GET type:"POST", //提交的网址 url:"https://www.ucloud.cn/yun/favorite/topicadd.html", //提交的数据 data:{tid:_tid,rs:_rs}, //返回数据的格式 datatype: "json",//"xml", "html", "script", "json", "jsonp", "text". //在请求之前调用的函数 beforeSend:function(){}, //成功返回之后调用的函数 success:function(data){ var data=eval("("+data+")"); console.log(data) if(data.code==2000){ layer.msg(data.msg,function(){ if(data.rs==1){ //取消收藏 $(".layui-layer-tips").attr("data-tips","收藏文章"); $(".layui-layer-tips").html('<i class="fa fa-heart-o"></i>'); } if(data.rs==0){ //收藏成功 $(".layui-layer-tips").attr("data-tips","已收藏文章"); $(".layui-layer-tips").html('<i class="fa fa-heart"></i>') } }) }else{ layer.msg(data.msg) } } , //调用执行后调用的函数 complete: function(XMLHttpRequest, textStatus){ postadopt=true; }, //调用出错执行的函数 error: function(){ //请求出错处理 postadopt=false; } }); } </script> <footer> <div class="layui-container"> <div class="flex_box_zd"> <div class="left-footer"> <h6><a href="https://www.ucloud.cn/"><img src="https://www.ucloud.cn/yun/static/theme/ukd//images/logo.png" alt="UCloud (优刻得科技股份有限公司)"></a></h6> <p>UCloud (优刻得科技股份有限公司)是中立、安全的云计算服务平台,坚持中立,不涉足客户业务领域。公司自主研发IaaS、PaaS、大数据流通平台、AI服务平台等一系列云计算产品,并深入了解互联网、传统企业在不同场景下的业务需求,提供公有云、混合云、私有云、专有云在内的综合性行业解决方案。</p> </div> <div class="right-footer layui-hidemd"> <ul class="flex_box_zd"> <li> <h6>UCloud与云服务</h6> <p><a href="https://www.ucloud.cn/site/about/intro/">公司介绍</a></p> <p><a href="https://zhaopin.ucloud.cn/" >加入我们</a></p> <p><a href="https://www.ucloud.cn/site/ucan/onlineclass/">UCan线上公开课</a></p> <p><a href="https://www.ucloud.cn/site/solutions.html" >行业解决方案</a></p> <p><a href="https://www.ucloud.cn/site/pro-notice/">产品动态</a></p> </li> <li> <h6>友情链接</h6> <p><a href="https://www.compshare.cn/?ytag=seo">GPU算力平台</a></p> <p><a href="https://www.ucloudstack.com/?ytag=seo">UCloud私有云</a></p> <p><a href="https://www.surfercloud.com/">SurferCloud</a></p> <p><a href="https://www.uwin-link.com/">工厂仿真软件</a></p> <p><a href="https://pinex.it/">Pinex</a></p> <p><a href="https://www.picpik.ai/zh">AI绘画</a></p> </li> <li> <h6>社区栏目</h6> <p><a href="https://www.ucloud.cn/yun/column/index.html">专栏文章</a></p> <p><a href="https://www.ucloud.cn/yun/udata/">专题地图</a></p> </li> <li> <h6>常见问题</h6> <p><a href="https://www.ucloud.cn/site/ucsafe/notice.html" >安全中心</a></p> <p><a href="https://www.ucloud.cn/site/about/news/recent/" >新闻动态</a></p> <p><a href="https://www.ucloud.cn/site/about/news/report/">媒体动态</a></p> <p><a href="https://www.ucloud.cn/site/cases.html">客户案例</a></p> <p><a href="https://www.ucloud.cn/site/notice/">公告</a></p> </li> <li> <span><img src="https://static.ucloud.cn/7a4b6983f4b94bcb97380adc5d073865.png" alt="优刻得"></span> <p>扫扫了解更多</p></div> </div> <div class="copyright">Copyright © 2012-2023 UCloud 优刻得科技股份有限公司<i>|</i><a rel="nofollow" href="http://beian.miit.gov.cn/">沪公网安备 31011002000058号</a><i>|</i><a rel="nofollow" href="http://beian.miit.gov.cn/"></a> 沪ICP备12020087号-3</a><i>|</i> <script type="text/javascript" src="https://gyfk12.kuaishang.cn/bs/ks.j?cI=197688&fI=125915" charset="utf-8"></script> <script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?290c2650b305fc9fff0dbdcafe48b59d"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script> <!-- Global site tag (gtag.js) - Google Analytics --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-DZSMXQ3P9N"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-DZSMXQ3P9N'); </script> <script> (function(){ var el = document.createElement("script"); el.src = "https://lf1-cdn-tos.bytegoofy.com/goofy/ttzz/push.js?99f50ea166557aed914eb4a66a7a70a4709cbb98a54ecb576877d99556fb4bfc3d72cd14f8a76432df3935ab77ec54f830517b3cb210f7fd334f50ccb772134a"; el.id = "ttzz"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(el, s); })(window) </script></div> </div> </footer> </body> <script src="https://www.ucloud.cn/yun/static/theme/ukd/js/common.js"></script> <<script type="text/javascript"> $(".site-seo-depict *,.site-content-answer-body *,.site-body-depict *").css("max-width","100%"); </script> </html>