Headless Chrome入門(mén)

原文地址:Getting Started with Headless Chrome By Eric Bidelman Engineer @ Google working on web tooling: Headless Chrome, Puppeteer, Lighthouse

Headless Chrome在Chrome59中發(fā)布,用于在headless環(huán)境中運(yùn)行Chrome瀏覽器劫侧,也就是在非Chrome環(huán)境中運(yùn)行Chrome。它將Chromium和Blink渲染引擎提供的所有現(xiàn)代Web平臺(tái)功能引入命令行。

它有什么用處呢叨橱?

headless瀏覽器是自動(dòng)測(cè)試和服務(wù)器環(huán)境的絕佳工具呢铆,您不需要可見(jiàn)的UI shell朗恳。例如,針對(duì)真實(shí)的網(wǎng)頁(yè)進(jìn)行測(cè)試叨襟,創(chuàng)建網(wǎng)頁(yè)的PDF,或者只是檢查瀏覽器如何呈現(xiàn)URL幔荒。

0. 開(kāi)始

最簡(jiǎn)單的開(kāi)始使用headless模式的方法是從命令行打開(kāi)Chrome糊闽。如果你已經(jīng)安裝了Chrome59+的版本,可以使用 --headless 標(biāo)簽:

chrome \
  --headless \                   # 在headless模式運(yùn)行Chrome
  --disable-gpu \                # 在Windows上運(yùn)行時(shí)需要--remote-debugging-port=9222 \
  https://www.chromestatus.com   # 打開(kāi)URL. 默認(rèn)為about:blank

注意:若在Windows中運(yùn)行爹梁,則需要在命令行添加 --disable-gpu 右犹。

chrome 命令需要指向Chrome的安裝路徑。(即在Chrome的安裝路徑下運(yùn)行)

1. 命令行功能

在某些情況下姚垃,您可能不需要以編程方式編寫(xiě)Headless Chrome腳本傀履。下面是一些有用的命令行標(biāo)志來(lái)執(zhí)行常見(jiàn)任務(wù)。

1.1 打印DOM --dump-dom

將 document.body.innerHTML 在stdout打印出來(lái):

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

1.2 創(chuàng)建PDF --print-to-pdf :

chrome --headless --disable-gpu --print-to-pdf https://www.chromestatus.com/

演示:在chrome安裝目錄下運(yùn)行 chrome --headless --disable-gpu --print-to-pdf https://www.baidu.com/

命令行運(yùn)行

生成PDF文件:C:\Program Files (x86)\Google\Chrome\Application\69.0.3497.81\output.pdf

生成的pdf文件

1.3 截屏 --screenshot

chrome --headless --disable-gpu --screenshot https://www.chromestatus.com/
 # 標(biāo)準(zhǔn)屏幕大小
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/

運(yùn)行 --screenshot將會(huì)在當(dāng)前運(yùn)行目錄下生成一個(gè) screenshot.png 文件莉炉。若想給整個(gè)頁(yè)面的截圖钓账,那么會(huì)比較復(fù)雜。來(lái)自 David Schnurr 的一篇很棒的博文介紹了這一內(nèi)容絮宁。請(qǐng)查看 使用 headless Chrome 作為自動(dòng)截屏工具梆暮。

1.4 REPL模式(read-eval-print loop) --repl

在REPL模式運(yùn)行Headless,該模式允許通過(guò)命令行在瀏覽器中評(píng)估JS表達(dá)式:

$ chrome --headless --disable-gpu --repl --crash-dumps-dir=./tmp 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
$

注意:使用repl模式時(shí)需要添加 --crash-dumps-dir 命令绍昂。

2. 在沒(méi)有瀏覽器界面情況下調(diào)試Chrome

當(dāng)使用 --remote-debugging-port=9222 運(yùn)行Chrome時(shí)啦粹,會(huì)啟用DevTools協(xié)議的實(shí)例偿荷。該協(xié)議用于與Chrome通信并且驅(qū)動(dòng)headless瀏覽器實(shí)例。除此之外唠椭,它還是一個(gè)類(lèi)似于 Sublime, VS Code, 和Node的工具跳纳,可用于遠(yuǎn)程調(diào)試一個(gè)應(yīng)用。

