用Python驱动Headless Chrome

用Python驱动Headless Chrome_第1张图片

Headless Browser(无头的浏览器)是什么鬼?

简而言之,Headless Browser是没有图形用户界面(GUI)的web浏览器,通常是通过编程或命令行界面来控制的。

Headless Browser的许多用处之一是自动化可用性测试或测试浏览器交互。如果您正在尝试检查页面在不同的浏览器中呈现的方式,或者确认页面元素在用户启动某个工作流之后出现,那么使用Headless Browser可以提供大量的帮助。除此之外,如果内容是动态呈现的(比如通过Javascript),web抓取等传统的面向web的任务就很难做了。使用Headless Browser可以方便地访问这些内容,因为内容的呈现方式与完全浏览器中的内容完全相同。

基于不同的浏览器,有不同的浏览器引擎。(http://www.cnblogs.com/wangjunqiao/p/5212561.html)

主流浏览器所使用的内核分类

Trident内核:IE,MaxThon,TT,The World,360,搜狗浏览器等
Gecko内核:Netscape6及以上版本,FF,MozillaSuite/SeaMonkey等
Presto内核:Opera7及以上
Webkit内核:Safari,Chrome等

先让我们看看浏览器处理过程中的每一个步骤:

1.处理HTML脚本,生成DOM树
2.处理CSS脚本,生成CSSOM树 (DOM和CSSOM是独立的数据结构)
3.将DOM树和CSSOM树合并为渲染树
4.对渲染树中的内容进行布局,计算每个节点的几何外观
5.将渲染树中的每个节点绘制到屏幕中

Headless Browser实际就是节约了第4,5步的时间。

3年前,无头浏览器 PhantomJS 已经如火如荼出现了,紧跟着 NightmareJS 也成为一名巨星。无头浏览器带来巨大便利性:页面爬虫、自动化测试、WebAutomation...用过PhantomJS的都知道,它的环境是运行在一个封闭的沙盒里面,在环境内外完全不可通信,包括API、变量、全局方法调用等。

Headless Chrome和Python
在发布Headless Chrome之前,当你需要自动化浏览器的时候随时都有可能涉及多个窗口或标签,你必须担心CPU和/或内存的使用。这两种方式都与必须从被请求的URL中显示显示的图形的浏览器相关联。

当使用一个无头的浏览器时,我们不用担心这个。因此,我们可以预期我们编写的脚本的内存开销会降低,执行速度也会更快。
而Chrome从59版本开始 推出了 headless mode(当时仅支持Mac和Linux),而目前最新的Chrome63版已经开始在windows上支持headless mode。

安装Headless Chrome 在windows
Selenium操作chrome浏览器需要有ChromeDriver驱动来协助。
什么是ChromeDriver?

ChromeDriver是Chromium team开发维护的,它是实现WebDriver有线协议的一个单独的服务。ChromeDriver通过chrome的自动代理框架控制浏览器,建议从以下地址直接下载最新的版本:ChromeDriver 2.34
它才可以支持Chrome v61-63。
可以将此driver放置于:C:\Program Files\Google\Chrome\Application\ (对应的Chrome安装目录下)

安装Selenium 在windows
cmd命令里面运行:
$pip install selenium

编写对应的脚本
编写一个对应的百度搜索的脚本

import os
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options
import time

chrome_options = Options()
chrome_options.add_argument("--headless")

base_url = "http://www.baidu.com/"
#对应的chromedriver的放置目录
driver = webdriver.Chrome(executable_path=(r'C:\Program Files\Google\Chrome\Application\chromedriver.exe'), chrome_options=chrome_options)

driver.get(base_url + "/")

start_time=time.time()
print('this is start_time ',start_time)

driver.find_element_by_id("kw").send_keys("selenium webdriver")
driver.find_element_by_id("su").click()
driver.save_screenshot('screen.png')

driver.close()

end_time=time.time()
print('this is end_time ',end_time)
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')

 

以上的脚本运行完成后,你会在你的当前目录看到一个类似于下面画面的screen.png.

 

用Python驱动Headless Chrome_第2张图片

 

可以看出上面的写法和直接使用Selenium调用Chrome浏览器的时候极其类似,只是多添加了对chrome_options的重写。

据运行的试验表明,Headelss 的确比Headed的浏览器在内存消耗,运行时间,CPU占用上面都有一定的优势。

用Python驱动Headless Chrome_第3张图片

headless对比.png

使用Headless Chrome也许能让你的自动化测试运行更快,而且在视觉测试上面也有一定的优势。感兴趣的朋友可以上手试试。


链接:https://www.jianshu.com/p/11d519e2d0cb
 

 

 

Headless Chrome 入门

reflink:https://linux.cn/article-8850-1.html

在 Chrome 59 中开始搭载 Headless Chrome。这是一种在无需显示headless的环境下运行 Chrome 浏览器的方式。从本质上来说,就是不用 chrome 浏览器来运行 Chrome 的功能!它将 Chromium 和 Blink 渲染引擎提供的所有现代 Web 平台的功能都带入了命令行。

 

它有什么用?

 

无需显示headless的浏览器对于自动化测试和不需要可视化 UI 界面的服务器环境是一个很好的工具。例如,你可能需要对真实的网页运行一些测试,创建一个 PDF,或者只是检查浏览器如何呈现 URL。

 

注意: Mac 和 Linux 上的 Chrome 59 都可以运行无需显示模式。对 Windows 的支持将在 Chrome 60 中提供。要检查你使用的 Chrome 版本,请在浏览器中打开 chrome://version。

 

开启无需显示headless模式(命令行界面)

开启无需显示headless模式最简单的方法是从命令行打开 Chrome 二进制文件。如果你已经安装了 Chrome 59 以上的版本,请使用 --headless 标志启动 Chrome:

chrome \

  --headless \                   # Runs Chrome in headless mode.

  --disable-gpu \                # Temporarily needed for now.

  --remote-debugging-port=9222 \

  https://www.chromestatus.com   # URL to open. Defaults to 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 Cannary。

 

命令行的功能

在某些情况下,你可能不需要以脚本编程的方式操作 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 标志:

chrome --headless --disable-gpu --screenshot https://www.chromestatus.com/

# 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 标志运行 Headless Chrome 将在当前工作目录中生成一个名为 screenshot.png 的文件。如果你正在寻求整个页面的截图,那么会涉及到很多事情。来自 David Schnurr 的一篇很棒的博文已经介绍了这一内容。请查看 使用 headless Chrome 作为自动截屏工具。

 

REPL 模式 (read-eval-print loop)

--repl 标志可以使 Headless Chrome 运行在一个你可以使用浏览器评估 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-debugging-port=9222 运行 Chrome 时,它会启动一个支持 DevTools 协议的实例。该协议用于与 Chrome 进行通信,并且驱动 Headless Chrome 浏览器实例。它也是一个类似 Sublime、VS Code 和 Node 的工具,可用于应用程序的远程调试。#协同效应

 

由于你没有浏览器用户界面可用来查看网页,请在另一个浏览器中输入 http://localhost:9222,以检查一切是否正常。你将会看到一个可检查的inspectable页面的列表,可以点击它们来查看 Headless Chrome 正在呈现的内容:

用Python驱动Headless Chrome_第4张图片

 

DevTools 远程调试界面

 

从这里,你就可以像往常一样使用熟悉的 DevTools 来检查、调试和调整页面了。如果你以编程方式使用 Headless Chrome,这个页面也是一个功能强大的调试工具,用于查看所有通过网络与浏览器交互的原始 DevTools 协议命令。

 

使用编程模式 (Node)

Puppeteer 库 API

Puppeteer 是一个由 Chrome 团队开发的 Node 库。它提供了一个高层次的 API 来控制无需显示版(或 完全版)的 Chrome。它与其他自动化测试库,如 Phantom 和 NightmareJS 相类似,但是只适用于最新版本的 Chrome。

 

除此之外,Puppeteer 还可用于轻松截取屏幕截图,创建 PDF,页面间导航以及获取有关这些页面的信息。如果你想快速地自动化进行浏览器测试,我建议使用该库。它隐藏了 DevTools 协议的复杂性,并可以处理诸如启动 Chrome 调试实例等繁冗的任务。

 

安装:

 

yarn add puppeteer

例子 - 打印用户代理:

 

const puppeteer = require('puppeteer');

(async() => {

  const browser = await puppeteer.launch();

  console.log(await browser.version());

  browser.close();

})();

例子 - 获取页面的屏幕截图:

 

const puppeteer = require('puppeteer');

(async() => {

const browser = await puppeteer.launch();

const page = await browser.newPage();

await page.goto('https://www.chromestatus.com', {waitUntil: 'networkidle'});

await page.pdf({path: 'page.pdf', format: 'A4'});

browser.close();

})();

查看 Puppeteer 的文档,了解完整 API 的更多信息。

 

CRI 库

chrome-remote-interface 是一个比 Puppeteer API 更低层次的库。如果你想要更接近原始信息和更直接地使用 DevTools 协议的话,我推荐使用它。

 

启动 Chrome

 

chrome-remote-interface 不会为你启动 Chrome,所以你要自己启动它。

 

在前面的 CLI 章节中,我们使用 --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 是一个令人称奇的网络应用的质量测试工具。Lighthouse 内部开发了一个强大的用于启动 Chrome 的模块,现在已经被提取出来单独使用。chrome-launcher NPM 模块 可以找到 Chrome 的安装位置,设置调试实例,启动浏览器和在程序运行完之后将其杀死。它最好的一点是可以跨平台工作,感谢 Node!

 

默认情况下,chrome-launcher 会尝试启动 Chrome Canary(如果已经安装),但是你也可以更改它,手动选择使用的 Chrome 版本。要想使用它,首先从 npm 安装:

 

yarn add chrome-launcher

例子 - 使用 chrome-launcher 启动 Headless Chrome:

 

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。记住,它不会有任何的浏览器界面,我们是无需显示的。

 

为了控制浏览器,我们需要 DevTools 协议!

 

检索有关页面的信息

警告: DevTools 协议可以做一些有趣的事情,但是起初可能有点令人生畏。我建议先花点时间浏览 DevTools 协议查看器。然后,转到 chrome-remote-interface 的 API 文档,看看它是如何包装原始协议的。

 

我们来安装该库:

 

yarn add chrome-remote-interface

例子 - 打印用户代理:

 

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。

 

例子 - 检查网站是否有 Web 应用程序清单:

 

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. 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.

});

})();