由于沒(méi)有瀏覽器UI來(lái)查看頁(yè)面贪嫂,因此需要在另一個(gè)瀏覽器中導(dǎo)航到http:// localhost:9222以檢查一切是否正常寺庄。這將看到一個(gè)可查看頁(yè)面的列表,可以在其中單擊并查看Headless正在呈現(xiàn)的內(nèi)容:

DevTools遠(yuǎn)程調(diào)試界面

在這里力崇,你可以使用熟悉的DecTools功能來(lái)查看斗塘、調(diào)試、修改頁(yè)面亮靴。若以編程方式(programmatically)使用Headless馍盟,該頁(yè)面的功能更強(qiáng)大,可以用于查看所有的DecTools協(xié)議的命令茧吊,并與瀏覽器進(jìn)行通信贞岭。

3. 使用編程模式(Node)

3.1 Puppeteer

Puppeteer 由Chrome團(tuán)隊(duì)開(kāi)發(fā)的Node庫(kù)。它提供了控制headless Chrome的高階API搓侄。類(lèi)似于 Phantom 和 NightmareJS這樣的自動(dòng)測(cè)試庫(kù)瞄桨,但它只用于最新版本的Chrome。

除此之外休讳,Puppeteer還可用于截屏讲婚,創(chuàng)建PDF,頁(yè)面導(dǎo)航俊柔,以及獲取有關(guān)這些頁(yè)面的信息筹麸。如果需要快速進(jìn)行瀏覽器的自動(dòng)化測(cè)試,建議使用該庫(kù)雏婶。它隱藏了DevTools協(xié)議的復(fù)雜性物赶,并負(fù)責(zé)啟動(dòng)Chrome的調(diào)試實(shí)例等冗余任務(wù)。

安裝:

npm i --save puppeteer

例子-打印用戶(hù)代理信息:

const puppeteer = require('puppeteer');

(async() => { const browser = await puppeteer.launch();
  console.log(await browser.version()); await 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: 'networkidle2'}); await page.pdf({path: 'page.pdf', format: 'A4'}); await browser.close();
})();

查看 Puppeteer's 文檔 學(xué)習(xí)Puppeteer的更多用法留晚。

3.2 CRI庫(kù)

相對(duì)于Puppeteer's API來(lái)說(shuō)酵紫,chrome-remote-interface 是一個(gè)低階的庫(kù),推薦使用它更接近底層地直接使用DevTools協(xié)議错维。

打開(kāi)Chrome

chrome-remote-interface不能打開(kāi)Chrome奖地,因此需要自己打開(kāi)Chrome。

在CLI部分赋焕,我們使用--headless --remote-debugging-port = 9222手動(dòng)打開(kāi)Chrome参歹。但是,要實(shí)現(xiàn)完全自動(dòng)化測(cè)試隆判,您可能希望從應(yīng)用程序中生成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) => {
  ...
});

但是如果你想要一個(gè)適用于多個(gè)平臺(tái)的可移植解決方案僧界,那么事情會(huì)變得棘手〕敉欤看看Chrome的硬編碼路徑吧:(

使用ChromeLaucher

Lighthouse 是測(cè)試web應(yīng)用質(zhì)量絕佳工具捂襟。用于啟動(dòng)Chrome的強(qiáng)大的模塊就是在Lighthouse中開(kāi)發(fā)的,現(xiàn)在可以單獨(dú)使用欢峰。 chrome-launcher NPM module 可以找到Chrome的安裝路徑葬荷,設(shè)置調(diào)試實(shí)例,打開(kāi)瀏覽器赤赊,并且當(dāng)程序運(yùn)行完成時(shí)關(guān)掉它闯狱。最棒的是煞赢,由于Node抛计,它可以跨平臺(tái)工作!

默認(rèn)情況下照筑,chrome-launcher會(huì)嘗試啟動(dòng)Chrome Canary(如果已安裝)吹截,但可以更改它以手動(dòng)選擇要使用的Chrome。要使用它凝危,首先從npm安裝:

npm i --save chrome-launcher

例子-使用 chrome-launcher 啟動(dòng)Headless模式

const chromeLauncher = require('chrome-launcher'); // 可選: 設(shè)置launcher的日志記錄級(jí)別以查看其輸出 // 安裝:: npm i --save lighthouse-logger // const log = require('lighthouse-logger'); // log.setLevel('info');

/**
 * 啟動(dòng)Chrome的調(diào)試實(shí)例
 * @param {boolean=} headless True (default) 啟動(dòng)headless模式的Chrome.
 *     False 啟動(dòng)Chrome的完成版本.
 * @return {Promise<ChromeLauncher>} */ 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();
});

運(yùn)行此腳本并沒(méi)有太大作用波俄,但在任務(wù)管理器中應(yīng)該可以看到Chrome實(shí)例已啟動(dòng),內(nèi)容為 about:blank 蛾默。但是沒(méi)有瀏覽器界面懦铺。因?yàn)槭莌eadless模式。

要控制瀏覽器支鸡,我們需要DevTools協(xié)議冬念!

檢索有關(guān)頁(yè)面的信息

安裝:

npm i --save chrome-remote-interface

例子-打印用戶(hù)代理

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

...

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

結(jié)果類(lèi)似于: HeadlessChrome/60.0.3082.0

例子-檢查網(wǎng)站是否有應(yīng)用列表

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.
});

})();

例子-使用DOM API提取頁(yè)面的<title>

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.
});

})();

4. 使用Selenium,W??ebDriver和ChromeDriver

現(xiàn)在牧挣,Selenium打開(kāi)了一個(gè)完整地Chrome的實(shí)例急前,也就是說(shuō),換句話說(shuō)瀑构,它是一種自動(dòng)化解決方案裆针,但并非完全headless。但是寺晌,Selenium可以通過(guò)一些配置來(lái)運(yùn)行headless Chrome世吨。我建議使用headless Chrome運(yùn)行Selenium,若你還是想要如何自己設(shè)置的完整說(shuō)明呻征,我已經(jīng)在下面的一些例子中展示了如何讓你放棄耘婚。

使用ChromeDriver

ChromeDriver 2.32使用了Chrome61,并且在headless Chrome運(yùn)行的更好怕犁。

安裝:

npm i --save-dev selenium-webdriver chromedriver

例子