例子 - 使用 DOM API 提取页面的 :</p> <p> </p> <p>const CDP = require('chrome-remote-interface');</p> <p>...</p> <p>(async function() { </p> <p>const chrome = await launchChrome();</p> <p>const protocol = await CDP({port: chrome.port});</p> <p>// Extract the DevTools protocol domains we need and enable them.</p> <p>// See API docs: https://chromedevtools.github.io/devtools-protocol/</p> <p>const {Page, Runtime} = protocol;</p> <p>await Promise.all([Page.enable(), Runtime.enable()]);</p> <p>Page.navigate({url: 'https://www.chromestatus.com/'});</p> <p>// Wait for window. before doing stuff.</p> <p>Page.loadEventFired(async () => { </p> <p>  const js = "document.querySelector('title').textContent";</p> <p>  // Evaluate the JS expression in the page.</p> <p>  const result = await Runtime.evaluate({expression: js});</p> <p>  console.log('Title of page: ' + result.result.value);</p> <p>  protocol.close();</p> <p>  chrome.kill(); // Kill Chrome.</p> <p>});</p> <p>})();</p> <p>使用 Selenium、WebDriver 和 ChromeDriver</p> <p>现在,Selenium 开启了 Chrome 的完整实例。换句话说,这是一个自动化的解决方案,但不是完全无需显示的。但是,Selenium 只需要进行小小的配置即可运行 Headless Chrome。如果你想要关于如何自己设置的完整说明,我建议你阅读“使用 Headless Chrome 来运行 Selenium”,不过你可以从下面的一些示例开始。</p> <p> </p> <p>使用 ChromeDriver</p> <p>ChromeDriver 2.3.0 支持 Chrome 59 及更新版本,可与 Headless Chrome 配合使用。在某些情况下,你可能需要等到 Chrome 60 以解决 bug。例如,Chrome 59 中屏幕截图已知存在问题。</p> <p> </p> <p>安装:</p> <p> </p> <p>yarn add selenium-webdriver chromedriver</p> <p>例子:</p> <p> </p> <p>const fs = require('fs');</p> <p>const webdriver = require('selenium-webdriver');</p> <p>const chromedriver = require('chromedriver');</p> <p>// This should be the path to your Canary installation.</p> <p>// I'm assuming Mac for the example.</p> <p>const PATH_TO_CANARY = '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary';</p> <p>const chromeCapabilities = webdriver.Capabilities.chrome();</p> <p>chromeCapabilities.set('chromeOptions', { </p> <p>  binary: PATH_TO_CANARY // Screenshots require Chrome 60\. Force Canary.</p> <p>  'args': [</p> <p>    '--headless',</p> <p>  ]</p> <p>});</p> <p>const driver = new webdriver.Builder()</p> <p>  .forBrowser('chrome')</p> <p>  .withCapabilities(chromeCapabilities)</p> <p>  .build();</p> <p>// Navigate to google.com, enter a search.</p> <p>driver.get('https://www.google.com/');</p> <p>driver.findElement({name: 'q'}).sendKeys('webdriver');</p> <p>driver.findElement({name: 'btnG'}).click();</p> <p>driver.wait(webdriver.until.titleIs('webdriver - Google Search'), 1000);</p> <p>// Take screenshot of results page. Save to disk.</p> <p>driver.takeScreenshot().then(base64png => { </p> <p>  fs.writeFileSync('screenshot.png', new Buffer(base64png, 'base64'));</p> <p>});</p> <p>driver.quit();</p> <p>使用 WebDriverIO</p> <p>WebDriverIO 是一个在 Selenium WebDrive 上构建的更高层次的 API。</p> <p> </p> <p>安装:</p> <p> </p> <p>yarn add webdriverio chromedriver</p> <p>例子:过滤 chromestatus.com 上的 CSS 功能:</p> <p> </p> <p>const webdriverio = require('webdriverio');</p> <p>const chromedriver = require('chromedriver');</p> <p>// This should be the path to your Canary installation.</p> <p>// I'm assuming Mac for the example.</p> <p>const PATH_TO_CANARY = '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary';</p> <p>const PORT = 9515;</p> <p>chromedriver.start([</p> <p>  '--url-base=wd/hub',</p> <p>  `--port=${PORT}`,</p> <p>  '--verbose'</p> <p>]);</p> <p>(async () => { </p> <p>const opts = { </p> <p>  port: PORT,</p> <p>  desiredCapabilities: { </p> <p>    browserName: 'chrome',</p> <p>    chromeOptions: { </p> <p>      binary: PATH_TO_CANARY // Screenshots require Chrome 60\. Force Canary.</p> <p>      args: ['--headless']</p> <p>    }</p> <p>  }</p> <p>};</p> <p>const browser = webdriverio.remote(opts).init();</p> <p>await browser.url('https://www.chromestatus.com/features');</p> <p>const title = await browser.getTitle();</p> <p>console.log(`Title: ${title}`);</p> <p>await browser.waitForText('.num-features', 3000);</p> <p>let numFeatures = await browser.getText('.num-features');</p> <p>console.log(`Chrome has ${numFeatures} total features`);</p> <p>await browser.setValue('input[type="search"]', 'CSS');</p> <p>console.log('Filtering features...');</p> <p>await browser.pause(1000);</p> <p>numFeatures = await browser.getText('.num-features');</p> <p>console.log(`Chrome has ${numFeatures} CSS features`);</p> <p>const buffer = await browser.saveScreenshot('screenshot.png');</p> <p>console.log('Saved screenshot...');</p> <p>chromedriver.stop();</p> <p>browser.end();</p> <p>})();</p> <p>更多资源</p> <p>以下是一些可以带你入门的有用资源:</p> <p> </p> <p>文档</p> <p> </p> <p>DevTools Protocol Viewer - API 参考文档</p> <p>工具</p> <p> </p> <p>chrome-remote-interface - 基于 DevTools 协议的 node 模块</p> <p>Lighthouse - 测试 Web 应用程序质量的自动化工具;大量使用了协议</p> <p>chrome-launcher - 用于启动 Chrome 的 node 模块,可以自动化</p> <p>样例</p> <p> </p> <p>"The Headless Web" - Paul Kinlan 发布的使用了 Headless 和 api.ai 的精彩博客</p> <p>常见问题</p> <p>我需要 --disable-gpu 标志吗?</p> <p> </p> <p>目前是需要的。--disable-gpu 标志在处理一些 bug 时是需要的。在未来版本的 Chrome 中就不需要了。查看 https://crbug.com/546953#c152 和 https://crbug.com/695212 获取更多信息。</p> <p> </p> <p>所以我仍然需要 Xvfb 吗?</p> <p> </p> <p>不。Headless Chrome 不使用窗口,所以不需要像 Xvfb 这样的显示服务器。没有它你也可以愉快地运行你的自动化测试。</p> <p> </p> <p>什么是 Xvfb?Xvfb 是一个用于类 Unix 系统的运行于内存之内的显示服务器,可以让你运行图形应用程序(如 Chrome),而无需附加的物理显示器。许多人使用 Xvfb 运行早期版本的 Chrome 进行 “headless” 测试。</p> <p> </p> <p>如何创建一个运行 Headless Chrome 的 Docker 容器?</p> <p> </p> <p>查看 lighthouse-ci。它有一个使用 Ubuntu 作为基础镜像的 Dockerfile 示例,并且在 App Engine Flexible 容器中安装和运行了 Lighthouse。</p> <p> </p> <p>我可以把它和 Selenium / WebDriver / ChromeDriver 一起使用吗?</p> <p> </p> <p>是的。查看 Using Selenium, WebDrive, or ChromeDriver。</p> <p> </p> <p>它和 PhantomJS 有什么关系?</p> <p> </p> <p>Headless Chrome 和 PhantomJS 是类似的工具。它们都可以用来在无需显示的环境中进行自动化测试。两者的主要不同在于 Phantom 使用了一个较老版本的 WebKit 作为它的渲染引擎,而 Headless Chrome 使用了最新版本的 Blink。</p> <p> </p> <p>目前,Phantom 提供了比 DevTools protocol 更高层次的 API。</p> <p> </p> <p>我在哪儿提交 bug?</p> <p> </p> <p>对于 Headless Chrome 的 bug,请提交到 crbug.com。</p> <p> </p> <p>对于 DevTools 协议的 bug,请提交到 github.com/ChromeDevTools/devtools-protocol。</p> <p>编译自:https://developers.google.com/web/updates/2017/04/headless-chrome作者: Eric Bidelman<br> 原创:LCTT https://linux.cn/article-8850-1.html译者: Firmy Yang</p> <p><br> 链接:http://www.imooc.com/article/81012</p> <p> </p> <h1>selenium+headless chrome安装使用</h1> <p>pip install selenium</p> <p>因为phantomJS将停止维护,所以建议使用headless chrome<br> ChromeDriver is a separate executable that WebDriver uses to control Chrome.</p> <p>1、确保谷歌浏览器安装在可以找到的位置(默认位置或自己指定的位置)。<br> 如果不是默认位置,则需要用下面的代码来指定谷歌浏览器的安装位置:<br> ChromeOptions options = new ChromeOptions();<br> options.setBinary("/path/to/other/chrome/binary");</p> <p>2、下载你系统上所需要的ChromeDriver文件,windows所需下载地址为:<br> https://chromedriver.storage.googleapis.com/index.html?path=2.35/</p> <p>3、帮助WebDriver找到你下载的ChromeDriver文件:<br> 将ChromeDriver文件存放在PATH目录下或<br> 或<br> from selenium import webdriver<br> driver = webdriver.Chrome('/path/to/chromedriver')</p> <p>4、(可选)启动和退出ChromeDriver server需要一些时间,所以提供了两种方法来<br> 解决这个问题:<br> 1、使用ChromeDriverService<br> 2、作为一个服务器单独启动ChromeDriver server,然后用Remote WebDriver连接它。</p> <p>from selenium import webdriver<br> from selenium.webdriver.chrome.options import Options<br> chrome_options = Options()<br> chrome_options.add_argument('--headless')<br> chrome_options.add_argument('--disable-gpu')<br> driver = webdriver.Chrome(chrome_options=chrome_options)<br> driver.get("https://www.baidu.com")<br> print(driver.title)<br> driver.quit()</p> <p>参考链接:<br> https://sites.google.com/a/chromium.org/chromedriver/home 介绍地址<br> https://sites.google.com/a/chromium.org/chromedriver/getting-started 入门地址</p> <p> </p> <p> </p> <h1>puppeteer,新款headless chrome!</h1> <h2 id="puppeteer">puppeteer</h2> <p>puppeteer是一种谷歌开发的Headless Chrome,因为puppeteer的出现,业内许多自动化测试库停止维护,比如PhantomJS,Selenium IDE for Firefox 。</p> <h2 id="puppeteer是干啥用的">puppeteer是干啥用的?</h2> <p>官方给了一些功能:</p> <ul> <li>页面生成pdf</li> <li>爬spa/ssr类的网站</li> <li>自动提交表单,模拟用户操作,ui测试等等</li> <li>提供自动化测试环境</li> <li>分析网页性能问题,基于chrome timeline</li> </ul> <p>其实对于这么一个浏览器,我们能做的还有很多,比如前端监控,定期查询页面异常。这种思想产生的page-monitor。主要的功能其实就是基于它是一个浏览器,它可以模拟用户输入。能做什么依赖你的想象。</p> <h2 id="用code介绍一下puppeteer">用code介绍一下puppeteer</h2> <h3 id="页面生成pdf">页面生成pdf</h3> <pre class="has"><code>const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('http://open.toutiao.com', {waitUntil: 'networkidle2'}); await page.pdf({path: 'hn.pdf', format: 'A4'}); await browser.close(); })();</code></pre> <p>puppeteer是基于node v6.4.0,但是await/async的语法需要node v7.6.0以上才支持。</p> <p>可以<code>npm i puppeteer</code>然后在命令行看一下效果。<br> 代码都是api没有什么可以讲的。需要说的一点就是open.toutiao.com下面的文章内容都是异步接口请求,puppeteer是怎么获取内容的?</p> <p>page.goto的配置项waitUntil:networkidle2, 等待一直到500ms内的请求数不超过2个。其实不保证准确获得内容,那把等待时间写长一点就可以了。<br><code>await page.waitFor(2000);</code></p> <h3 id="调试">调试</h3> <ul> <li>puppeteer并不是只有headless模式,打开puppeteer的ui界面:<code>puppeteer.launch({headless: false)</code>,再放慢puppeteer执行的动作<code>puppeteer.launch({headless: false, slowMo: 250})</code>,就可以轻松调试。</li> <li>‘打call?’ <code>page.on('console', msg => console.log('PAGE LOG:', msg.text()));</code> 事件监听轻松打出页面的log。</li> </ul> <h3 id="爬虫">爬虫</h3> <p>这里爬一下头条的新闻标题:</p> <pre class="has"><code>(async () => { const browser = await puppeteer.launch({headless: false, slowMo: 250}); const page = (await browser.pages())[0]; page.on('console', msg => console.log('PAGE LOG:', msg.text())); await page.goto('https://open.toutiao.com'); await page.evaluate(() => console.log(`url is ${location.href}`)); const newsTitle = await page.evaluate((sel) => { const $els = document.querySelectorAll(sel); return Array.from($els).map((v) => { console.log(v.innerText); // 会被page.on 'console' 监听到 return v.innerText }) }, 'section h3'); console.log(newsTitle) // 可以处理新闻标题。 await page.screenshot({path: 'toutiao.png'}); // 屏幕快照 await browser.close(); })();</code></pre> <h2 id="模拟用户操作">模拟用户操作</h2> <p>这个功能用途挺多的,比如自动登陆,e2e测试,刷赞,抢票什么的,当然如果能跳过验证码的话。</p> <h3 id="github-登陆">github 登陆</h3> <p>模拟输入用户名和密码。</p> <pre class="has"><code> await page.goto('https://github.com/login'); await page.click('#login_field'); await page.type('username'); await page.click('#password'); await page.type('password'); await page.click('#login > form > div.auth-form-body.mt-3 > input.btn.btn-primary.btn-block'); await page.waitForNavigation();</code></pre> <p>puppetter提供了page.focus,page.click,page.type,page.$eval(获取dom属性)等等api,鼠标位置,按键按下,tap,页面跳转众多用户可操作的api,都可以通过程序来模拟。</p> <blockquote> <p>对这种模拟登陆,puppeteer还贴心的提供了这种api - -!<code>page.type('#mytextarea', 'World', {delay: 100}); // Types slower, like a user</code></p> </blockquote> <h2 id="ui测试">ui测试</h2> <p>之前分享过的testcafe,跟puppeteer的api非常像,testcafe是一个自动化测试框架,他与puppeteer不同的一点就是他集成了mocha断言库。<br> puppeteer和testcafe都提供了一套自动化测试的环境。puppeteer做e2e的测试需要自己选一个断言库,不过无伤大雅。</p> <h2 id="请求拦截模拟请求">请求拦截/模拟请求</h2> <p>puppeteer比testcafe好的一点就是支持请求拦截,记得当初用testcafe测试请求是否被发出用了很多黑科技,提过issue。。</p> <pre class="has"><code>const puppeteer = require('puppeteer'); puppeteer.launch({headless: false, slowMo: 250}).then(async browser => { const page = await browser.newPage(); await page.setRequestInterception(true); page.on('console', msg => console.log('PAGE LOG:', msg.text())); page.on('request', interceptedRequest => { if (interceptedRequest.url().endsWith('.png') || interceptedRequest.url().endsWith('.jpg')) interceptedRequest.abort(); else interceptedRequest.continue(); }); await page.goto('https://open.toutiao.com'); // await browser.close(); });</code></pre> <p>提供了request,response事件,可以拦截请求,首先需要打开这个开关<code>await page.setRequestInterception(true);</code>。<br> 这里的例子就是停掉所有的png和jpg请求。<br> 拦截能做的东西有很多,比如一些爬虫可以通过拦截请求捕获一些数据,来处理一些东西。</p> <h3 id="修改环境">修改环境</h3> <p>puppeteer可以通过page.setViewport,page.setUserAgent来修改访问的环境。</p> <pre class="has"><code>await page.setViewport({ width: 1920, height: 1080 }); await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36');</code></pre> <p><code>puppeteer/DeviceDescriptors</code>还给我们封装好了一些环境,比如:</p> <pre class="has"><code>const puppeteer = require('puppeteer'); const devices = require('puppeteer/DeviceDescriptors'); const iPhone = devices['iPhone 6']; puppeteer.launch().then(async browser => { const page = await browser.newPage(); await page.emulate(iPhone); // emulate的配置有Viewport,UserAgent等等。之前的setUserAgent等方法是它的语法糖。 await page.goto('https://www.google.com'); // other actions... await browser.close(); });</code></pre> <h2 id="性能测试">性能测试</h2> <p>可以生成一个trace.json的文件,供chrome控制台解析,<code>await page.metrics()</code>还可以给出一些性能测试的数据。</p> <pre class="has"><code>const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.tracing.start({path: 'trace.json'}) await page.goto('https://open.toutiao.com') await page.tracing.stop() const metrics = await page.metrics() console.log(metrics) await browser.close(); })(); // output { Timestamp: 27888.820538, Documents: 2, Frames: 1, JSEventListeners: 58, Nodes: 171, LayoutCount: 20, RecalcStyleCount: 26, LayoutDuration: 0.042335, RecalcStyleDuration: 0.010091, ScriptDuration: 0.124838, TaskDuration: 0.000039, JSHeapUsedSize: 6388448, JSHeapTotalSize: 10334208 } </code></pre> <p>https://www.cnblogs.com/dh-dh/p/8490047.html</p> <p> </p> <h1>linux 安装 Headless Chrome</h1> <p>https://chromedriver.storage.googleapis.com/index.html?path=2.34/</p> <p> http://chromedriver.storage.googleapis.com/index.html</p> <p> </p> <p> 看到网上基本没有最新的chromedriver与chrome的对应关系表,便兴起整理了一份如下,希望对大家有用:</p> <table> <thead> <tr> <th style="text-align:center;vertical-align:middle;">chromedriver版本</th> <th style="text-align:center;vertical-align:middle;">支持的Chrome版本</th> </tr> </thead> <tbody> <tr> <td style="text-align:center;vertical-align:middle;">v2.35</td> <td style="text-align:center;vertical-align:middle;">v62-64</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.34</td> <td style="text-align:center;vertical-align:middle;">v61-63</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.33</td> <td style="text-align:center;vertical-align:middle;">v60-62</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.32</td> <td style="text-align:center;vertical-align:middle;">v59-61</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.31</td> <td style="text-align:center;vertical-align:middle;">v58-60</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.30</td> <td style="text-align:center;vertical-align:middle;">v58-60</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.29</td> <td style="text-align:center;vertical-align:middle;">v56-58</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.28</td> <td style="text-align:center;vertical-align:middle;">v55-57</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.27</td> <td style="text-align:center;vertical-align:middle;">v54-56</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.26</td> <td style="text-align:center;vertical-align:middle;">v53-55</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.25</td> <td style="text-align:center;vertical-align:middle;">v53-55</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.24</td> <td style="text-align:center;vertical-align:middle;">v52-54</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.23</td> <td style="text-align:center;vertical-align:middle;">v51-53</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.22</td> <td style="text-align:center;vertical-align:middle;">v49-52</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.21</td> <td style="text-align:center;vertical-align:middle;">v46-50</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.20</td> <td style="text-align:center;vertical-align:middle;">v43-48</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.19</td> <td style="text-align:center;vertical-align:middle;">v43-47</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.18</td> <td style="text-align:center;vertical-align:middle;">v43-46</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.17</td> <td style="text-align:center;vertical-align:middle;">v42-43</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.13</td> <td style="text-align:center;vertical-align:middle;">v42-45</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.15</td> <td style="text-align:center;vertical-align:middle;">v40-43</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.14</td> <td style="text-align:center;vertical-align:middle;">v39-42</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.13</td> <td style="text-align:center;vertical-align:middle;">v38-41</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.12</td> <td style="text-align:center;vertical-align:middle;">v36-40</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.11</td> <td style="text-align:center;vertical-align:middle;">v36-40</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.10</td> <td style="text-align:center;vertical-align:middle;">v33-36</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.9</td> <td style="text-align:center;vertical-align:middle;">v31-34</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.8</td> <td style="text-align:center;vertical-align:middle;">v30-33</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.7</td> <td style="text-align:center;vertical-align:middle;">v30-33</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.6</td> <td style="text-align:center;vertical-align:middle;">v29-32</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.5</td> <td style="text-align:center;vertical-align:middle;">v29-32</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;">v2.4</td> <td style="text-align:center;vertical-align:middle;">v29-32</td> </tr> </tbody> </table> <p>综合上面的表格,我们下载chrome可以到这里选择</p> <p>https://dl.lancdn.com/landian/software/chrome/m/</p> <p> </p> <p>chromedriver可以到这里下载</p> <p>https://chromedriver.storage.googleapis.com/index.html</p> <p> </p> <p>下面是一片chrome和phantomjs的文章</p> <p>http://insights.thoughtworks.cn/phantomjs-and-chrome-headless/</p> <p></p> <p>附:<br> 所有chromedriver均可在下面链接中下载到:</p> <p>http://chromedriver.storage.googleapis.com/index.html</p> <p>现在有一点好的是出了按照chrome版本对应的driver,直接按照浏览器版本去找对应的driver(只对应大版本就行),不用再费心去对应了,大家可以尝试一下:</p> <p><br> 有些同学说下不了,到taobao下也是可以的:</p> <p>http://npm.taobao.org/mirrors/chromedriver/</p> <p> </p> <h3>前言</h3> <p><br> 公司的爬虫项目使用了selenium+phantomjs,这个做过爬虫的应该都用过,但是缺点也很明显,慢,占用资源等,本身还有很多小坑就不一一列举了</p> <p>后来无意中发现了headless,<br> 参考这篇文章:https://intoli.com/blog/making-chrome-headless-undetectable/</p> <p>经过安装测试,效果确实比phantomjs好很多,目前已有的资料都是使用nodejs,python相关的资料不多,只能等大佬们整理了。</p> <p>这里记录一下安装的过程</p> <p>安装chrome<br> 需要V59以上版本<br> 下载地址:https://www.landiannews.com/archives/36966.html</p> <p>下载driver<br> selenium调用需要使用到<br> 下载地址:https://sites.google.com/a/chromium.org/chromedriver/downloads</p> <p>安装调试<br> 下载安装chromedriver</p> <pre class="has"><code>mkdir chrome cd chrome wget https://chromedriver.storage.googleapis.com/2.31/chromedriver_linux64.zip  unzip chromedriver_linux64.zip  cd  vi .bashrc #添加环境变量 export PATH=/home/username/chrome:$PATH #在最后一行添加后保存退出 source ~/.bashrc #立即生效</code></pre> <p><br><br> 下载安装chrome</p> <pre class="has"><code>wget https://dl.lancdn.com/landian/software/chrome/m/60.0.3112.90_amd64.deb sudo apt -f -y install sudo dpkg -i 60.0.3112.90_amd64.deb</code></pre> <p><br><br> 调试安装结果<br> 新建一个.py文件</p> <pre class="has"><code># coding=utf-8 from selenium import webdriver from selenium.webdriver.chrome.options import Optionsurl="http://news.163.com/" chrome_options = Options() # specify headless mode chrome_options.add_argument("--headless") browser = webdriver.Chrome(chrome_options=chrome_options) browser.set_page_load_timeout(300) browser.set_script_timeout(300) browser.get(url) title=browser.find_elements_by_xpath('//div[@id="js_top_news"]/h2/a') print title[0].get_attribute('innerHTML') browser.quit()</code></pre> <p><br><br> 原文链接:https://blog.csdn.net/goodzyw/article/details/77269875</p> <p> </p> <p> </p> <h1>phantomJs之殇,chrome-headless之生</h1> <p><em>技术雷达快讯:自2017年中以来,Chrome用户可以选择以headless模式运行浏览器。此功能非常适合运行前端浏览器测试,而无需在屏幕上显示操作过程。在此之前,这主要是PhantomJS的领地,但Headless Chrome正在迅速取代这个由JavaScript驱动的WebKit方法。Headless Chrome浏览器的测试运行速度要快得多,而且行为上更像一个真正的浏览器,虽然我们的团队发现它比PhantomJS使用更多的内存。有了这些优势,用于前端测试的Headless Chrome很可能成为事实上的标准。</em></p> <p><a href="http://img.e-com-net.com/image/info8/2d42d05ae34944889c5bd35d983490f0.jpg" target="_blank"><img alt="用Python驱动Headless Chrome_第5张图片" class="has" height="325" src="http://img.e-com-net.com/image/info8/2d42d05ae34944889c5bd35d983490f0.jpg" width="650" style="border:1px solid black;"></a></p> <p>随着Google在Chrome 59版本放出了headless模式,Ariya Hidayat决定放弃对Phantom.js的维护,这也标示着Phantom.js 统治fully functional headless browser的时代将被chrome-headless代替。</p> <h3>Headless Browser</h3> <p>也许很多人对无头浏览器还是很陌生,我们先来看看维基百科的解释:</p> <blockquote> <p>A headless browser is a web browser without a graphical user interface.</p> <p>Headless browsers provide automated control of a web page in an environment similar to popular web browsers, but are executed via a command-line interface or using network communication.</p> </blockquote> <p>对,就是没有页面的浏览器。多用于测试web、截图、图像对比、测试前端代码、爬虫(虽然很慢)、监控网站性能等。</p> <h3>为什么要使用headless测试?</h3> <p><strong>headless broswer可以给测试带来显著好处:</strong></p> <ol> <li>对于UI自动化测试,少了真实浏览器加载css,js以及渲染页面的工作。无头测试要比真实浏览器快的多。</li> <li>可以在无界面的服务器或CI上运行测试,减少了外界的干扰,使自动化测试更稳定。</li> <li>在一台机器上可以模拟运行多个无头浏览器,方便进行并发测试。</li> </ol> <p><strong>headless browser有什么缺陷?</strong></p> <p>以phantomjs为例</p> <p><a href="http://img.e-com-net.com/image/info8/2396068aa4f94d9bb937690e112e3e80.jpg" target="_blank"><img alt="用Python驱动Headless Chrome_第6张图片" class="has" height="325" src="http://img.e-com-net.com/image/info8/2396068aa4f94d9bb937690e112e3e80.jpg" width="650" style="border:1px solid black;"></a></p> <ol> <li>虽然Phantom.js 是fully functional headless browser,但是它和真正的浏览器还是有很大的差别,并不能完全模拟真实的用户操作。很多时候,我们在Phantom.js发现一些问题,但是调试了半天发现是Phantom.js自己的问题。<a href="http://img.e-com-net.com/image/info8/f6cbd521fc3d4e428affc3509594f4f4.jpg" target="_blank"><img alt="用Python驱动Headless Chrome_第7张图片" class="has" height="186" src="http://img.e-com-net.com/image/info8/f6cbd521fc3d4e428affc3509594f4f4.jpg" width="650" style="border:1px solid black;"></a></li> <li>将近2k的issue,仍然需要人去修复。</li> <li>Javascript天生单线程的弱点,需要用异步方式来模拟多线程,随之而来的callback地狱,对于新手而言非常痛苦,不过随着es6的广泛应用,我们可以用promise来解决多重嵌套回调函数的问题。</li> <li>虽然webdriver支持htmlunit与phantomjs,但由于没有任何界面,当我们需要进行调试或复现问题时,就非常麻烦。</li> </ol> <p>那么Headless Chrome与上面提到fully functional headless browser又有什么不同呢?</p> <h3>什么是Headless Chrome?</h3> <p>Headless Chrome 是 Chrome 浏览器的无界面形态,可以在不打开浏览器的前提下,使用所有Chrome支持的特性,在命令行中运行你的脚本。相比于其他浏览器,Headless Chrome 能够更加便捷的运行web自动化测试、编写爬虫、截取图等功能。</p> <p>有的人肯定会问:看起来它的作用和phantomjs没什么具体的差别?</p> <p>对,是的,Headless Chrome 发布就是来代替phantomjs。</p> <p><strong>我们凭什么换用Headless Chrome?</strong></p> <ol> <li>我爸是Google,那么就意味不会出现phantomjs近2k问题没人维护的尴尬局面。 比phantomjs有更快更好的性能。</li> <li>有人已经做过实验,同一任务,Headless Chrome要比现phantomjs更加快速的完成任务,且占用内存更少。<a href="http://img.e-com-net.com/image/info8/55e67bee618b495ba6025c77ea18f8a6.jpg" target="_blank"><img alt="用Python驱动Headless Chrome_第8张图片" class="has" height="299" src="http://img.e-com-net.com/image/info8/55e67bee618b495ba6025c77ea18f8a6.jpg" width="650" style="border:1px solid black;"></a><a href="http://img.e-com-net.com/image/info8/bd2356f689e04c68922171ce9f6e5c48.jpg" target="_blank"><img alt="用Python驱动Headless Chrome_第9张图片" class="has" height="299" src="http://img.e-com-net.com/image/info8/bd2356f689e04c68922171ce9f6e5c48.jpg" width="650" style="border:1px solid black;"></a>(https://hackernoon.com/benchmark-headless-chrome-vs-phantomjs-e7f44c6956c)</li> <li>chrome对ECMAScript 2017 (ES8)支持,同样headless随着chrome更新,意味着我们也可以使用最新的js语法来编写的脚本,例如async,await等。</li> <li>完全真实的浏览器操作,chrome headless支持所有chrome特性。</li> <li>更加便利的调试,我们只需要在命令行中加入–remote-debugging-port=9222,再打开浏览器输入localhost:9222(ip为实际运行命令的ip地址)就能进入调试界面。<a href="http://img.e-com-net.com/image/info8/350617e988584598acd43ee88ad35f72.jpg" target="_blank"><img alt="用Python驱动Headless Chrome_第10张图片" class="has" height="394" src="http://img.e-com-net.com/image/info8/350617e988584598acd43ee88ad35f72.jpg" width="650" style="border:1px solid black;"></a></li> </ol> <h3>能带给QA以及项目什么好处?</h3> <p>前端测试改进</p> <p>以目前的项目来说,之前的前端单元测试以及组件测试是用karma在phantomjs运行的,非常不稳定,在远端CI上运行时经常会莫名其妙的挂掉,也找不出来具体的原因,自从Headless Chrome推出后,我们将phantomjs切换成Headless Chrome,再也没有出现过异常情况,切换也非常简单,只需要把karma.conf.js文件中的配置改下就OK了。如下</p> <pre class="has"><code>customLaunchers: { myChrome: { base: 'ChromeHeadless', flags: ['--no-sandbox', '--disable-gpu', '--remote-debugging-port=9222'] } }, browsers: ['myChrome'], </code></pre> <p>UI功能测试改进</p> <p><strong>原因一</strong>,Chrome-headless能够完全像真实浏览器一样完成用户所有操作,再也不用担心跑测试时,浏览器受到干扰,造成测试失败</p> <p><strong>原因二</strong>,之前如果我们像要在CI上运行UI自动化测试,非常麻烦。必须使用Xvfb帮助才能在无界面的Linux上 运行UI自动化测试。(Xvfb是一个实现了X11显示服务协议的显示服务器。 不同于其他显示服务器,Xvfb在内存中执行所有的图形操作,不需要借助任何显示设备。)现在也只需要在webdriver启动时,设置一下chrome option即可,以capybara为例:</p> <pre class="has"><code>Capybara.register_driver :selenium_chrome do |app| Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: { "chromeOptions" => { "args" => [ "--incognito", "--allow-running-insecure-content", "--headless", "--disable-gpu" ]} }) end </code></pre> <p>无缝切换,只需更改下配置,就可以提高运行速度与稳定性,何乐而不为。</p> <h3>Google终极大招</h3> <p>Google 最近放出了终极大招——Puppeteer(Puppeteer is a Node library which provides a high-level API to control headless Chrome over the DevTools Protocol. It can also be configured to use full (non-headless) Chrome.)</p> <p>类似于webdriver的高级别的api,去帮助我们通过DevTools协议控制无界面Chrome。</p> <p>在puppteteer之前,我们要控制chrome headless需要使用chrome-remote-interface来实现,但是它比 Puppeteer API 更接近低层次实现,无论是阅读还是编写都要比puppteteer更复杂。也没有具体的dom操作,尤其是我们要模拟一下click事件,input事件等,就显得力不从心了。</p> <p>我们用同样2段代码来对比一下2个库的区别。</p> <p>首先来看看 chrome-remote-interface</p> <pre class="has"><code>const chromeLauncher = require('chrome-launcher'); const CDP = require('chrome-remote-interface'); const fs = require('fs'); 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' : '' ] }); } (async function() { const chrome = await launchChrome(); const protocol = await CDP({port: chrome.port}); const {Page, Runtime} = protocol; await Promise.all([Page.enable(), Runtime.enable()]); Page.navigate({url: 'https://www.github.com/'}); await Page.loadEventFired( console.log("start") ); const {data} = await Page.captureScreenshot(); fs.writeFileSync('example.png', Buffer.from(data, 'base64')); // Wait for window.onload before doing stuff. protocol.close(); chrome.kill(); // Kill Chrome. </code></pre> <p>再来看看 puppeteer</p> <pre class="has"><code>const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://www.github.com'); await page.screenshot({path: 'example.png'}); await browser.close(); })(); </code></pre> <p>对,就是这么简短明了,更接近自然语言。没有callback,几行代码就能搞定我们所需的一切。</p> <h3>总结</h3> <p>目前Headless Chrome仍然存在一些问题,还需要不断完善,我们应该拥抱变化,适应它,让它给我们的工作带来更多帮助。</p> </div> </div> </div> </div> </div> <!--PC和WAP自适应版--> <div id="SOHUCS" sid="1391859385592008704"></div> <script type="text/javascript" src="/views/front/js/chanyan.js"></script> <!-- 文章页-底部 动态广告位 --> <div class="youdao-fixed-ad" id="detail_ad_bottom"></div> </div> <div class="col-md-3"> <div class="row" id="ad"> <!-- 文章页-右侧1 动态广告位 --> <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_1"> </div> </div> <!-- 文章页-右侧2 动态广告位 --> <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_2"></div> </div> <!-- 文章页-右侧3 动态广告位 --> <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_3"></div> </div> </div> </div> </div> </div> </div> <div class="container"> <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(LINUX)</h4> <div id="paradigm-article-related"> <div class="recommend-post mb30"> <ul class="widget-links"> <li><a href="/article/1946917416277700608.htm" title="bash-completion未安装或未启用" target="_blank">bash-completion未安装或未启用</a> <span class="text-muted">teayear</span> <a class="tag" taget="_blank" href="/search/bash/1.htm">bash</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>在Linux系统中,按下Tab键无法触发自动补全或提示的情况,通常是由以下原因导致的。以下是具体分析和解决方案:一、常见原因及解决方案1.bash-completion未安装或未启用原因:bash-completion是提供命令补全功能的核心工具,部分Linux发行版(如CentOS)默认未安装此工具。解决方案:#安装bash-completionsudoyuminstall-ybash-comp</div> </li> <li><a href="/article/1946910863730470912.htm" title="Linux 命令:uname" target="_blank">Linux 命令:uname</a> <span class="text-muted">hweiyu00</span> <a class="tag" taget="_blank" href="/search/Linux%E5%91%BD%E4%BB%A4/1.htm">Linux命令</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a><a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">服务器</a> <div>Linuxuname命令详细教程uname(UnixName)是Linux系统中用于获取系统基本信息的基础命令。它能快速展示操作系统、内核、主机名等关键信息,是系统诊断和环境确认的常用工具。资料已经分类整理好:https://pan.quark.cn/s/26d73f7dd8a7一、基本语法uname[选项]核心功能:默认只显示操作系统名称(如Linux)。通过选项可获取更详细的系统信息。二、常用</div> </li> <li><a href="/article/1946899768953335808.htm" title="Linux进程间通信--命名管道" target="_blank">Linux进程间通信--命名管道</a> <span class="text-muted"></span> <div>目录1、什么是命名管道1.1命名管道的创建和使用1.2、命名管道的工作原理1.3、命名管道与匿名管道的区别2.命名管道的特点及特殊场景2.1特点2.2四种特殊场景3.日志类的模拟3.1可变参数的利用3.2time()函数和structtm类的介绍3.3日期类的实现1、什么是命名管道命名管道是一种在文件系统中存在的特殊文件类型,它允许不同进程通过文件名(即“命名”)来访问和进行通信。与匿名管道相比,</div> </li> <li><a href="/article/1946895864970670080.htm" title="个人笔记(linux/sort与uniq命令)" target="_blank">个人笔记(linux/sort与uniq命令)</a> <span class="text-muted"></span> <div>sort命令(排序)功能:行排序核心语法:sort[选项][文件]常用选项:选项作用示例-n数值排序sort-n-r降序排序sort-nr-k指定排序列sort-k2,2n-t指定分隔符sort-t':'-k3n-u去重(相当于uniq)sort-u典型用法:#按第二列数字降序排序sort-k2,2nrdata.txt#处理CSV文件(以逗号分隔)sort-t','-k3ndata.csv注意事项</div> </li> <li><a href="/article/1946888123904487424.htm" title="inotify-tools监控文件的变动情况" target="_blank">inotify-tools监控文件的变动情况</a> <span class="text-muted">Tim在路上</span> <div>在实际的生产中,都会存在不同系统的对接问题,比如A系统将数据生产后存放到/data文件下,B系统需要监控/data文件夹下数据的变动情况,来做出调整,linux系统中inotify-tools正好可以完成系统的监控而supervise正好可以完成进程的持续监控,起到出错重启的效果。inotify-toolsinotify-tools下载地址:http://github.com/downloads/</div> </li> <li><a href="/article/1946886030460252160.htm" title="通过 Ollama 获取并运行本地大型语言模型(LLM)" target="_blank">通过 Ollama 获取并运行本地大型语言模型(LLM)</a> <span class="text-muted"></span> <div>Ollama是一个开源工具,专为在本地机器上便捷部署和运行大型语言模型(LLM)而设计。它支持多种操作系统(Windows、macOS、Linux),并提供简单的命令行接口和API,适合开发者、研究人员以及对数据隐私有较高要求的用户。本文档将详细指导您如何通过Ollama在本地获取和运行LLM。1.准备工作在开始之前,请确保您的系统满足以下要求:操作系统:Windows、macOS或Linux(支</div> </li> <li><a href="/article/1946875414513250304.htm" title="软件测试理论基础、质量保证常见面试题" target="_blank">软件测试理论基础、质量保证常见面试题</a> <span class="text-muted">程序员阿沐</span> <div>全面掌握软件测试理论基础、文档编写,测试流程1.测试分为哪几个阶段?⒉谈谈你之前测试的项目流程,在每个阶段的输出有哪些?3.谈谈敏捷模式的认识?4.linux常见查看日志命令有哪些?5.线上质量BUG频频爆发怎么办?6.如何分析一个bug是前端还是后端的问题?这些问题你一定要能够很全面的表述出来。比如说我现在是面试官,我第一个肯定不会去问你哪些代码的问题,也不会问你自动化、测试开发的问题。第一个查</div> </li> <li><a href="/article/1946873555736129536.htm" title="Linux+Python实战课堂:笔记、练习与应用" target="_blank">Linux+Python实战课堂:笔记、练习与应用</a> <span class="text-muted"></span> <div>本文还有配套的精品资源,点击获取简介:本压缩包提供全面的Linux学习资源和Python编程练习,旨在帮助初学者和IT从业者深入理解Linux系统及其技能,并通过Python编程练习巩固相关技能。涵盖Linux基础概念、文件系统、命令行操作、文本编辑器使用、用户和组管理、软件管理、进程监控、网络配置以及系统性能监控等多个方面。同时,包含Python基础语法、函数与模块、面向对象编程、文件操作、异常</div> </li> <li><a href="/article/1946871414229364736.htm" title="【立创泰山派】Linux驱动之UART驱动程序" target="_blank">【立创泰山派】Linux驱动之UART驱动程序</a> <span class="text-muted">Monisa_sama</span> <a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/%E5%8D%95%E7%89%87%E6%9C%BA/1.htm">单片机</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a> <div>Linux串口驱动前言1.串口通信2.调试和测试3.性能分析4.扩展串口功能一、基础知识1.什么是串口1.1波特率2.通信协议2.1UART帧结构2.2校验方式二、硬件接口1.基于TTL的UART通讯2.基于RS232的UART通讯3.基于RS485的UART通讯三、软件框架1.驱动子系统框架1.1串口驱动程序位置1.2使用8250驱动的方式1.3串口设备的调试方法2.注册流程分析3.设备树配置3</div> </li> <li><a href="/article/1946860826069626880.htm" title="buildroot+qemu+arm64虚拟环境多种方式启动linux内核" target="_blank">buildroot+qemu+arm64虚拟环境多种方式启动linux内核</a> <span class="text-muted">左家垅的牛</span> <a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a><a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">服务器</a> <div>Qemu:QEMU是一款开源的硬件虚拟化软件,可以在不同的主机平台上运行虚拟机。它通过动态的二进制转换,模拟CPU,并且提供一组设备模型,使它能够运行多种未修改的客户机OS。QEMU采用全系统仿真,可以模拟完整的计算机系统,包括处理器、内存、存储和外围设备。它提供硬件仿真,允许在一个虚拟环境中运行不同体系结构的操作系统和应用程序。QEMU可以与KVM一起使用,进而接近本地速度运行虚拟机。目前,QE</div> </li> <li><a href="/article/1946846331783933952.htm" title="【Linux】权限详解 权限本质、权限属性、su、sudo提权、chmod\chown\chgrp、文件类别" target="_blank">【Linux】权限详解 权限本质、权限属性、su、sudo提权、chmod\chown\chgrp、文件类别</a> <span class="text-muted"></span> <div>文章目录一、权限的认识二、linux的权限本质三、linux的用户su指令sudo提权四、linux角色五、文件权限属性六、修改权限的指令操作chmod指令(权限只会验证一次)chown/chgrp指令修改文件权限的八进制方案七、文件类别详解一、权限的认识什么是权限?生活中处处都有权限,本质就是你要做一件事能还是不能的问题,有权限就能做,没权限就算你想做也不能做为什么linux要有权限?首先lin</div> </li> <li><a href="/article/1946835610178220032.htm" title="LNMP的安装记录" target="_blank">LNMP的安装记录</a> <span class="text-muted">Jay_MIng</span> <a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/php/1.htm">php</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a><a class="tag" taget="_blank" href="/search/nginx/1.htm">nginx</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a> <div>Linux可以使用虚拟机挂载使用Centos、Debian、Ubunto等的一些镜像,有条件的话可以使用阿里云的系统,本文使用的就是阿里云的x86_64x86_64x86_64GNU/Linux系统,虚拟机安装不做详解,可以自行搜索,或者有疑问可以提出一起探讨安装PHP1、下载解压wgethttp://cn2.php.net/distributions/php-7.2.8.tar.gztar-xz</div> </li> <li><a href="/article/1946833214496632832.htm" title="CSS面试题及详细答案140道之(101-120)" target="_blank">CSS面试题及详细答案140道之(101-120)</a> <span class="text-muted">还是大剑师兰特</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E5%90%8E%E7%AB%AF%E9%9D%A2%E8%AF%95%E9%A2%98/1.htm">前后端面试题</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a><a class="tag" taget="_blank" href="/search/%E5%A4%A7%E5%89%91%E5%B8%88/1.htm">大剑师</a><a class="tag" taget="_blank" href="/search/CSS%E9%9D%A2%E8%AF%95%E9%A2%98/1.htm">CSS面试题</a> <div>《前后端面试题》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux…。前后端面试题-专栏总目录文章目录一、本文面试题目录101.解释`text-indent`属性的作用。102.如何在CSS中实现响应</div> </li> <li><a href="/article/1946832207234527232.htm" title="报错解决:/usr/bin/python^M: bad interpreter: No such file or directory" target="_blank">报错解决:/usr/bin/python^M: bad interpreter: No such file or directory</a> <span class="text-muted">KimmyDs</span> <a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a><a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">服务器</a> <div>报错问题分析:这是不同系统编码格式引起的:在windows系统中编辑的.sh.py文件可能有不可见字符,所以在Linux系统下执行会报以上异常信息。一般是因为windows行结尾和linux行结尾标识不同造成的。问题解决:1)在windows下转换:利用一些编辑器如UltraEdit或EditPlus等工具先将脚本编码转换,再放到Linux中执行。转换方式如下(UltraEdit):File--></div> </li> <li><a href="/article/1946828267474448384.htm" title="Filebeat + Logstash + ES进行Nginx日志采集" target="_blank">Filebeat + Logstash + ES进行Nginx日志采集</a> <span class="text-muted">一个只会喊666的菜比</span> <div>简易架构图service.png架构图比较简单,日志收集大同小异,这次不添加任何中间服务比如:rediskafka后端只是存储进ES使用的版本jdk-8u161-linux-x64.rpmelasticsearch-6.7.2.rpmlogstash-6.7.2.rpmfilebeat-6.7.2-x86_64.rpm安装比较简单,只用进行rpm-ivh即可,接下来直接贴配置文件:Elastics</div> </li> <li><a href="/article/1946819473017204736.htm" title="头歌实践JAVA项目开发实战入门--第10阶段【Linux操作系统】" target="_blank">头歌实践JAVA项目开发实战入门--第10阶段【Linux操作系统】</a> <span class="text-muted"></span> <div>开始更新头歌了!!!有需要的小伙伴自取吧;有什么好的建议也可以评论区留言,大家一起共勉!Linux操作系统一、Linux初体验#!/bin/bash#在以下部分写出完成任务的命令#*********begin*********#cd/ls-a#*********end*********#二、Linux常用命令#!/bin/bash#在以下部分写出完成任务的命令#*********begin****</div> </li> <li><a href="/article/1946817833321164800.htm" title="docker 安装Home Assistant" target="_blank">docker 安装Home Assistant</a> <span class="text-muted">铭keny</span> <a class="tag" taget="_blank" href="/search/Home/1.htm">Home</a><a class="tag" taget="_blank" href="/search/Assistant/1.htm">Assistant</a><a class="tag" taget="_blank" href="/search/docker/1.htm">docker</a><a class="tag" taget="_blank" href="/search/%E5%AE%B9%E5%99%A8/1.htm">容器</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a> <div>一、安装docker1、先切换到root用户,先安装一些基本环境:yuminstall-yyum-utilsdevice-mapper-persistent-datalvm22、添加阿里云软件源yum-config-manager--add-repohttp://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo3、然后安装Docker</div> </li> <li><a href="/article/1946816295047589888.htm" title="Windows 11 上的 Android 应用程序的工作方式" target="_blank">Windows 11 上的 Android 应用程序的工作方式</a> <span class="text-muted">QWERTY_18fb</span> <div>微软的Windows11公告令我们惊讶的是,即将推出的操作系统将在Windows应用程序旁边运行Android应用程序。不幸的是,主题演讲在细节上很简单。这些应用程序会使用仿真吗?是否会涉及Windows现有的Linux支持?我们在主题演讲后不久就得到了答案,这要归功于后续的开发人员谈话,其中详细介绍了一些细节。该功能正式称为“适用于Android的Windows子系统”,它应该会告诉您很多有关其</div> </li> <li><a href="/article/1946810269481103360.htm" title="【CMake】使用 CMake 构建 C/C++ 项目的标准流程详解" target="_blank">【CMake】使用 CMake 构建 C/C++ 项目的标准流程详解</a> <span class="text-muted"></span> <div>目录️使用CMake构建C/C++项目的标准流程详解目录1️⃣项目结构约定2️⃣跨平台构建环境准备3️⃣标准构建流程4️⃣构建后目录结构示例Linux/macOSWindows+MinGW5️⃣常用构建操作命令6️⃣跨平台命令对照表7️⃣注意事项与最佳实践8️⃣总结相关文章:️使用CMake构建C/C++项目的标准流程详解目录项目结构约定跨平台构建环境准备标准构建流程构建后目录结构示例常用构建操作</div> </li> <li><a href="/article/1946800182238703616.htm" title="【实时Linux实战系列】实时任务与信号处理" target="_blank">【实时Linux实战系列】实时任务与信号处理</a> <span class="text-muted">望获linux</span> <a class="tag" taget="_blank" href="/search/%E5%AE%9E%E6%97%B6Linux%E5%AE%9E%E6%88%98%E7%B3%BB%E5%88%97/1.htm">实时Linux实战系列</a><a class="tag" taget="_blank" href="/search/chrome/1.htm">chrome</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a><a class="tag" taget="_blank" href="/search/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/1.htm">操作系统</a><a class="tag" taget="_blank" href="/search/%E5%B5%8C%E5%85%A5%E5%BC%8F%E8%BD%AF%E4%BB%B6/1.htm">嵌入式软件</a> <div>在实时系统中,信号处理是任务间通信和同步的重要机制之一。信号是一种软件中断,用于在任务之间传递异步事件。实时任务需要能够快速响应信号,以确保系统的实时性和可靠性。掌握信号处理技能对于开发者来说至关重要,它可以帮助开发者设计出更加高效和可靠的实时系统。本文将通过实际案例,详细介绍如何在实时Linux中处理信号,包括信号的基本概念、信号处理机制以及如何在实时任务中有效地使用信号。我们将从基本的信号处理</div> </li> <li><a href="/article/1946778238529368064.htm" title="Linux 网络管理命令大全:网卡、端口、路由全掌握" target="_blank">Linux 网络管理命令大全:网卡、端口、路由全掌握</a> <span class="text-muted"></span> <div>Linux网络管理命令大全:网卡、端口、路由全掌握一.网卡管理(NetworkInterfaces)1.1查看网卡信息1.2启用和禁用网卡1.3配置静态IP二.端口管理2.1查看当前监听的端口2.2检查端口是否被占用2.3开放端口(防火墙配置)三.路由管理3.1查看路由表3.2添加路由四.网络连接诊断4.1测试网络连通性4.2测试某个端口是否开放4.3直接获取网页内容前言肝文不易,点个免费的赞和关</div> </li> <li><a href="/article/1946775172602195968.htm" title="Linux下如何高效回退到特定层级目录?" target="_blank">Linux下如何高效回退到特定层级目录?</a> <span class="text-muted">EchoPython</span> <div>Linux下如果我们进入到了一个比较长的路径,比如:/home/alvin/projects/blogdemos/linux-system-programming/thread/home/alvin/projects/blogdemos/diff/home/harry/study/亚洲文化/日本文化/中日交流/影视业/动作片如果我们想要回退到一个特定的父目录,那么我们通常的做法是这样敲:#cd..</div> </li> <li><a href="/article/1946773649650413568.htm" title="一文掌握oracle19c之离线情况下命令行安装和建库(上)" target="_blank">一文掌握oracle19c之离线情况下命令行安装和建库(上)</a> <span class="text-muted">运维家</span> <div>声明:本文乃“运维家”原创,转载请注明出处,更多内容请关注公众号“运维家”。主旨oracle作为主流数据库之一,身为IT人员,怎么能不会搭建呢?我不允许哈,下来看看如何一步一步的搭建起来吧。建议收藏,不然一会儿就找不见了,哈哈哈。环境linux环境oracle软件根目录下磁盘空间最少4G软件下载官网下载太慢,而且需要注册,这里直接从公众号“运维家”后台回复“oracle”即可获取软件包,即取即用。</div> </li> <li><a href="/article/1946765248237203456.htm" title="运维技术干货 — 不仅是 Linux 运维最佳实践" target="_blank">运维技术干货 — 不仅是 Linux 运维最佳实践</a> <span class="text-muted">python算法小白</span> <a class="tag" taget="_blank" href="/search/Linux/1.htm">Linux</a> <div>附Java/C/C++/机器学习/算法与数据结构/前端/安卓/Python/程序员必读书籍书单大全:书单导航页(点击右侧极客侠栈即可打开个人博客):极客侠栈①【Java】学习之路吐血整理技术书从入门到进阶最全50+本(珍藏版)②【算法数据结构+acm】从入门到进阶吐血整理书单50+本(珍藏版)③【数据库】从入门到进阶必读18本技术书籍网盘吐血整理网盘(珍藏版)④【Web前端】从HTML到JS到AJ</div> </li> <li><a href="/article/1946745644874002432.htm" title="Deepin 与 Ubuntu 系统N卡登录卡死的解决办法" target="_blank">Deepin 与 Ubuntu 系统N卡登录卡死的解决办法</a> <span class="text-muted">蓝色_fea0</span> <div>DeepinLinux介绍深度公司介绍DeepinLinux是一款国产的Linux系统,桌面效果特别的炫酷,而且对Windows上的大多数软件都支持(游戏除外,游戏是不可能游戏的)下面贴几张装好了Deepin系统的桌面截图深度截图_选择区域_20180921092828.png深度截图_20180921002524.png深度截图_选择区域_20180921092741.png我的电脑是I卡集显加</div> </li> <li><a href="/article/1946735257168441344.htm" title="Libevent(3)之使用教程(2)创建事件" target="_blank">Libevent(3)之使用教程(2)创建事件</a> <span class="text-muted">Once-Day</span> <a class="tag" taget="_blank" href="/search/%23/1.htm">#</a><a class="tag" taget="_blank" href="/search/Linux%E5%AE%9E%E8%B7%B5%E8%AE%B0%E5%BD%95/1.htm">Linux实践记录</a><a class="tag" taget="_blank" href="/search/%23/1.htm">#</a><a class="tag" taget="_blank" href="/search/%E5%8D%81%E5%B9%B4%E4%BB%A3%E7%A0%81%E8%AE%AD%E7%BB%83/1.htm">十年代码训练</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/C/1.htm">C</a><a class="tag" taget="_blank" href="/search/libevent/1.htm">libevent</a> <div>Libevent(3)之使用教程(2)创建事件Author:OnceDayDate:2025年6月29日一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦…漫漫长路,有人对你微笑过嘛…本文档翻译于:Fastportablenon-blockingnetworkprogrammingwithLibevent全系列文章可参考专栏:十年代码训练_Once-Day的博客-C</div> </li> <li><a href="/article/1946733181872959488.htm" title="ls总结" target="_blank">ls总结</a> <span class="text-muted">黑客不黑撒</span> <div>linuxls列出目录下所有文件数量http://blog.hehehehehe.cn/a/12311.htm查看统计当前目录下文件的个数,包括子目录里的。ls-lR|grep"^-"|wc-lLinux下查看某个目录下的文件、或文件夹个数用到3个命令:ls列目录、用grep过虑、再用wc统计。举例说明:1、查看统计当前目录下文件的个数ls-l|grep"^-"|wc-l2、查看统计当前目录下文件</div> </li> <li><a href="/article/1946727068045733888.htm" title="cx_Oracle.DatabaseError: Error while trying to retrieve text for error ORA-01804" target="_blank">cx_Oracle.DatabaseError: Error while trying to retrieve text for error ORA-01804</a> <span class="text-muted">智海观潮</span> <a class="tag" taget="_blank" href="/search/Oracle/1.htm">Oracle</a><a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a> <div>问题:使用cx_Oracle连接oracle时报错cx_Oracle.DatabaseError:ErrorwhiletryingtoretrievetextforerrorORA-01804samplecode:importcx_Oracleconn=cx_Oracle.connect(user,pwd,self.ois_tns)解决:排查服务器执行该代码的Linux用户下的.bash_prof</div> </li> <li><a href="/article/1946724042186747904.htm" title="vue2 面试题及详细答案150道(121 - 130)" target="_blank">vue2 面试题及详细答案150道(121 - 130)</a> <span class="text-muted"></span> <div>《前后端面试题》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux…。前后端面试题-专栏总目录文章目录一、本文面试题目录121.Vue2中如何实现组件的动态样式绑定?122.Vue2中如何处理跨域请求</div> </li> <li><a href="/article/1946719881005166592.htm" title="Java 实习模拟面试之信也科技:IO、多线程、集合、MySQL、Redis、HTTP、Linux 常见面试题解析" target="_blank">Java 实习模拟面试之信也科技:IO、多线程、集合、MySQL、Redis、HTTP、Linux 常见面试题解析</a> <span class="text-muted"></span> <div>在本次模拟面试中,我们将模拟一场面向Java实习生岗位的面试,重点围绕Java基础(IO、多线程、集合)、MySQL、Redis、MQ、HTTP协议以及Linux基础等核心知识点。通过模拟面试官提问和候选人的回答方式,帮助你更好地准备技术面试。一、Java基础(IO、多线程、集合)面试官提问:请谈谈你对JavaIO的理解,以及NIO和BIO的区别?候选人回答:JavaIO是Java提供的一套用于处</div> </li> <li><a href="/article/118.htm" title="异常的核心类Throwable" target="_blank">异常的核心类Throwable</a> <span class="text-muted">无量</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E6%BA%90%E7%A0%81/1.htm">源码</a><a class="tag" taget="_blank" href="/search/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/1.htm">异常处理</a><a class="tag" taget="_blank" href="/search/exception/1.htm">exception</a> <div>java异常的核心是Throwable,其他的如Error和Exception都是继承的这个类 里面有个核心参数是detailMessage,记录异常信息,getMessage核心方法,获取这个参数的值,我们可以自己定义自己的异常类,去继承这个Exception就可以了,方法基本上,用父类的构造方法就OK,所以这么看异常是不是很easy package com.natsu; </div> </li> <li><a href="/article/245.htm" title="mongoDB 游标(cursor) 实现分页 迭代" target="_blank">mongoDB 游标(cursor) 实现分页 迭代</a> <span class="text-muted">开窍的石头</span> <a class="tag" taget="_blank" href="/search/mongodb/1.htm">mongodb</a> <div>上篇中我们讲了mongoDB 中的查询函数,现在我们讲mongo中如何做分页查询      如何声明一个游标        var mycursor = db.user.find({_id:{$lte:5}});       迭代显示游标数</div> </li> <li><a href="/article/372.htm" title="MySQL数据库INNODB 表损坏修复处理过程" target="_blank">MySQL数据库INNODB 表损坏修复处理过程</a> <span class="text-muted">0624chenhong</span> <a class="tag" taget="_blank" href="/search/tomcat/1.htm">tomcat</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a> <div>最近mysql数据库经常死掉,用命令net stop mysql命令也无法停掉,关闭Tomcat的时候,出现Waiting for N instance(s) to be deallocated 信息。查了下,大概就是程序没有对数据库连接释放,导致Connection泄露了。因为用的是开元集成的平台,内部程序也不可能一下子给改掉的,就验证一下咯。启动Tomcat,用户登录系统,用netstat -</div> </li> <li><a href="/article/499.htm" title="剖析如何与设计人员沟通" target="_blank">剖析如何与设计人员沟通</a> <span class="text-muted">不懂事的小屁孩</span> <a class="tag" taget="_blank" href="/search/%E5%B7%A5%E4%BD%9C/1.htm">工作</a> <div>最近做图烦死了,不停的改图,改图……。烦,倒不是因为改,而是反反复复的改,人都会死。很多需求人员不知该如何与设计人员沟通,不明白如何使设计人员知道他所要的效果,结果只能是沟通变成了扯淡,改图变成了应付。 那应该如何与设计人员沟通呢? 我认为设计人员与需求人员先天就存在语言障碍。对一个合格的设计人员来说,整天玩的都是点、线、面、配色,哪种构图看起来协调;哪种配色看起来合理心里跟明镜似的,</div> </li> <li><a href="/article/626.htm" title="qq空间刷评论工具" target="_blank">qq空间刷评论工具</a> <span class="text-muted">换个号韩国红果果</span> <a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a> <div> var a=document.getElementsByClassName('textinput'); var b=[]; for(var m=0;m<a.length;m++){ if(a[m].getAttribute('placeholder')!=null) b.push(a[m]) } var l</div> </li> <li><a href="/article/753.htm" title="S2SH整合之session" target="_blank">S2SH整合之session</a> <span class="text-muted">灵静志远</span> <a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/AOP/1.htm">AOP</a><a class="tag" taget="_blank" href="/search/struts/1.htm">struts</a><a class="tag" taget="_blank" href="/search/session/1.htm">session</a> <div>错误信息: Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cartService': Scope 'session' is not active for the current thread; consider defining a scoped</div> </li> <li><a href="/article/880.htm" title="xmp标签" target="_blank">xmp标签</a> <span class="text-muted">a-john</span> <a class="tag" taget="_blank" href="/search/%E6%A0%87%E7%AD%BE/1.htm">标签</a> <div>今天在处理数据的显示上遇到一个问题: var html = '<li><div class="pl-nr"><span class="user-name">' + user + '</span>' + text + '</div></li>'; ulComme</div> </li> <li><a href="/article/1007.htm" title="Ajax的常用技巧(2)---实现Web页面中的级联菜单" target="_blank">Ajax的常用技巧(2)---实现Web页面中的级联菜单</a> <span class="text-muted">aijuans</span> <a class="tag" taget="_blank" href="/search/Ajax/1.htm">Ajax</a> <div>在网络上显示数据,往往只显示数据中的一部分信息,如文章标题,产品名称等。如果浏览器要查看所有信息,只需点击相关链接即可。在web技术中,可以采用级联菜单完成上述操作。根据用户的选择,动态展开,并显示出对应选项子菜单的内容。 在传统的web实现方式中,一般是在页面初始化时动态获取到服务端数据库中对应的所有子菜单中的信息,放置到页面中对应的位置,然后再结合CSS层叠样式表动态控制对应子菜单的显示或者隐</div> </li> <li><a href="/article/1134.htm" title="天-安-门,好高" target="_blank">天-安-门,好高</a> <span class="text-muted">atongyeye</span> <a class="tag" taget="_blank" href="/search/%E6%83%85%E6%84%9F/1.htm">情感</a> <div>    我是85后,北漂一族,之前房租1100,因为租房合同到期,再续,房租就要涨150。最近网上新闻,地铁也要涨价。算了一下,涨价之后,每次坐地铁由原来2块变成6块。仅坐地铁费用,一个月就要涨200。内心苦痛。     晚上躺在床上一个人想了很久,很久。        我生在农</div> </li> <li><a href="/article/1261.htm" title="android 动画" target="_blank">android 动画</a> <span class="text-muted">百合不是茶</span> <a class="tag" taget="_blank" href="/search/android/1.htm">android</a><a class="tag" taget="_blank" href="/search/%E9%80%8F%E6%98%8E%E5%BA%A6/1.htm">透明度</a><a class="tag" taget="_blank" href="/search/%E5%B9%B3%E7%A7%BB/1.htm">平移</a><a class="tag" taget="_blank" href="/search/%E7%BC%A9%E6%94%BE/1.htm">缩放</a><a class="tag" taget="_blank" href="/search/%E6%97%8B%E8%BD%AC/1.htm">旋转</a> <div>android的动画有两种  tween动画和Frame动画   tween动画;,透明度,缩放,旋转,平移效果   Animation   动画 AlphaAnimation 渐变透明度 RotateAnimation 画面旋转 ScaleAnimation 渐变尺寸缩放 TranslateAnimation 位置移动 Animation</div> </li> <li><a href="/article/1388.htm" title="查看本机网络信息的cmd脚本" target="_blank">查看本机网络信息的cmd脚本</a> <span class="text-muted">bijian1013</span> <a class="tag" taget="_blank" href="/search/cmd/1.htm">cmd</a> <div>@echo 您的用户名是:%USERDOMAIN%\%username%>"%userprofile%\网络参数.txt" @echo 您的机器名是:%COMPUTERNAME%>>"%userprofile%\网络参数.txt" @echo ___________________>>"%userprofile%\</div> </li> <li><a href="/article/1515.htm" title="plsql 清除登录过的用户" target="_blank">plsql 清除登录过的用户</a> <span class="text-muted">征客丶</span> <a class="tag" taget="_blank" href="/search/plsql/1.htm">plsql</a> <div>tools---preferences----logon history---history  把你想要删除的删除 -------------------------------------------------------------------- 若有其他凝问或文中有错误,请及时向我指出, 我好及时改正,同时也让我们一起进步。 email : binary_spac</div> </li> <li><a href="/article/1642.htm" title="【Pig一】Pig入门" target="_blank">【Pig一】Pig入门</a> <span class="text-muted">bit1129</span> <a class="tag" taget="_blank" href="/search/pig/1.htm">pig</a> <div>Pig安装 1.下载pig   wget http://mirror.bit.edu.cn/apache/pig/pig-0.14.0/pig-0.14.0.tar.gz   2. 解压配置环境变量      如果Pig使用Map/Reduce模式,那么需要在环境变量中,配置HADOOP_HOME环境变量   expor</div> </li> <li><a href="/article/1769.htm" title="Java 线程同步几种方式" target="_blank">Java 线程同步几种方式</a> <span class="text-muted">BlueSkator</span> <a class="tag" taget="_blank" href="/search/volatile/1.htm">volatile</a><a class="tag" taget="_blank" href="/search/synchronized/1.htm">synchronized</a><a class="tag" taget="_blank" href="/search/ThredLocal/1.htm">ThredLocal</a><a class="tag" taget="_blank" href="/search/ReenTranLock/1.htm">ReenTranLock</a><a class="tag" taget="_blank" href="/search/Concurrent/1.htm">Concurrent</a> <div>为何要使用同步?      java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),      将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,      从而保证了该变量的唯一性和准确性。 1.同步方法&</div> </li> <li><a href="/article/1896.htm" title="StringUtils判断字符串是否为空的方法(转帖)" target="_blank">StringUtils判断字符串是否为空的方法(转帖)</a> <span class="text-muted">BreakingBad</span> <a class="tag" taget="_blank" href="/search/null/1.htm">null</a><a class="tag" taget="_blank" href="/search/StringUtils/1.htm">StringUtils</a><a class="tag" taget="_blank" href="/search/%E2%80%9C%E2%80%9D/1.htm">“”</a> <div>转帖地址:http://www.cnblogs.com/shangxiaofei/p/4313111.html   public static boolean isEmpty(String str)     判断某字符串是否为空,为空的标准是 str== null  或 str.length()== 0  </div> </li> <li><a href="/article/2023.htm" title="编程之美-分层遍历二叉树" target="_blank">编程之美-分层遍历二叉树</a> <span class="text-muted">bylijinnan</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/%E7%BC%96%E7%A8%8B%E4%B9%8B%E7%BE%8E/1.htm">编程之美</a> <div> import java.util.ArrayList; import java.util.LinkedList; import java.util.List; public class LevelTraverseBinaryTree { /** * 编程之美 分层遍历二叉树 * 之前已经用队列实现过二叉树的层次遍历,但这次要求输出换行,因此要</div> </li> <li><a href="/article/2150.htm" title="jquery取值和ajax提交复习记录" target="_blank">jquery取值和ajax提交复习记录</a> <span class="text-muted">chengxuyuancsdn</span> <a class="tag" taget="_blank" href="/search/jquery%E5%8F%96%E5%80%BC/1.htm">jquery取值</a><a class="tag" taget="_blank" href="/search/ajax%E6%8F%90%E4%BA%A4/1.htm">ajax提交</a> <div> // 取值 // alert($("input[name='username']").val()); // alert($("input[name='password']").val()); // alert($("input[name='sex']:checked").val()); // alert($("</div> </li> <li><a href="/article/2277.htm" title="推荐国产工作流引擎嵌入式公式语法解析器-IK Expression" target="_blank">推荐国产工作流引擎嵌入式公式语法解析器-IK Expression</a> <span class="text-muted">comsci</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%BA%94%E7%94%A8%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">应用服务器</a><a class="tag" taget="_blank" href="/search/%E5%B7%A5%E4%BD%9C/1.htm">工作</a><a class="tag" taget="_blank" href="/search/Excel/1.htm">Excel</a><a class="tag" taget="_blank" href="/search/%E5%B5%8C%E5%85%A5%E5%BC%8F/1.htm">嵌入式</a> <div>这个开源软件包是国内的一位高手自行研制开发的,正如他所说的一样,我觉得它可以使一个工作流引擎上一个台阶。。。。。。欢迎大家使用,并提出意见和建议。。。 ----------转帖--------------------------------------------------- IK Expression是一个开源的(OpenSource),可扩展的(Extensible),基于java语言</div> </li> <li><a href="/article/2404.htm" title="关于系统中使用多个PropertyPlaceholderConfigurer的配置及PropertyOverrideConfigurer" target="_blank">关于系统中使用多个PropertyPlaceholderConfigurer的配置及PropertyOverrideConfigurer</a> <span class="text-muted">daizj</span> <a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a> <div>1、PropertyPlaceholderConfigurer Spring中PropertyPlaceholderConfigurer这个类,它是用来解析Java Properties属性文件值,并提供在spring配置期间替换使用属性值。接下来让我们逐渐的深入其配置。 基本的使用方法是:(1) <bean id="propertyConfigurerForWZ&q</div> </li> <li><a href="/article/2531.htm" title="二叉树:二叉搜索树" target="_blank">二叉树:二叉搜索树</a> <span class="text-muted">dieslrae</span> <a class="tag" taget="_blank" href="/search/%E4%BA%8C%E5%8F%89%E6%A0%91/1.htm">二叉树</a> <div>    所谓二叉树,就是一个节点最多只能有两个子节点,而二叉搜索树就是一个经典并简单的二叉树.规则是一个节点的左子节点一定比自己小,右子节点一定大于等于自己(当然也可以反过来).在树基本平衡的时候插入,搜索和删除速度都很快,时间复杂度为O(logN).但是,如果插入的是有序的数据,那效率就会变成O(N),在这个时候,树其实变成了一个链表. tree代码: </div> </li> <li><a href="/article/2658.htm" title="C语言字符串函数大全" target="_blank">C语言字符串函数大全</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/c/1.htm">c</a><a class="tag" taget="_blank" href="/search/function/1.htm">function</a> <div>C语言字符串函数大全     函数名: stpcpy 功 能: 拷贝一个字符串到另一个 用 法: char *stpcpy(char *destin, char *source); 程序例:   #include <stdio.h> #include <string.h>   int main</div> </li> <li><a href="/article/2785.htm" title="友盟统计页面技巧" target="_blank">友盟统计页面技巧</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/%E6%8A%80%E5%B7%A7/1.htm">技巧</a> <div>在基类调用就可以了, 基类ViewController示例代码 -(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [MobClick beginLogPageView:[NSString stringWithFormat:@"%@",self.class]]; </div> </li> <li><a href="/article/2912.htm" title="window下在同一台机器上安装多个版本jdk,修改环境变量不生效问题处理办法" target="_blank">window下在同一台机器上安装多个版本jdk,修改环境变量不生效问题处理办法</a> <span class="text-muted">flyvszhb</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/jdk/1.htm">jdk</a> <div>window下在同一台机器上安装多个版本jdk,修改环境变量不生效问题处理办法 本机已经安装了jdk1.7,而比较早期的项目需要依赖jdk1.6,于是同时在本机安装了jdk1.6和jdk1.7. 安装jdk1.6前,执行java -version得到 C:\Users\liuxiang2>java -version java version "1.7.0_21&quo</div> </li> <li><a href="/article/3039.htm" title="Java在创建子类对象的同时会不会创建父类对象" target="_blank">Java在创建子类对象的同时会不会创建父类对象</a> <span class="text-muted">happyqing</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%88%9B%E5%BB%BA/1.htm">创建</a><a class="tag" taget="_blank" href="/search/%E5%AD%90%E7%B1%BB%E5%AF%B9%E8%B1%A1/1.htm">子类对象</a><a class="tag" taget="_blank" href="/search/%E7%88%B6%E7%B1%BB%E5%AF%B9%E8%B1%A1/1.htm">父类对象</a> <div>  1.在thingking in java 的第四版第六章中明确的说了,子类对象中封装了父类对象,   2."When you create an object of the derived class, it contains within it a subobject of the base class. This subobject is the sam</div> </li> <li><a href="/article/3166.htm" title="跟我学spring3 目录贴及电子书下载" target="_blank">跟我学spring3 目录贴及电子书下载</a> <span class="text-muted">jinnianshilongnian</span> <a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a> <div>    一、《跟我学spring3》电子书下载地址: 《跟我学spring3》  (1-7 和 8-13) http://jinnianshilongnian.iteye.com/blog/pdf     跟我学spring3系列 word原版 下载     二、 源代码下载 最新依</div> </li> <li><a href="/article/3420.htm" title="第12章 Ajax(上)" target="_blank">第12章 Ajax(上)</a> <span class="text-muted">onestopweb</span> <a class="tag" taget="_blank" href="/search/Ajax/1.htm">Ajax</a> <div>index.html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/</div> </li> <li><a href="/article/3547.htm" title="BI and EIM 4.0 at a glance" target="_blank">BI and EIM 4.0 at a glance</a> <span class="text-muted">blueoxygen</span> <a class="tag" taget="_blank" href="/search/BO/1.htm">BO</a> <div>http://www.sap.com/corporate-en/press.epx?PressID=14787   有机会研究下EIM家族的两个新产品~~~~   New features of the 4.0 releases of BI and EIM solutions include: Real-time in-memory computing – </div> </li> <li><a href="/article/3674.htm" title="Java线程中yield与join方法的区别" target="_blank">Java线程中yield与join方法的区别</a> <span class="text-muted">tomcat_oracle</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>长期以来,多线程问题颇为受到面试官的青睐。虽然我个人认为我们当中很少有人能真正获得机会开发复杂的多线程应用(在过去的七年中,我得到了一个机会),但是理解多线程对增加你的信心很有用。之前,我讨论了一个wait()和sleep()方法区别的问题,这一次,我将会讨论join()和yield()方法的区别。坦白的说,实际上我并没有用过其中任何一个方法,所以,如果你感觉有不恰当的地方,请提出讨论。 &nb</div> </li> <li><a href="/article/3801.htm" title="android Manifest.xml选项" target="_blank">android Manifest.xml选项</a> <span class="text-muted">阿尔萨斯</span> <a class="tag" taget="_blank" href="/search/Manifest/1.htm">Manifest</a> <div>结构 继承关系 public final class Manifest extends Objectjava.lang.Objectandroid.Manifest 内部类 class Manifest.permission权限 class Manifest.permission_group权限组 构造函数 public Manifest () 详细 androi</div> </li> <li><a href="/article/3928.htm" title="Oracle实现类split函数的方" target="_blank">Oracle实现类split函数的方</a> <span class="text-muted">zhaoshijie</span> <a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a> <div>关键字:Oracle实现类split函数的方 项目里需要保存结构数据,批量传到后他进行保存,为了减小数据量,子集拼装的格式,使用存储过程进行保存。保存的过程中需要对数据解析。但是oracle没有Java中split类似的函数。从网上找了一个,也补全了一下。 CREATE OR REPLACE TYPE t_split_100 IS TABLE OF VARCHAR2(100); cr</div> </li> </ul> </div> </div> </div> <div> <div class="container"> <div class="indexes"> <strong>按字母分类:</strong> <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a> </div> </div> </div> <footer id="footer" class="mb30 mt30"> <div class="container"> <div class="footBglm"> <a target="_blank" href="/">首页</a> - <a target="_blank" href="/custom/about.htm">关于我们</a> - <a target="_blank" href="/search/Java/1.htm">站内搜索</a> - <a target="_blank" href="/sitemap.txt">Sitemap</a> - <a target="_blank" href="/custom/delete.htm">侵权投诉</a> </div> <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved. <!-- <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>--> </div> </div> </footer> <!-- 代码高亮 --> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script> <link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/> <script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script> </body> </html>