const fs = require('fs'); const webdriver = require('selenium-webdriver'); const chromedriver = require('chromedriver'); const chromeCapabilities = webdriver.Capabilities.chrome();
chromeCapabilities.set('chromeOptions', {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();

使用WebDriverIO

WebDriverIO 是Selenium WebDriver之上的更高階的API边篮。

安裝:

npm i --save-dev webdriverio chromedriver

例子-chromestatus.com上的CSS filter功能

const webdriverio = require('webdriverio'); const chromedriver = require('chromedriver'); const PORT = 9515;

chromedriver.start([ '--url-base=wd/hub',
  `--port=${PORT}`, '--verbose' ]);

(async () => { const opts = {
  port: PORT,
  desiredCapabilities: {
    browserName: 'chrome',
    chromeOptions: {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();

})();

5. 更多資源

以下是一些有用的資源己莺,可幫助您入門(mén):

文檔:

工具:

  • chrome-remote-interface - 包裝DevTools協(xié)議的節(jié)點(diǎn)模塊
  • Lighthouse - 用于測(cè)試Web應(yīng)用質(zhì)量的自動(dòng)化工具;大量使用協(xié)議
  • chrome-launcher - 節(jié)點(diǎn)模塊戈轿,用于啟動(dòng)Chrome凌受,為自動(dòng)化做好準(zhǔn)備

演示:

  • "The Headless Web" - Paul Kinlan關(guān)于使用Headless和api.ai的博客文章

6. FAQ

6.1 是否需要 --disable-gpu 命令?

僅Windows平臺(tái)需要思杯。其他平臺(tái)不需要胜蛉。--disable-gpu命令是一個(gè)臨時(shí)解決一些錯(cuò)誤的方案。在將來(lái)的Chrome版本中色乾,不再需要此命令誊册。有關(guān)更多信息,請(qǐng)參閱 crbug.com/737678暖璧。

6.2 是否需要 Xvfb案怯?

不需要。Headless Chrome不使用窗口澎办,因此不再需要像Xvfb這樣的顯示服務(wù)器嘲碱。沒(méi)有它,也可以愉快地運(yùn)行自動(dòng)化測(cè)試局蚀。

什么是Xvfb麦锯?Xvfb是一種用于類(lèi)Unix系統(tǒng)的內(nèi)存顯示服務(wù)器,它使您能夠運(yùn)行圖形應(yīng)用程序(如Chrome)而無(wú)需附加物理顯示設(shè)備琅绅。許多人使用Xvfb運(yùn)行早期版本的Chrome進(jìn)行“headless”測(cè)試扶欣。

6.3 如何創(chuàng)建運(yùn)行Headless Chrome的Docker容器?

看看lighthouse-ci千扶。它有一個(gè)示例 Dockerfile 料祠,它使用node:8-slim作為基本映像,在App Engine Flex上安裝+ 運(yùn)行Lighthouse 县貌。

6.4 Headless Chrome與PhantomJS有什么關(guān)系术陶?

Headless Chrome與PhantomJS等工具類(lèi)似。兩者都可用于headless環(huán)境中的自動(dòng)化測(cè)試煤痕。兩者之間的主要區(qū)別在于Phantom使用較舊版本的WebKit作為其渲染引擎梧宫,而Headless Chrome使用最新版本的Blink。

目前摆碉,Phantom還提供了比DevTools 協(xié)議更高級(jí)別的API塘匣。

6.5 在哪里提交bugs?

對(duì)于Headless Chrome的bugs巷帝,請(qǐng)?jiān)?a target="_blank" rel="nofollow">crbug.com上提交忌卤。

對(duì)于DevTools協(xié)議中的錯(cuò)誤,請(qǐng)將它們發(fā)送到github.com/ChromeDevTools/devtools-protocol楞泼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驰徊,一起剝皮案震驚了整個(gè)濱河市笤闯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌棍厂,老刑警劉巖颗味,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異牺弹,居然都是意外死亡浦马,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)张漂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)晶默,“玉大人,你說(shuō)我怎么就攤上這事航攒』嵌福” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵屎债,是天一觀的道長(zhǎng)仅政。 經(jīng)常有香客問(wèn)我垢油,道長(zhǎng)盆驹,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任滩愁,我火速辦了婚禮躯喇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘硝枉。我一直安慰自己廉丽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布妻味。 她就那樣靜靜地躺著正压,像睡著了一般。 火紅的嫁衣襯著肌膚如雪责球。 梳的紋絲不亂的頭發(fā)上焦履,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音雏逾,去河邊找鬼嘉裤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛栖博,可吹牛的內(nèi)容都是我干的屑宠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼仇让,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼典奉!你這毒婦竟也來(lái)了躺翻?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤卫玖,失蹤者是張志新(化名)和其女友劉穎获枝,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體骇笔,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡省店,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笨触。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片懦傍。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖芦劣,靈堂內(nèi)的尸體忽然破棺而出粗俱,到底是詐尸還是另有隱情,我是刑警寧澤虚吟,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布寸认,位于F島的核電站,受9級(jí)特大地震影響串慰,放射性物質(zhì)發(fā)生泄漏偏塞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一邦鲫、第九天 我趴在偏房一處隱蔽的房頂上張望灸叼。 院中可真熱鬧,春花似錦庆捺、人聲如沸古今。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)捉腥。三九已至,卻和暖如春你画,著一層夾襖步出監(jiān)牢的瞬間抵碟,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工撬即, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留立磁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓剥槐,卻偏偏與公主長(zhǎng)得像唱歧,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 技術(shù)雷達(dá)快訊:自2017年中以來(lái),Chrome用戶(hù)可以選擇以headless模式運(yùn)行瀏覽器沿后。此功能非常適合運(yùn)行前端...
    ThoughtWorks閱讀 7,853評(píng)論 8 28
  • 1.puppeteer簡(jiǎn)介 puppeteer是一個(gè)node庫(kù)沿彭,是Google chrome團(tuán)隊(duì)官方的無(wú)界面(he...
    伊人風(fēng)采_690d閱讀 7,619評(píng)論 0 11
  • 昨晚先生單位有事,搞的一晚上沒(méi)有睡好尖滚。昨天剛買(mǎi)的煎餅檔本想退回去喉刘,想想就算了,將就用吧漆弄,早上和兒子一起合作好早飯睦裳,...
    姣燕閱讀 283評(píng)論 0 1
  • 風(fēng)告訴了我該去的地方, 云擋住了我的去路撼唾; 我不知道云里是什么廉邑, 于是我扎進(jìn)了云里, 可是我還什么都沒(méi)有看清云就隨...
    空氣中的魚(yú)閱讀 232評(píng)論 0 3